0% encontró este documento útil (0 votos)
187 vistas

Manual - Java II - Java EE

Cargado por

Sergio Grau
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
187 vistas

Manual - Java II - Java EE

Cargado por

Sergio Grau
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 444

carrier

PROGRAMADOR JAVA DE ORACLE


escola

JAVA EE

MANUAL DEL CURSO

Nombre
Curso
Grupo
MANUAL JAVA
APLICACIONES WEB JAVA EE

ACADEMIA CARRIER)250$&,Ð1 2014

1
Índice de contenidos
UNIDAD 11. APLICACIONES WEB J2EE Y HTML53 UNIDAD 16. ACCESO A DATOS. SEGURIDAD.
1. Aplicaciones web Java EE ..................................................... 3 PATRONES DE DISEÑO ........................................... 208
2. El protocolo HTTP ................................................................ 7 1. Tecnologías de acceso remoto a datos. ............................. 208
3. Aplicación de servidor web para Java EE ............................ 11 2. Pool de conexiones en servidores web .............................. 214
4. Fundamentos de HTML5 ..................................................... 22 3. Seguridad en aplicaciones Web......................................... 220
5. Fundamentos de CSS ........................................................... 34 4. Patrones de diseño............................................................. 235
6. Fundamentos de JavaScript ................................................. 41 PRÁCTICA........................................................................... 246
PRÁCTICA ............................................................................. 49 UNIDAD 17. DOCUMENTOS XML. ........................ 247
UNIDAD 12. LA TECNOLOGÍA SERVLET ............. 51 1. Las tecnologías XML. ....................................................... 247
1. Fundamentos sobre Servlets ................................................ 51 2. JAXP: APIs de Java para XML ........................................ 271
2. Contexto de solicitud y respuesta de los servlets ................. 61 3. El API JAXB. ................................................................... 286
3. Métodos de solicitud en servlets .......................................... 68 4. Librería JSTL XML. ......................................................... 292
4. Redirigiendo la respuesta en servlets. .................................. 80 PRÁCTICA........................................................................... 294
PRÁCTICA ............................................................................. 87 UNIDAD 18. COMPONENTES JAVA EE: EJB 3.0 296
UNIDAD 13. GESTIÓN DE SERVLETS Y FILTROS89 1. Tecnología JMS ................................................................ 296
1. Gestión de sesiones.............................................................. 89 2. EJB 3.0.............................................................................. 304
2. Gestión de eventos y registro de eventos. ............................ 91 3. Ciclo de vida de EJBs e inyección de dependencias. ........ 327
3. Gestión de estado ................................................................. 95 4. Servicio de temporizador en beans.................................... 335
4. Filtros ................................................................................ 111 5. EJBs como contextos de persistencia ................................ 340
PRÁCTICA ........................................................................... 122 6. Seguridad en Java EE. ....................................................... 348
UNIDAD 14. LA TECNOLOGÍA JSP ....................... 124 PRÁCTICA........................................................................... 359
1. Fundamentos de JSP .......................................................... 124 UNIDAD 19. SERVICIOS WEB ................................ 360
2. Reutilización de componentes web .................................... 135 1. Fundamentos sobre servicios Web .................................... 360
3. Uso de JavaBeans en páginas JSP ..................................... 140 2. Servicios web RESTful y JAX-RS.................................... 371
4. JSTL y el lenguaje EL ....................................................... 146 3. Clientes de servicios web RESTful. .................................. 378
PRÁCTICA ........................................................................... 160 4. Técnicas avanzadas con JAX-RS ...................................... 387
UNIDAD 15. ETIQUETAS PERSONALIZADAS .... 163 PRÁCTICA........................................................................... 397
1. Fundamentos de librerías de etiquetas ............................... 163 UNIDAD 20. APLICACIONES MVC ........................ 398
2. Etiquetas basadas en clases manejadoras ........................... 168 1. El marco Spring ................................................................ 398
3. Opciones avanzadas en clases manejadoras de etiquetas. .. 192 2. Técnicas avanzadas con Spring ......................................... 416
4. Ficheros de etiqueta ........................................................... 199 3. Introducción al marco Structs 1.3. .................................... 427
5. Técnicas avanzadas con TLDs ........................................... 205 PRÁCTICA........................................................................... 440
PRÁCTICA ........................................................................... 207

2
UNIDAD 11.APLICACIONES WEB J2EE Y HTML5
1. Aplicaciones webJava EE
J2EE o Java Plataform Enterprise Edition (conocido desde la versión 1.4 como Java EE) es una plataforma
de programación para desarrollar y ejecutar software de aplicaciones en el lenguaje de programación Java.
Permite utilizar arquitecturas de N capas distribuidas y se apoya ampliamente en componentes de software
modulares ejecutándose sobre un servidor de aplicaciones. Java EE tiene varias especificaciones de API, tales
como JDBC, RMI, e-mail, JMS, Servicios Web, XML, etc. y define cómo coordinarlos. Java EE también
configura algunas especificaciones únicas para componentes Java EE. Estas incluyen Enterprise JavaBeans,
servlets, portlets (siguiendo la especificación de Portlets Java), JavaServer Pages (JSPs) y varias tecnologías de
servicios web. Ello permite al desarrollador crear una Aplicación de Empresa portable entre plataformas y
escalable, a la vez que integrable con tecnologías anteriores. Otros beneficios añadidos son, por ejemplo, que
el servidor de aplicaciones puede manejar transacciones, seguridad, escalabilidad, concurrencia y gestión de
los componentes desplegados, significando esto que los desarrolladores pueden concentrarse más en la lógica
de negocio de los componentes en lugar de en tareas de mantenimiento de bajo nivel.
1.1. Las tecnologías Java EE.
Las APIs de Java 2EE incluyen varias tecnologías que extienden la funcionalidad de las APIs base de Java SE.
javax.ejb.*
La API Enterprise JavaBeans define un conjunto de APIs que un contenedor de objetos distribuidos
soportará para suministrar persistencia, RPCs (usando RMI o RMI-IIOP), control de concurrencia,
transacciones y control de acceso para objetos distribuidos.
javax.naming
Los paquetes javax.naming, javax.naming.directory, javax.naming.event, javax.naming.ldap y javax.naming.spi
definen la API de Java Naming and Directory Interface (JNDI).
java.sql
Los paquetes java.sql y javax.sql definen la API JDBC.
java.transaction.*
Estos paquetes definen la Java Transaction API (JTA).
javax.xml.*
Estos paquetes definen la API JAXP, para manipular documentos XML.
javax.jms.*
Estos paquetes definen la API JMS, para gestionar mensajes de e-mail.
javax.persistence
Este paquete provee las clases e interfaces para gestionar la interacción entre los proveedores de
persistencia, las clases administradas y los clientes de la API Java Persistence.
1.2. Arquitectura por capas.
Java EE es una plataforma capaz de proporcionar algunos de los servicios que una aplicación empresarial
necesita:
• La habilidad de almacenar datos en distintas bases de datos comerciales.
• La habilidad de distribuir la aplicación en más de un computador.
• Soportar transacciones.
• Ejecuciones multihilo.
• Gestión de pool de conexiones.
• Diseño escalable.
Java EE permite descomponer una aplicación en componentes que podemos disponer por capas. En los años
80's y 90's era habitual utilizar una arquitectura de 2 capas.

3
Figura 1

Sin embargo, esta arquitectura tiene el inconveniente de que la lógica de negocio y la lógica de presentación
están mezcladas, lo que resulta en un sistema difícil de entender y de mantener.
Con un modelo de 3 capas se separa la lógica de presentación y la lógica de negocio en 2 capas distintas.
Figura 2

Pero tiene la desventaja de la complejidad involucrada en desarrollar la aplicación, y de se requiere por parte
del desarrollador conocimientos de computo distribuido (por ejemplo, sobre RMI, CORBA, ...).
La Arquitectura Java EE es muy similar al modelo de tres capas peromás más extendido:
Figura 3

La tecnología Java EE soporta multicapa y se apoya en Servidores de aplicaciones. Un Servidor de aplicación


proporciona un marco de trabajo para desarrollar la aplicación multicapa. Este marco de trabajo proporciona
servicios tales como cómputo distribuido, multihilo, seguridad y persistencia.
En el pasado, cada servidor de aplicación era desarrollado independientemente y por lo tanto implementaba
los servicios de diferente manera. Por lo tanto, cambiar de un servidor de aplicaciones a otro era difícil o
imposible. En 1997 un grupo de vendedores de servidores de aplicaciones ( BEA, IBM, Oracle, Sybase, SUN
entre otros) comenzaron a trabajar juntos para definir un estándar en los servidores de aplicación basados en
el lenguaje Java. La visión fue crear un conjunto de servicios estandarizados y unas APIs para acceder a estos
servicios. Como resultado se creó el estándar J2EE.
J2EE define tres tipos de componentes que un programador puede desarrollar en cada capa:
• Servlets, para las capas de la lógica del negocio.
• JSP, para la capa de presentación.
• Enterprise Java Beans, para las capas que invocan servicios y acceden a datos.
Esos tres componentes se desarrollan en el lado servidor. Esto significa que el código que implementa cada
uno de estos tres componentes es ejecutado por un servidor de aplicación. Pero también podemos hablar de
componentes del lado cliente, como las páginas HTML que incluso pueden contener código script. Los
componentes del lado cliente se desarrollan como parte de la aplicación pero no son ejecutados por el
servidor de aplicaciones, sino que su contenido o código script es interpretado por el propio cliente.
1.3. Modelo cliente/servidor en aplicaciones Web.
Las aplicaciones Java EE para Web se construyen con componentes web que realizan tareas específicas y que
exponen sus servicios a través de una red usando el protocolo HTTP. Las aplicaciones Web que usan

4
tecnologías Java se componen de: servlets, páginas JSP, archivos HTML, archivos de imágenes y otros. Todos
estos componentes deben coordinarse entre sí para ofrecer un conjunto completo de servicios a los usuarios.
En general, las aplicaciones Web residen en una aplicación servidora Web (por ejemplo, Apache, ISS,
Glassfish, etc.). Pero cada tecnología de desarrollo requiere de un servidor Web que la soporte. Por ejemplo,
la tecnología ASPX de Microsoft es soportada por el servidor Web IIS, pero no por el servidor Glassfish.
Para la tecnología Java EE los servidores Web que los soportan son Apache, Glassfish, JBoss y otros.
La aplicación servidora para Java EE gestiona los diversos recursos mediante: un contenedor de servlets, un
contenedor de EJB, un servidor JNDI, un servidor JMS, etc. Todos estos recursos están descritos en un
archivo de configuración denominado web.xml.
En las aplicaciones Web se distinguen dos tipos de recursos:
• Recursos del lado servidor o dinámicos. Incluyen código que es ejecutado por el servidor correspondiente.
• Recursos del lado cliente o estáticos. No incluyen código que deba ser ejecutado por un servidor.
Una aplicación Web no se ejecuta en un único proceso o en una única máquina. En vez de eso, normalmente
se hospeda en un servidor Web y es accedida a través de un navegador Web usando el protocolo HTTP. Es
necesario tener una comprensión básica de cómo trabajan estos elementos y se comunican entre sí antes de
empezar a escribir código.
Figura 4

El proceso de comunicación típico entre un navegador y un servidor se puede generalizar en los siguientes
pasos:
1) Un usuario usa un navegador Web (como Internet Explores, Chrome u otro) para iniciar un
requerimiento a un recurso de un servidor Web.
2) Se utiliza el protocolo HTTP para enviar un requerimiento de tipo GET al servidor Web.
3) El servidor Web procesa el requerimiento GET sobre el servidor (normalmente localiza el recurso
requerido y lo ejecuta).
4) El servidor Web envía entonces una respuesta al navegador Web. Se usa el protocolo HTTP para enviar
la respuesta.
5) El navegador Web procesa la respuesta (normalmente ésta llega en formato HTML y JavaScript) y
renderiza una página para mostrársela al usuario.
6) El usuario puede introducir datos y realizar acciones como pulsar sobre un botón para enviar datos de
regreso al servidor Web para que los procese.
7) Se usa el protocolo HTTP para enviar los datos de regreso al servidor Web, realizando un requerimiento
de tipo POST.
8) El servidor Web procesa el requerimiento POST (otra vez, ejecutando algún código).
9) El servidor Web envía una respuesta al navegador Web mediante el protocolo HTTP.
10) El navegador Web vuelve a procesar la respuesta y muestra una página Web al usuario.
Este proceso se repite una y otra vez durante una sesión típica de navegación por un sitio Web.
1.4. El rol del servidor Web.
Los primeros servidores Web eran responsables de recibir y procesar requerimientos de usuario desde
navegadores a través de HTTP. El servidor Web gestionaba los requerimientos y enviaba una respuesta de
regreso al navegador Web. Hecho esto, el servidor Web entonces cerraba cualquier conexión entre él y el

5
navegador y liberaba todos los recursos involucrados con el requerimiento. Estos recursos eran fácilmente
liberados cuando el servidor Web finalizaba de procesar el requerimiento.
Este tipo de aplicaciones Web eran consideradas sin estado porque los datos no eran conservados por el
servidor Web entre requerimientos y las conexiones no se reutilizaban. Estas aplicaciones normalmente
involucraban simples página HTML y eran por tanto capaces de gestionar miles de requerimientos por
minuto. La siguiente figura muestra un ejemplo de este simple entorno sin estado.
Figura 5

Hoy en día los servidores Web realizan servicios que van más allá de los servidores Web originales. Además
de servir archivos HTML estáticos, los modernos servidores Web también gestionan requerimientos a páginas
que contienen código que es ejecutado sobre el servidor; el servidor Web ejecuta este código ante el
requerimiento y responde con resultados. Estos servidores Web son también capaces de almacenar datos
entre los requerimientos. Esto significa que las páginas Web pueden conectarse mediante un formulario a
aplicaciones Web que comprenden el estado actual de cada requerimiento individual del usuario. Estos
servidores mantienen una conexión abierta con los navegadores durante un periodo de tiempo anticipando
requerimientos de páginas adicionales por parte del mismo usuario. Este tipo de interacción se ilustra en la
siguiente figura.
Figura 6

1.5. El rol del navegador Web.


Para que un navegador Web pueda mostrar los datos que recibe desde un servidor de una forma
independiente de la plataforma se creó un lenguaje estándar para mostrar contenido. Este lenguaje se concreta
en el lenguaje HTML mediante el uso de etiquetas. El lenguaje HTML fue diseñado para ser capaz de
renderizar información sobre algún sistema operativo sin tener que poner restricciones sobre el tamaño de la
ventana. Esto es así porque las páginas Web se consideran independientes de la plataforma. El HTML fue
diseñado para "fluir", para romper el texto si es necesario ajustarlo a los bordes de la ventana del navegador.
El navegador Web también muestra imágenes y responde a enlaces a otras páginas. Cada página Web
requerida al servidor Web provoca que el navegador Web actualice su contenido para mostrar la nueva
información.
Aunque el rol del navegador Web es simplemente presentar información y recolectar datos de los usuarios,
muchas nuevas tecnologías del lado cliente permiten hoy en día a los navegadores Web ejecutar código como
JavaScript y soportar complementos que aumentan la experiencia del usuario. Tecnologías como AJAX

6
permiten a los navegadores Web realizar refrescos parciales de la página comunicándose con el servidor Web.
Estas tecnologías hacen la experiencia del usuario más dinámica e interactiva.

2. El protocolo HTTP
HTTP (Hypertext Transfer Protocol) es un protocolo de comunicaciones basado en texto que es usado para
solicitar páginas Web a un servidor Web y enviar respuestas de retorno al navegador Web. El protocolo
HTTP es el que gestiona la navegación por páginas web a través de Internet, y por tanto será el utilizado por
las aplicaciones Web.
HTTP es un protocolo orientado a transacciones y sigue el esquema solicitud-respuesta entre un cliente y un
servidor. Al cliente que efectúa la solicitud (habitualmente un navegador web) se lo conoce como "user agent"
(agente del usuario). A la información transmitida se la llama recurso y se la identifica mediante un localizador
uniforme de recursos (URL). Los recursos pueden ser archivos, el resultado de la ejecución de un programa,
una consulta a una base de datos, la traducción automática de un documento, etc.
2.1. Transacciones HTTP.
Una transacción HTTP está formada por un encabezado seguido, opcionalmente, por una línea en blanco y
algún dato. El encabezado especificará cosas como la acción requerida del servidor, o el tipo de dato
retornado, o el código de estado.
El uso de campos de encabezados enviados en las transacciones HTTP le da gran flexibilidad al protocolo.
Estos campos permiten que se envíe información descriptiva en la transacción, permitiendo así la
autenticación, cifrado e identificación de usuario.
Si se reciben líneas de encabezado del cliente, el servidor las coloca en variables de entorno, conocidas como
variable CGI, con el prefijo HTTP_ seguido del nombre del encabezado. Cualquier carácter guion "-" del
nombre del encabezado se convierte a caracteres "_".Ejemplos de estas variables CGI son HTTP_ACCEPT y
HTTP_USER_AGENT:
• La cabecera HTTP_ACCEPT especifica los tipos de contenido (llamados también tipos MIME)de respuesta
que el cliente aceptará, dados los encabezados HTTP. Los elementos de esta lista deben estar separados por
una coma.
• La cabecera HTTP_USER_AGENT especifica el navegador que utiliza el cliente para realizar la solicitud. El
formato general para esta variable es: software/versión biblioteca/versión.
2.1.1. Uso de URLs.
El punto de partida para el inicio de una transacción HTTP es la especificación de una URL (Uniform Resource
Locutor) por parte de la aplicación cliente. Una URL es una cadena de texto que especifica generalmente un
protocolo de red (http o https para solicitudes web), un dominio y la información del recurso solicitado. El
formato general es el siguiente:
protocolo://dominio/informacion_de_recurso
Los protocolos más utilizados son:
• http: para realizar navegaciones por un sitio web. Por ejemplo, para acceder a la página inicial del sitio web
de Oracle usaríamos.
http://www.oracle.es
• https: para realizar navegaciones mediante un protocolo seguro por un sitio web. Por ejemplo, las
entidades bancarias utilizan habitualmente seguridad SSL para acceder a sus servicios. En estos casos
usaríamos:
https://www.entidadbancaria.es/cuentas
• ftp: para acceder al sistema de ficheros del servidor. Algunos sitios web permiten compartir ficheros
hospedados en alguna de sus carpetas. Por ejemplo, para acceder a un sitio de publicación de aplicaciones
Linux podemos usar:
ftp://sunsite.unc.edu/
• mailto: para acceder al servicio de correo electrónico. Por ejemplo, podemos crear un mensaje de correo
electrónico de la siguiente forma.
mailto:[email protected]?subject=asunto&cc=destinatario&body=mensaje
• file: para navegar por el sistema de fichero local. Por ejemplo, si queremos acceder a la carpeta raíz de la
unidad C: usaríamos:
file:///C:/

7
Para los protocolos http y https, el dominio es aquella parte de la URL que identifica el sitio web al que
queremos hacer una solicitud. Existen dos formatos para especificar el dominio:
• Un formato donde se indica un nombre de host o dirección IP y un puerto.
Cada ordenador que comparte algún recurso dentro de una red (lo que se denomina un host) tiene asignado
un número que lo identifica de manera lógica y jerárquica. Este número se denomina dirección IP y está
formado por 4 segmentos. Por ejemplo, la dirección 127.0.0.1 está reservada para identificar el sistema
local. Como alterativa a usar la dirección IP la mayoría de sistemas operativos permiten un nombre de host
más comprensible. Por ejemplo, el nombre localhost es equivalente a la IP 127.0.0.1
Además, dentro de cada host pueden ejecutarse varias aplicaciones servidoras accesibles a través de la red.
Cada host identifica sus aplicaciones servidoras mediante otro número denominado puerto. La mayoría de
aplicaciones servidoras comerciales utilizan un puerto fijo; por ejemplo, el servidor de base de datos de
Oracle utiliza habitualmente el puerto 1521. Por tanto, para especificar un dominio en una URL se utilizará
una sintaxis como:
http://124.23.0.1:1521
• Un formato que especifica un nombre de dominio, como por ejemplo http://www.oracle.es.
Para hacer más amigables las URLs, el protocolo HTTP permite especificar un nombre de dominio en vez
de una IP y puerto. El nombre de dominio queda registrado en un Sistema de Dominio de Nombres
(DNS), donde está asociado con su host y puerto. El protocolo HTTP hace transparente el uso de DNS, y
de esa forma es habitual nombres de dominio para navegar por Internet.
La última parte de una URL especifica el recurso solicitado e información adicional. La información del
recurso consta de las siguientes partes:
• Una ruta, la cual identifica al recurso dentro de la organización física o virtual del sitio web. Por ejemplo,
la ruta "informes/ventas.html" puede hacer referencia a un fichero llamado "ventas.html" ubicado dentro del
directorio "informes". Pero como veremos en unidades posteriores, esta ruta puede estar asociada a una
aplicación y no a un fichero. Si no se especifica la ruta, habitualmente los sitios web definen una ruta o
página por defecto.
• Una cadena de consulta, que especifica datos adicionales. La información de esta cadena está estructurada
normalmente con pares "clave=valor". Se utiliza la cadena de consulta como una técnica sencilla para pasar
datos adicionales con la solicitud de un recurso. Por ejemplo, si queremos invocar la página de búsqueda de
google para buscar por el término "Oracle" utilizaríamos la siguiente URL:
https://www.google.es/?q=Oracle
El comienzo de la cadena de consulta se inicia siempre con el caracter '?'.
2.1.2. Uso de URNs.
Un Nombre de Recurso Uniforme, o URN, es un identificador único para un recurso dentro de la red, y se
puede usar en vez de una URL. Pero a diferencia de una URL, no especifica cómo acceder a él.
Existe un estándar oficial para mantener identificadores únicos para cada recurso. Un ejemplo de recurso
identificado por una URN esISBN:1-930110-59-6.
Actualmente no se suelen utilizar URNs.
2.1.3. Uso de URIs.
Un Identificador de Recursos Uniforme o URI es una cadena de caracteres que identifica los recursos de una
red de forma unívoca. La diferencia respecto a una URL que estas últimas hacen referencia a recursos que, de
forma general, pueden variar en el tiempo.
Normalmente estos recursos especificados en una URI son accesibles en una red o sistema. Las URIs pueden
ser localizador de recursos uniforme (URL), un nombre de recursos uniforme (URN), o ambos (URL+URN).
La sintaxis de una URI es la misma que la de una URL, con la diferencia de que permite añadir a la
información del recurso una especificación de fragmento:
• Un fragmento permite identificar una parte del recurso solicitado. Se suele utilizar con páginas HTML
para que el navegador muestre una parte específica de la página. El comienzo de estaparte se indica
mediante el carácter '#'. Por ejemplo, si en una página llamada "informe.html" definimos un elemento con
el identificador "ventas", podemos utilizar la siguiente URL:
http://dominio/informe.htm#ventas
2.2. Solicitudes HTTP.
Cuando un navegador hace un requerimiento o solicitud se envía un mensaje HTTP al servidor, cuya primera
línea tiene la siguiente estructura:

8
▪ Un nombre de método (GET, HEAD, POST, PUT, DELETE, OPTIONS o TRACE).
▪ La URI local del recurso requerido.
▪ La versión HTTP usada.
Un ejemplo de cabecera de requerimiento es:
GET /informes/saldos/index.html HTTP/1.0
2.2.1. Anatomía de una solicitud HHTP GET.
Una solicitud mediante el método GET añade la ruta del recurso y cualquier parámetro a la URL en la línea de
solicitud.
Para una URL como "http://www.dominio.com/informes/saldos/index.html?nombre1=valor1&nombre2=valor2",
la estructura de llamada puede ser como la siguiente:
GET /informes/saldos/index.html?nombre1=valor1&nombre2=valor2 HTTP/1.1 < línea de solicitud
Host: www.dominio.com < cabeceras …
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.4) Gecko/ <
20030624 Netscape/7.1
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain; <
q=0.8,video/x-mng,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Language: en-us,en;q=0.5 <
Accept-Encoding: gzip,deflate <
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 <
Keep-Alive: 300 <
Connection: keep-alive <
En la URL de ejemplo, nombre1 y nombre2 son los parámetros y, valor1 y valor2 son los valores de dichos
parámetros. Se usa para requerir recursos pasivos (como páginas HTML) o activos (como páginas JSP). La
longitud total de la línea de solicitud no debe exceder de 255 caracteres.
2.2.2. Anatomía de una solicitud HHTP POST.
Las solicitudes con el método POST están diseñadas para ser usadas por navegadores que necesitan realizar
una petición más compleja sobre el servidor. Por ejemplo, si un usuario completa un formulario, la aplicación
puede querer enviar todos los datos introducidos al servidor para que los almacene en una base de datos.
Mediante HTTP POST los datos son enviados al servidor en el cuerpo del mensaje (o payload), y pueden ser
tan largos como se precise.
Para una URL como "http://www.dominio.com/informes/saldos/index.html?nombre1=valor1&nombre2=valor2",
la estructura de llamada puede ser como la siguiente:
POST /informes/saldos/index.html HTTP/1.1 < línea de solicitud
Host: www.dominio.com < cabeceras
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.4) Gecko/ <
20030624 Netscape/7.1
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain; <
q=0.8,video/x-mng,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Language: en-us,en;q=0.5 <
Accept-Encoding: gzip,deflate <
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 <
Keep-Alive: 300 <
Connection: keep-alive <
< línea en blanco
nombre1=valor1&nombre2=valor2 < cuerpo mensaje
2.2.3. Otros métodos de solicitud HTTP.
Hay varios métodos de solicitud definidos en HTPP. La siguiente tabla describe los métodos HTTP más
comunes.
OPTIONS Es usado por aplicaciones clientes para solicitar una lista de comandos soportados. De
este modo podemos comprobar si un servidor permite un determinado comando antes de
ocupar el ancho de banda intentando enviar una solicitud no soportada.

9
GET Obtiene una URL desde el servidor. Una solicitud GET para una URL específica, como
/test.htm, recupera el archivo test.htm. Los datos recuperados usando este comando son
normalmente guardados en caché por el navegador. GET también trabaja con colecciones,
como directorios que contienen colecciones de archivos. Si se solicita un directorio, el
servidor puede ser configurado para retornar un archivo por defecto, como index.html,
que puede representar al directorio.
HEAD Recupera la meta información de un recurso. Esta información es normalmente idéntica a
la meta información enviada en una respuesta de solicitud GET, pero el comando HEAD
nunca retorna el recurso actual. La meta información es guardada en caché.
POST Envía datos para que sean procesados por el servidor Web. Esto es normalmente el
resultado de que el usuario introduzca datos en un formulario y los envíe como parte de su
solicitud.
PUT Permite a los clientes crear directamente un recurso, indicado en la URL, sobre el servidor.
El servidor mira el cuerpo de la solicitud, crea el fichero especificado en la URL, y copia
los datos recibidos en el nuevo fichero. Si el fichero existe y no está bloqueado, el
contenido del fichero será rescrito.
DELETE Se usa para eliminar un recurso del servidor Web. Requiere permisos de escritura sobre el
directorio.
TRACE Se usa para testar o diagnosticar; permite al cliente ver que está siendo recibido al otro
final de la cadena de solicitud. Las respuesta de este método nunca son guardadas en
caché.
CONNECT Reservado para usos con un proxy que puede dinámicamente actuar de pasarela, como el
protocolo Secure Sockets Layer (SSL).
DEBUG No definido en la especificación HTTP/1.1, pero usado para comenzar la depuración
ASP.NET. Este método informa a Visual Studio de los procesos supervisados por el
depurador.
2.3. Respuestas HTTP.
Cuando un servidor Web responde a una petición de un navegador u otro cliente Web, la respuesta consiste
típicamente en una línea de estado, algunas cabeceras de respuesta, una línea en blanco, y un mensaje
opcional. Aquí tenemos un ejemplo mínimo con la línea de estado por defecto:
HTTP/1.1 200 OK < Línea de estado
Set-Cookie: JSESSIONID=0AAB6C8DE415E2E5F307CF334BFCA0C1 < Cookie de sesión
Content-Type: text/html < Cabeceras …
Content-Lenght: 36 <
< Línea en blanco
<html><body>Hello World</body></html> < Cuerpo mensaje
La línea de estado consiste en la versión HTTP, un entero que se interpreta como código de estado, y un
mensaje muy corto que corresponde con el código de estado.Los códigos de estado son tres dígitos
agrupados tal como describe a continuación:
1xx Información: solitud recibida, procesando.
2xx Éxito: la acción fue recibida con éxito, atendida y aceptada.
3xx Comando de redirección: una acción remota debe ser realizada para completar la solicitud.
4xx Error del cliente: la solicitud tiene un error de sintaxis o el servidor no sabe cómo completar la
solicitud.
5xx Error del servidor: el servidor falló al completar una solicitud que parece ser válida.
Además de grupos de códigos de estado, HTTP/1.1 define códigos de estado únicos y sus razones. Una
razón no es nada más que breve descripción del código de estado.
La siguiente tabla muestra los códigos de estado comunes y sus razones.
100 Continue
200 OK
201 Created
300 Multiple Choices

10
301 Moved Permanently
302 Found
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
407 Proxy Authentication Required
408 Request Time-out
413 Request Entity Too Large
500 Internal Server Error
501 Not Implemented
El texto de las razones puede ser modificado sin romper el protocolo. Tal como se verá, para especificar
códigos de estado, en los servlets se usa el método setStatus(código), donde como argumento puede usarse
una de las constantes SC_* predefinidas en la claseHttpServletResponse.
La línea de cabecera Content-Type indica el tipo de recurso que será enviado al navegador Web como parte de
la respuesta. En el ejemplo, Content-Type: text/html, indica que se envía un archivo de texto HTML estático.
Un navegador puede administrar varios tipos de archivos, incluyendo documentos PDF, documentos Word,
animaciones de Flash, etc.; y mostrarlos directamente en el navegador. Para ello debemos transmitir al
navegador el tipo MIME apropiado que describa el contenido del flujo de respuesta. El tipo MIME es una
descripción estandarizada de contenidos, independiente de la plataforma, similar al de las extensiones de
archivos bajo Windows.
La cabecera Content-Length establece la longitud del cuerpo. De esta manera el navegador sabrá hasta dónde
tiene que leer contenido para renderizarlo.
2.4.¿Qué determina el método de solicitud HTTP?
Un navegador realiza una petición HTTP GET en los siguientes casos:
• Cuando se introduce directamente una URL en la barra de direcciones del navegador. Por ejemplo:
http://www.google.es/
• Cuando se pulsa un enlace creado con la etiqueta HTML <a />. Por ejemplo:
<a href=" http://www.google.es">Página de búsqueda</a>
• Cuando se envían datos mediante un formulario que no tiene asignado el atributo method, o lo tiene
asignado al valor GET. Por ejemplo:
<form action="http://www.google.es" method="GET">
Texto a buscar: <input type="text" name="q" />
<input type="submit" value="buscar" />
</form>
Los formularios deben incluir habitualmente un botón de posteo. Cuando se pulsa este botón se realiza una
solicitud a dirección indicada en el atributo action de la etiqueta <form />.
Nota. Los formularios utilizan por defecto el método de solicitud HTTP GET.
Un navegador realiza una petición HTTP POST en los siguientes casos:
• Cuando se envían datos mediante un formulario que tiene asignado el atributo method al valor POST. Por
ejemplo:
<form action="http://www.gestionempleado.es" method="POST">
Nombe empleado: <input type="text" name="nombre" />
<input type="submit" value="enviar" />
</form>
Otros métodos HTTP se aplican cuando se realizan solicitudes mediante código se servidor o código script.
Veremos cómo hacer esto más adelante.

3. Aplicación de servidor web para Java EE


La tecnología Java EE para Web es soportada por varias aplicaciones servidoras de Web como Apache
Tomcat, Glassfish, JBossy otras. En este curso nos centraremos en el desarrollo de aplicaciones web para
Apache Tomcat, aunque las técnicas y conceptos desarrollados son aplicables para cualquier otro servidor.

11
3.1. Instalar y configurar Apache Tomcat.
Es necesario tener instalado un servidor web como «Apache Tomcat» para poder ejecutar los servlets y
paginas JSPde Java. Tomcat fue desarrollado para servir páginas HTML, páginas JSP y Servlets.
3.1.1. Instalación de Apache Tomcat.
Podemos descargar una versión de Apache Tomcat desde la página oficial de Oracle:
http://tomcat.apache.org/index.html
Una vez bajado los archivos de instalación se pueden ejecutar para instalar Tomcat en nuestro sistema.
Los servlets y páginas JSP son clases de Java, así que para procesarlos Tomcat requiere de una máquina virtual
de Java. Para indicarle a Tomcat dónde se ubica el SDK se deben definir dos variables de entorno en el
sistema operativo:
JAVA_HOME = <debe contener la ruta dela carpeta raíz del JDK>
JRE_HOME = <debe contener la ruta dela carpeta raíz del JRE>
Para los sistemas Windows, también es aconsejable indicar en la variable de entorno PAHT la ruta de los
binarios de Tomcat (<ruta_instalación_Tomcat>\bin). Por defecto, en Windows, «Apache Tomcat» se instala
en la carpeta:
C:\Program Files\Apache Software Foundation\Apache Tomcat
Dependiendo de la versión instalada, se arranca Tomcat con el comando "startup.bat" (para Windows) o
"startup.sh" (para Unix). Se detiene el servidor de Tomcat con el comando "shutdown.bat" (para Windows) o
"shutdown.sh" (para Unix). Si no existen estos archivos deberemos usar directamente los ejecutables
disponibles de la subcarpeta \bin.
Tomcat utiliza por defecto el puerto 8080 para sus conexiones y reserva la subcarpeta "webapps/ROOT/"
dentro de su carpeta de instalación como carpeta raíz por defecto para ubicar las aplicaciones o sitios Web.
Para comprobar que el servidor de Tomcat está funcionando podemos escribir la dirección
http://localhost:8080/ en un navegador. Debe mostrarse una página de inicio con información sobre Tomcat.
Figura 7

3.1.2. Instalar Tomcat con NetBeans.


El archivo de instalación de NetBeans también incluye los servidores Glassfish y Tomcat. Sin embargo, las
opciones de instalación por defecto sólo instalan el servidor Glassfish. Para instalar Tomcat debemos ejecutar
el instalador de NetBeans y en su primera pantalla pulsar el botón «Personalizar/Customize» y a continuación
seleccionar la opción «Apache Tomcat» y continuar con la instalación.

12
Figura 8

Si NetBeans no detecta de forma automática una instalación de Tomcat, podemos configurarla a mano. Los
pasos a seguir son los siguientes:
1) En el panel de «Prestaciones/Services» debemos añadir un nuevo servidor al nodo «Servidores».
Haciendo clic con el botón secundario del ratón sobre este nodo aparece la opción de añadir un nuevo
servidor. En el cuadro de diálogo «Añadir un servidor» hay que seleccionar el tipo de servidor, en este caso
«Apache Tomcat».

13
Figura 9

2) Tras pulsar el botón «Siguiente», debemos ahora indicar la localización de la carpeta de la instancia de
Tomcat y las credenciales de un administrador de Tomcat. Se pueden crear nuevas credenciales desde este
asistente, pero para ello es necesario que el usuario actual del sistema operativo tenga permisos para
modificar los ficheros de configuración ubicados en la carpeta de instalación de Tomcat.
Figura 10

3) Tras pulsar el botón «Finalizar» aparecerá desplegado en el nodo «Servidores» el nuevo servidor.
Podemos editar sus propiedades, por ejemplo, para cambiar el puerto de conexión.

14
Figura 11

Si tenemos instalado más de un servidor web podemos seleccionarlo al crear cada nuevo proyecto Web.
3.2. Estructura de carpetas de una aplicación web Java EE.
Para publicar una aplicación Web en Tomcat, podemos ubicar todos sus ficheros en el directorio
"webapps/ROOT/", o bien debemos crear un carpeta específica dentro del directorio "webapps".
Los proyectos Web que admite Tomcat deben corresponderse con una estructura bien definida. Esta
estructura se muestra a continuación:
Figura 12

Podemos ubicar nuestras páginas web, por ejemplo, en la capeta raíz "MisitoWeb1". Si creamos una página con
nombre "index.html", se mostrará automáticamente si en un navegador introducimos la dirección:
http://localhost:8080/MiSitioWeb1
donde localhost indica la conexión al propio ordenador, siendo sustituido normalmente por la dirección URL
del servidor web.
Todas las clases compiladas de Java (incluyendo a servlets y filtros) deben meterse en la carpeta "classes".
Mientras que todas la librerías (archivos con extensión jar) deben meterse en la subcarpeta "lib".
El archivo de configuración "web.xml" siempre debe ir directamente en la carpeta "WEB-INF".
3.3. Archivos WAR.
Los archivos y recursos de una aplicación web pueden ser empaquetados dentro de un archivo JAR, a
diferencia que el archivo debe tener la extensión .war en lugar de .jar. La extensión .war viene de las siglas
«Web ARchive», y significa que el archivo deberá ser tratado de diferente manera que un archivo JAR. El
contenedor de servlets del servidor Web puede instalar el archivo WAR de una aplicación web sin una
intervención manual.
15
Dentro del archivo WAR, se crea un directorio llamado META-INF. Dicho directorio contiene el archivo
MANIFEST.MF, el cual permite declarar dependencias con librerías y clases. Es decir, brinda un testeo en
tiempo de despliegue (deploy-time) para que el contenedor pueda verificar las librerías y clases que se necesitan
para ejecutar la aplicación web correctamente. Esto permite que no sea necesario esperar hasta que un recurso
sea solicitado, para verificar que existan todos los componentes de los que depende la aplicación.
El archivo WAR puede sustituir al directorio raíz de la aplicación web si lo ubicamos directamente dentro de
"webapps". Para crear un archivo WAR de la aplicación MiSitioWeb1:
1) Abriremos una consola y nos desplazaremos a la carpeta webapps.
2) Usaremos la utilidad jar para comprimir la carpeta:
jar –cvf MiSitioWeb1.war MiSitioWeb1
3.4. Cómo crear una aplicación web con NetBeans.
El entorno de desarrollo NetBeans ofrece un conjunto de herramientas comprensible para desarrollar
aplicaciones web aplicando la estructura de las aplicaciones Web Java EE.
El primer paso es crear una nueva aplicación usando el menú «Archivo|Nuevo proyecto». En el cuadro de
diálogo «Nuevo Proyecto» hay que seleccionar la categoría «Java Web» y la plantilla «Aplicación Web».
Figura 13

Tras pulsar el botón «Siguiente» hay que asignar un nombre al proyecto (para este ejemplo "WebAplicacion"), y
la ubicación de mismo ("E:\Test").

16
Figura 14

Tras pulsar «Siguiente», a continuación se puede seleccionar el servidor sobre el que se piensa ejecutar la
aplicación Web (para este ejemplo Apache Tomcat 8), y la versión de Java EE.
Figura 15

Tras pulsar «Siguiente» el asistente permite seleccionar varios marcos de trabajo para desarrollar la aplicación
web. De momento no se seleccionará ninguna.

17
Figura 16

Por último, tras pulsar «Finalizar», se añadirá el proyecto Web al panel de «Proyectos».
Figura 17

Los proyectos Web de NetBeans mantienen una estructura lógica que se corresponde con la estructura física
de las aplicaciones Web Java EE. A continuación se comenta la correspondencia:
Nodos del proyecto Ubicación en la aplicación Web Java EE
Web Pages Se corresponde con la carpeta raíz del proyecto Web. En este nodo se deben crear
las páginas HTML y JSP, así como cualquier otro tipo de recurso estático,
pudiéndose organizar estos en subcarpetas.
META-INF Este nodo genera la carpeta "META-INF" de la aplicación. Contiene el archivo de
contexto de la aplicación: "context.xml"
WEB-INF Este nodo genera la carpeta "WEB-INF" de la aplicación. Contiene el archivo de
configuración de la aplicación: "web.xml"
En este nodo también se pueden añadir páginas JSP y otros recursos privados de
la aplicación, tales librerías de etiquetas.
Source packages En este nodo se crean los archivos fuente de los servlets, filtros y clases de Java
organizados por paquetes. Se corresponde con la carpeta "WEB-INF/classes" de la
aplicación.

18
Libraries En este nodo se añaden las librerías y ficheros JAR que queremos referenciar en la
aplicación. Se corresponde con la carpeta "WEB-INF/lib" de la aplicación.
Configuration Files Este nodo de utilidad agrupa los diversos ficheros de configuración de la
aplicación. Es conveniente no editarlos directamente desde este nodo.
Como se puede ver, el nuevo proyecto añade una página HTML, llamada index.html, a la aplicación; esta
página es el recurso por defecto de la aplicación. Pero por defecto no se añade el fichero web.xml. Para añadir
este fichero hay que agregar un nuevo fichero para acceder al cuadro de diálogo «Nuevo fichero». En la
categoría «Web» se puede encontrar la plantilla «Standard Deployment Descriptor (web.xml)». Tras aceptar se
agrega el fichero al proyecto, tal como se muestra a continuación:
Figura 18

Se puede probar el nuevo proyecto Web de la misma manera que se prueba una aplicación de escritorio,
ejecutando el proyecto con el icono «Ejecutar proyecto», pulsando la tecla F6 o pulsando el menú
«Ejecutar|Ejecutar proyecto». NetBeans también permite seleccionar el tipo de navegador desde el cual se
harán las solicitudes a la aplicación web.
Como resultado de ejecutar la aplicación se arrancará el servidor Web (en este caso Tomcat) si es que no
estaba en ejecución, y se desplegará la aplicación sobre el servidor. También se abrirá el navegador web
seleccionado con la página por defecto de nuestra aplicación.
Figura 19

Podemos modificar el proyecto sin tener que volver a ejecutarlo. NetBeans se encargará de trasladar cualquier
cambio al servidor Web en cuanto modifiquemos un fichero y guardemos los cambios. Por ejemplo,
manteniendo abierto el navegador, podemos cambiar el título de la página Web y el formato de su contenido:

19
Figura 20

Ahora basta con refrescar el navegador web para que se reflejen los cambios:
Figura 21

3.5. El archivo descriptor «web.xml».


El archivo web.xml, denominado oficialmente archivo descriptor (Deployment Descriptor o DD), es un
archivo de configuración usado por el contenedor de servlets para obtener información sobre nuestra
aplicación web.
3.5.1. Contenido del archivo descriptor.
El archivo descriptor que se crea cuando se añade a un proyecto web en NetBeans contiene lo siguiente:
Figura 22

El archivo descriptor utiliza el formato XML, expresando toda su información mediante etiquetas. Usa una
etiqueta raíz llamada <web-app>, dentro de la cual se expresa cualquier información dentro de una etiqueta
significativa. Inicialmente sólo se establece el tiempo de invalidación de las sesiones en 30 minutos.
Dentro del nodo raíz <web-app>se pueden declarar las siguientes secciones:

20
Sección Etiqueta xml Significado
Servlet Declarations <servlet> Especifica las propiedades de los servlets.
Servlet Mappings <servltet-mapping> Especifica la URL para mapear un servlet.
Application Lifecycle <listener> Especifica clases observadoras de eventos de la
Listener classes aplicación.
ServletContext Init <context-param> Especifica parámetros para la aplicación web.
Parameters
Error Pages <error-page> Especifica páginas para re direccionar automáticamente
condiciones de error.
Session Configuration <session-config> Especifica el tiempo de expiración de la sesión.
Security Constraints <security-constraint> Especifica condiciones de seguridad para la aplicación
web.
Tag libraries <taglib> Especifica los identificadores de las librerías usadas por
las páginas JSP.
Welcome File list <welcome-file-list> Especifica los archivos de saludo para la aplicación web.
Filter Definitions <filter> Especifican filtros.
Filter Mapping
MIME Type Mappings <mime-mapping> Especifica los tipos MIME para extensiones de archivos.
JNDI names <ejb-ref> Especifica los nombres JNDI de los EJBs.
JSP Config <jsp-config> Especifica configuraciones para grupos de páginas JSP.
En posteriores unidades se ampliarán las explicaciones sobre estas secciones. NetBeans ofrece un editor
especializado para el archivo descriptor. Este editor ofrece varias pestañas donde se agrupan las
configuraciones por categorías. En la categoría «General» podemos escribir información sobre nuestra
aplicación y cambiar el tiempo de invalidación de sesión, entre otras configuraciones generales.
Figura 23

3.5.2. Especificación de páginas por defecto (o welcome files ).


A través del archivo web.xml se puede especificar la lista de recursos por defecto de una aplicación web. Los
recursos por defecto se tienen en cuenta cuando en la URL de un navegador se especifica un final de carpeta.
Por ejemplo:
http://localhost:8084/WebAplicacion/
http://localhost:8084/WebAplicacion/informes/
El servidor web utiliza la lista de recursos por defecto para buscar el primer recurso que se corresponda con
uno de los indicados en la lista. Es decir, el servidor devolverá el primer archivo de la lista que exista en el
directorio especificado en la URL.
Se deben tener en cuenta los siguientes puntos al especificar la lista de recursos por defecto:
• No se puede utilizar una barra delante del nombre del archivo incluido en la lista.

21
• Todos los archivos de la lista definidos con (<welcome-file>) deben estar dentro de un único
elemento<welcome-file-list>, es decir, tienen que estar dentro de la misma lista.
El siguiente trozo de código es un ejemplo típico de páginas por defecto:
<web-app ...>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Con el editor de NetBeans para el fichero web.xml podemos editar esta lista en la categoría «Páginas»:
Figura 24

4. Fundamentos de HTML5
HTML ha sido el lenguaje de publicación de páginas web desde 1992. En este capítulo veremos los
fundamentos de HTML5 (la última especificación de este lenguaje), cómo se estructuran las páginas HTML y
algunas de las características básicas que podemos añadir a una página HTML.
4.1. La estructura de una página HTML.
HTML es el acrónimo de Hyper Text Markup Language. Es un lenguaje estático que determina la estructura y
significado semántico de una página web. Se usa HTML para crear contenido y metadatos que el navegador
usa para renderizar y mostrar información. El contenido HTML incluye texto, imágenes, audio, vídeo,
formularios, listas, tablas y muchos otros elementos. Una página HTML puede también contener híper-
enlaces, los cuales conectan con otras páginas y otros sitios web.
Toda página HTML tiene la misma estructura básica:
• Una declaración DOCTYPEque define la versión HTML que usa la página.
• Una sección<html>que contiene los siguientes elementos:
- Una cabecera (elemento <header>) que contiene información sobre la página para los navegadores.
Esto puede incluir el lenguaje principal (Inglés, Chino, Francés, y demás), el juego de caracteres, hojas de
estilo y ficheros scriptasociados, información del autor, y palabras clave para los motores de búsqueda.
- Un cuerpo (elemento <body>) que contiene todo lo visible de la página.
Un ejemplo básico del código de una página HTML 5 es el siguiente:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Página con la estructura mínima</title>
</head>
<body>
Contenido de la página
</body>
</html>
4.2. Etiquetas, elementos, atributos y contenido.
La cabeza y cuerpo de una página web usan ambos elementos HTML para definir su estructura y contenido.
Por ejemplo, un elemento paragraph, que representa un párrafo de texto en una página, consiste de:
22
• Una etiqueta de apertura, <p>, para denotar que inicio del párrafo.
• El contenido de texto.
• Una etiqueta de cierre, </p>, para denotar el final del párrafo.
A veces se habla indistintamente de etiquetas o elementos, como si fuesen lo mismo, pero esto es incorrecto.
Un elemento consiste de etiquetas y contenido.
Se anidan elementos dentro de otros para obtener mayor información semántica acerca del contenido. Si no
es obvio por el contexto, elementos anidados ayudan a mantener un registro de cuáles son los padres y cuáles
son elementos hijos.
4.2.1. El cuerpo de un documento sencillo.
El siguiente código HTML muestra un contenido sencillo del cuerpo de una página:
<body>
<h1 class="blue">Una introducción a elementos, etiquetas y contenido</h1>
<p>
Los <strong>elementos</strong> consisten de <strong>contenido</strong> encapsulado entre
una etiqueta de <em>comienzo</em> y una etiqueta de <em>cierre</em>.
</p>
<hr />
<p>
Ciertos elementos, como el elemento de la línea horizontal, no necesitan contenido;
consisten de un elemento auto-cerrado. Se conocen como elementos vacíos.
</p>
</body>
Cada elemento HTML le dice al navegador algo acerca de lo que se encuentra entre la etiqueta de comienzo y
la de cierre. Por ejemplo, los elementos strong yem representan un contenido de "fuerte importancia" y de
"énfasis", por lo cual los navegadores los renderizan respectivamente en negritas y en itálicas. Los elementos
h1 representas una cabecera de nivel más alto en nuestro documento, las cuales son renderizadas por los
navegadores con un texto grande y en negritas.
Los atributos proporcionan información adicional, representacional o semántica, sobre el contenido de los
elementos. Aparecen dentro de la etiqueta de comienzo de un elemento y constan de un nombre y de un
valor. El nombre debería estar con minúsculas. Muchos valores de atributos son predefinidos y deberían estar
contenidos entre comillas simples o dobles. En el ejemplo previo, la etiqueta h1 contiene el atributo class
asignado al valor blue.
4.2.2. Estructura del documento en HTML5.
HTML5 incluye nuevos elementos que permiten marcar nuestro contenido y presentar una estructura mejor
para los documentos, comparado con las versiones previas.
Una de las tareas comunes de una página es identificar áreas del documento: la barra de navegación, la
cabecera, el pie de página, y demás.
En HTML4 se usaba el atributo id para diferenciar áreas.Por ejemplo:
<ul id="navigation"> ... </ul>
<div id="footer> ... </div>
Aunque este método es válido, no concede un significado semántico a las diversas áreas. HTML5
proporciona una estructura semántica más rica para documentos, incluyendo los siguientes elementos:
Elemento Descripción
<section> Identifica piezas de contenido de una página. Por ejemplo, los ingredientes y los métodos de
un recipiente mostrado en una página pueden tener dos secciones separadas.
<header> Identifica el contenido de la cabecera de una página. Por ejemplo, el sitio web de una
compañía puede incluir un logotipo, nombre y lema en la cabecera.
<footer> Identifica el contenido del pie de página. Por ejemplo, enlaces al sitio web, instrucciones de
privacidad, o términos y condiciones suelen ser incluidos en el pie de página.
<nav> Identifica el contenido para una sección de navegación principal de la página. Los
programadores suelen usar este elemento para implementar un menú de navegación a través
del sitio web.
<article> Identifica contenido independiente que tendría sentido fuera del contexto de la página
actual. Por ejemplo, un blog, una receta, o una entrada de catálogo.
<aside> Identifica contenido relacionado en un <article>que no es parte de su flujo. Por ejemplo,
podemos usar <aside> para identificar puntos o contenido adicional.
23
El siguiente ejemplo de marcado muestra una forma de marcar un documento HTML5 usando los nuevos
elementos estructurales:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Mis mejores recetas</title>
</head>
<body>
<nav>
<a href="/">Inicio</a>
</nav>
<header>
<h1>Mis mejores recetas</h1>
<p>Mis recetas favoritas</p>
</header>
<article>
<h1>Tostada de alubias</h1>
<section>
<h1>Ingredientes</h1>
<ul>
<li>Alubias</li>
<li>Pan</li>
</ul>
</section>
<section>
<h1>Método</h1>
<ol>
<li>Tostar el pan</li>
<li>Cocer las alubias</li>
<li>Poner las alubias sobre el pan</li>
</ol>
</section>
</article>
<footer>
<small>Última actualización <time datetime="2012-08-12">12 de Agosto de 2012</time></small>
</footer>
</body>
</html>
Es importante tener en cuenta que no hay un orden preescrito en el cual se usan estos elementos ni
proporcionan ningún formato de visualización por defecto. Por ejemplo, puedes decidir incluir tus enlaces de
navegación en el encabezado, pie de página o barra lateral. Similarmente se puede dividir la página dentro de
secciones semánticas (como Desayunos, Almuerzos y Cenas) e incluir elementos <article>dentro de estas
secciones. Lo importante es entender el propósito de cada elemento nuevo y utilizarlo adecuadamente.
Nota:Nótese que en el ejemplo previo hay tres elementos <h1> en la página, en vez de uno solo como
era esperado. En versiones previas, esto debería ser una semántica incorrecta, pero los elementos
<section> y<article> definidos en HTML5 pueden reiniciar la numeración de títulos.El algoritmo de
esquematización de HTML5 que define esto y el uso de <hgroup> se verá a continuación.
4.3. Mostrando texto en HTML.
La mayoría de páginas web requieren de contenido de texto e imágenes. HTML define muchos elementos
que permiten estructurar el contenido y aportan alguna semántica de contexto.
En la siguiente tabla se resumen las etiquetas relacionadas con la renderización de contenido de texto en
títulos y párrafos:
Elemento Descripción
<p> Identifica un párrafo de texto.
<br /> Provoca un salto de línea dentro de un párrafo.

24
<h1> a <h6> Identifican seis niveles de texto de título. Se usa <h1> para identificar el título de toda
la página, <h2> para identificar el título de cada sección de la página, <h3> para
identificar sub-secciones, y así hasta <h6>.
<hgroup> Agrupa contenido debería ser tratado como un título simple. Este elemento puede
contener etiquetas de título <h1> a <h6>.
<hgroup>
<h1>Mis recetas</h1>
<h2>Bueno para comer, fácil para hacer</h2>
</hgroup>
Otros elementos que dan un significado semántico al contenido de texto son:
Elemento Descripción
<time> Permite definir un valor de fecha, duración o periodo no ambiguo, legible tanto por
humanos como por la máquina. El atributo datetime contiene la representación
estándar ISO del contenido del elemento.
<time datetime="2012-08-08">Hoy</time>
<time datetime="2012-08-08T09:00:00-0500">9am hoy en New York</time>
<time>4h</time>
<time>2012</time>
<mark> Identifica que el contenido debe ser tratado como texto marcado o resaltado para
propósitos de referencia.
<p>Este texto debería ser <mark>marcado para su uso futuro</mark>
en vez de ser <em>enfatizado</em>.</p>
<small> Identifica que el contenido debe ser tratado como texto de comentarios, como la letra
pequeña o atribuciones de autor.
<p>Come tus tostada durante cinco minutos. <small>
O hasta que no puedas más.</small></p>
Es importante usar las etiquetas de párrafo y título para identificar secciones, sub-secciones y el contexto de
un texto en la página web. Los títulos y etiquetas hacen que el contenido sea más comprensible para lectores e
indexadores, y más fácil de leer sobre la pantalla.
Nota. Cuando se escribe marcado HTML, cualquier secuencia de espacios en blanco, tabuladores y
retornos de carro dentro del texto es tratado como un único espacio en blanco. La única excepción es
cuando la secuencia de texto está dentro de un elemento <pre>, que le dice a los navegadores que
rendericen todos los espacios.
HTML también define cuatro elementos para denotar un cambio de énfasis en el texto:
Elemento Descripción
<strong> Indica que el texto es más importante que el que le circunda. Los navegadores lo
renderizan normalmente poniéndolo en negritas.
<em> Identifica texto que necesita ser tensionado. Los navegadores normalmente lo
renderizan con itálicas.
<b> Identifican texto que debe ser renderizado en negritas.
<i> Identifican texto que debe ser renderizado en itálicas.
Se pueden combinar y anidar los elementos <strong>, <em>, <b>, y<i> para indicar diferentes tipos de
énfasis. Los navegadores pueden renderizar el texto enfatizado de muchas formas diferentes.
Nota: Los elementos <b>y<i>de HTML4 son simples instrucciones para mostrar el texto, en lugar de
especificar un significado semántico. En HTML5, es mejor usar <strong>y <em> en vez de <b>y<i>.
4.4. Listas.
Las listas organizan conjuntos de información de una forma clara y con un formato comprensible. HTML
define tres tipos de listas:
• Listas no ordenadas. Conjuntos de grupos de elementos sin ningún orden en particular. Cada elemento es
mostrado con una viñeta al inicio.
<p>Lista no ordenada de editores HTML</p>
<ul>
<li>Notepad</li>

25
<li>Textmate</li>
<li>NetBeans</li>
</ul>
• Listas con orden. Conjuntos de grupos de elementos en un orden particular. Cada elemento es numerado
secuencialmente.
<p>Lista ordenada de cómo escribir una página web</p>
<ol>
<li>Crear un nuevo fichero de texto</li>
<li>Añadir algún HTML</li>
<li>Guardar el fichero en un sitio web</li>
</ol>
• Listas de definición. Conjuntos de grupos de pares nombre-valor, como un término y su definición. El
nombre aparece resaltado y la definición se sangra en un párrafo posterior.
<p>Lista de definición con gente importante de Internet</p>
<dl>
<dt>Sir Tim Berners Lee</dt>
<dd>Inventó HTML y escribión WorldWideWeb</dd>
<dt>Linus Torvalds</dt>
<dd>Creó las bases de Linux</dd>
<dt>Charles Herzfeld</dt>
<dd>Autorizó la creación de ARPANET, el predecesor de Internet</dd>
</dl>
Estos tres tipos usan una etiqueta para definir el comienzo y cierre de la lista: <ul>, <ol>, y<dl>,
respectivamente.
Cada entrada individual es identificada con la etiqueta <li> para listas sin orden y con orden, mientras que en
listas de definición se usan las etiquetas <dt>para el nombre (o término) y <dd>para su valor (o definición).
Es posible anidar listas.
4.5. Mostrando imágenes.
Se usa la etiqueta <img>para insertar una imagen en una página web. Esta etiqueta no requiere una etiqueta
de cierre y no contiene nada. Su definición queda determinada por los siguientes atributos:
Atributo Descripción
src Especifica la URL que determina la localización del fichero de imagen a mostrar.
alt Identifica un texto alternativo que se mostrará si el navegador no puede renderizar la imagen
por alguna razón. Este texto normalmente describirá el contenido de la imagen.
title Identifica algún texto que será usado por herramientas de ayuda cuando el cursor pase por
encima de la imagen.
longdesc Identifica otra página web que describe la imagen en más detalle.
height Asignan las dimensiones en píxeles de la caja dentro de la página web que contendrá la
width imagen; si las dimensiones son diferentes de las de la imagen, los navegadores
redimensionarán la imagen.
De todos estos atributos sólo el atributo src es obligatorio.
Uno de los tipos de imágenes más comunes que incluyen las páginas web es un logotipo de algún tipo, como
en el siguiente código:
<body>
<p>
<img src="logo.jpg" alt="El logotipo de mi sitio" height="100" width="100" />
</p>
<h1>¡Bienvenidos a mi sitio!</h1>
</body>
HTML5 incluye el elemento<figure>, que normalmente se usa para identificar una imagen, vídeo o listado de
código y su descripción asociada u otros elementos. Si el contenido necesita un título, podemos anidar el
elemento <figcaption> dentro del elemento <figure>.
<figure>
<img src="plateofbeans.jpg" alt="Un plato de tostadas de alubias" />
<figcaption>Un maravilloso platod de tostadas en cinco minútos</figcaption>
</figure>

26
4.6. Mostrando enlaces a documentos.
La razón principal para la invención de HTML fue vincular documentos. La etiqueta <a>, también conocida
como la etiqueta de anclaje, permite identificar una sección de contenido de le página que enlaza otro recurso
en la web. Normalmente el destino de este enlace de hipertexto es otra página web, pero puede ser
igualmente un archivo de texto, una imagen, un fichero, un correo o un servicio web. Cuando veamos nuestra
página en el navegador, podemos hacer clic sobre el contenido enmarcado de las etiquetas de anclaje para
descargar el documento vinculado en el navegador.
Las etiquetas de anclaje tienen los siguientes atributos no globales:
Atributo Descripción
href Identifica la página web o el recurso enlazado.
target Identifica dónde el navegador debe mostrar el recurso vinculado; valores válidos son
_blank,_parent, _self, y_top.
rel Identifica qué tipo de enlace está siendo creado.
hreflang Identifica el lenguaje del recurso vinculado.
type Identifica el tipo MIME del recurso vinculado.
Un uso común de los enlaces de hipertexto es crear menús de navegación sobre las páginas para que el
usuario pueda visitar otras páginas del sitio.
<body>
<ul>
<li><a href="default.html" alt="Página inicial">Inicio</a></li>
<li><a href="about.html" alt="Acerca de este sitio Web">Acerca de</a></li>
<li><a href="essays.html" alt="Una lista de mis trabajos">Trabajos</a></li>
</ul>
</body>
El atributo hrefes laparte más importante de vincular un recurso en línea a otro. Podemos usar varios tipos
diferentes de valores:
• Una URL de la misma carpeta (por ejemplo: about.html).
• Una URL relativa a la carpeta actual (por ejemplo: ../about.html).
• Una URL absoluta a la carpeta raíz del servidor (por ejemplo: /<ruta de contexto>/pages/about.html).
• Una URL ubicada en otro servidor (por ejemplo: http://www.microsoft.com/default.html).
• Un identificador de fragmento o un nombre de identificador precedido por una almohadilla (por ejemplo:
#section2).
• Una combinación de URL y un identificador de fragmento (por ejemplo: about.html#section2).
En el siguiente ejemplo se muestra cómo definir un fragmento y crear un enlace al mismo:
<body>
<a href="#pie">ir al final de la página</a>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<p id="pie">Final de página</p>
4.7. Formularios de datos.
Muchos sitios web requieren que el usuario introduzca información, como un nombre de usuario, contraseña
o dirección. Los textos e imágenes definen el contenido que el usuario puede leer, pero un formulario
proporciona un mecanismo de interacción entre el usuario y el sitio web, dándole la posibilidad de enviar
datos al servidor de nuestro sitio web para que los recoja y los procese.
Se usa el elemento <form> para identificar un área de nuestra página que actuará como un formulario de
petición de datos. Este elemento tiene los siguientes atributos:
Atributo Descripción
action Identifica la URL de la página que recibirá los datos posteados.
method Define cómo son enviados los datos al servidor. Valores válidos son: GETyPOST.
accept-charset Define la codificación de caracteres de los datos enviados desde el formulario.
27
enctype Identifica el tipo de formato (tipo MIME) usado cuando se descodifican los datos del
formularios cuando el método es POST.
target Identifica dónde mostrará el navegador la página indicada en el atributo action;
valores válidos son _blank, _parent, _self, y_top.
Se deben añadir controles y elementos de texto dentro del contenido del formulario para determinar su
diseño.
4.7.1. Controles de formulario.
Las etiquetas form normalmente incluyen información para el usuario en forma de texto y etiquetas de entrada
que definen cosas como botones y cuadros de texto. Un uso típico de la etiqueta <form> puede ser el
siguiente:
Código de «index.html» Aspecto en el navegador

<!DOCTYPE html>
<html> Figura 25
<head>
<title>Primer ejemplo web</title>
</head>
<body>
<form method="POST" action="cliente.jsp">
Introduzca el ID de cliente:
<br />
<input type="text" name="Id">
<input type="hidden" name="status" value="id"/>
<br />
<input type="submit" value="Obtén cliente">
</form>
</body>
Este ejemplo solicita al usuario un ID de cliente, muestra un cuadro de edición para introducir el id, y
también muestra un botón para iniciar el envío de datos al servidor Web. A modo de ejemplo se ha incluido
también un campo de tipo hidden, el cual no tiene una visualización en el navegador, ya que se usa para
almacenar algún valor fijo que queramos enviar al servidor.
El atributo method del formulario indica el método HTTP (POST) usado cuando se envía la solicitud al
servidor. El atributo action es una URL relativa al recurso de servidor al cual se envían los datos posteados
desde el formulario.
Nota. Si en un formulario no se especifica el atributo action, el posteo se realiza sobre una solicitud de la
propia página.
Un elemento <input> representa el control HTML principal para entrada de datos y tiene muchas formas
según el atributo type, tal como se muestra en la siguiente tabla. El atributo value permite asignar un valor por
defecto para controles basados en números o texto, y el atributo name permite asignar un nombre que
quedará asociado al dato del control.
<input /> Resultado
text Una caja de texto para editar una sola línea.
password Una caja de texto de una línea para introducir contraseñas.
hidden Un campo para almacenar un texto no visible para el usuario.
checkbox Una caja de verificación. Proporciona una elección si/no o verdadero/falso. Se usa el
atributo selected para indicar si la caja está marcada por defecto.
radio Un botón de radio. Se usa el atributo name para agrupar varios botones de radio. El
formulario sólo permitirá seleccionar un botón de radio dentro de un grupo.
reset Un botón de reseteo. Al pulsar este botón el usuario pondrá todos los controles del
formulario con sus valores por defecto.
submit Un botón de envío. Al pulsar sobre este botón el formulario enviará los datos a la
página indicada en el atributo action.
image Una imagen para usar como un botón de envío. Se usa el atributo src para identificar la
imagen que se usará.
button Un botón de comando. Este botón no tiene ninguna funcionalidad por defecto, pero
28
puede ser usado para ejecutar código script cuando se pulse.
file Un control de fichero. Proporciona un modo de enviar un fichero al servidor.
url Una caja para editar una URL.
Hay otros tres elementos HTML que podemos usar en un formulario:
<textarea> Genera un cuadro de texto plano editable en varias líneas. Se usan los atributos
rowsycols para asignar su tamaño.
<select> Define una lista fija de elementos o una lista desplegable. Se usa el atributo multiple para
indicar si el usuario puede seleccionar más de un elemento. Se usan elementos anidados
<option> para definir los elementos de la lista. Podemos usar el atributo selected de los
elementos <option> para indicar que están seleccionados por defecto, y su atributo value
para asociar un valor con cada elemento. Si se establece el atributo value, será su valor lo
que se envíe, sino se enviará el contenido de texto del elemento <option>.
<button> Define un botón de comando. Se usa el atributo type para indicar si es un botón submit,
reset, obutton. El valor por defecto essubmit.
Se debería usar el elemento <button>en vez de sus equivalentes <input> si necesitamos
que el contenido mostrado del botón sea más complejo que una simple pieza de texto o
una imagen simple.
Es importante resaltar que para que se postee el dato de un control se deben cumplir dos condiciones:
• El control debe estar anidado dentro de un elemento <form/>.
• El control debe tener asignado su atributo name a un valor. Se permite que más de un control tenga
asignado el mismo valor en name.
4.7.2. Elementos de distribución del formulario.
Podemos usar las etiquetas <p>y<div> para aplicar un diseño básico a un formulario. HTML también define
dos etiquetas adicionales que pueden ayudar a mejorar la presentación de un formulario:
• <fieldset>, el cual identifica un grupo de controles dentro del formulario. El navegador refleja esto
dibujando una caja alrededor del contenido con una etiqueta de título. Este título se asigna mediante el
elemento anidado <legend>, el cual debe ser el primer hijo del elemento <fieldset>.
• <label>, el cual define una etiqueta de texto no editable asociada a un control del formulario. Se logra así
enmarcar ambos, el texto y el control, o bien enmarcar el texto y asignando su atributo for al atributo id del
control.
Podemos usar un formulario para una gran variedad de tipos de entradas del usuario. El siguiente ejemplo
muestra cómo aplicar estos elementos para el ejemplo previo.
Código de «index.html» Aspecto en el navegador

<!DOCTYPE html>
<html> Figura 26
<head>
<title>Primer ejemplo web</title>
</head>
<body>
<form method="POST" action="cliente.jsp">
<fieldset>
<legend>Solicitud de cliente</legend>
<label for="Id">ID de cliente:</label>
<input type="text" name="Id">
<input type="hidden" name="status" value="id"/>
</fieldset>
<br />
<input type="submit" value="Obtén cliente">
</form>
</body>
4.7.3. Métodos de posteo de datos de formulario.
Hay dos método HTTP que podemos usar para postear los datos del formulario de regreso al servidor Web:
GET y POST. Cuando se usa GET, los datos son añadidos a la URL como parte de la cadena de consulta. La

29
cadena de consulta es una colección de pares clave-valor, separados por un caracter ampersand (&). El
siguiente ejemplo muestra una solicitud GET:
GET /cliente.jsp?Id=123&status=id HTTP/1.1
Host: localhost:8084
En este ejemplo, se realiza una solicitud GET al servidor Web sobre una página llamada cliente.jsp, situada en
el directorio raíz del sitio Web (esto se indica por la barra inclinada). La cadena de consulta contiene los datos
del formulario después del signo de interrogación (?).
Cuando se usa el método GET para enviar datos al servidor, la URL completa y la cadena de consulta pueden
ser enviados y modificados en la barra de dirección del navegador Web. Ténganse en mente que,
dependiendo del escenario, esto puede ser una desventaja o provocar riesgos de seguridad. Seguramente no
querremos que cualquiera manipule estos datos en la cadena de consulta y que potencialmente vean cosas que
no deberían ver o que corrompan los datos. También podremos querer que los usuarios no puedan señalar
páginas que incluyan información reservada enviada al servidor. Otra desventaja es que la cadena de consulta
está limitada en tamaño por el navegador Web y el servidor Web.
El método POST es el preferido para enviar datos de regreso al servidor como parte de una solicitud HTTP.
Cuando se usa el comando POST, los datos se envían dentro del cuerpo del mensaje de la solicitud como
sigue:
POST /cliente.jsp HTTP/1.1
Host: localhost:8084

Id=123&status=id
Usando el comando POST se elimina la restricción del tamaño de los datos. (Como prueba, se pasaron más de
10 megabytes de datos a un servidor Web para ver si los aceptaba. Esto funcionó, pero el enviar tantos datos
a través de Internet puede causar otros problemas, principalmente relacionados con el ancho de banda, con
errores de tiempo de espera y problemas de rendimiento.)
Además, el método POST impide a los usuarios manipular la solicitud en la barra de dirección de los
navegadores. Esto es así porque los datos se ocultan en el cuerpo del mensaje. Por lo tanto, en muchos
escenarios, el método POST es el más deseable para enviar datos a un servidor Web.
4.7.4. Cómo recuperar los datos posteados desde un formulario.
Los datos posteados por un formulario sólo pueden ser recuperados desde un recurso dinámico, tal como un
servlet o una página JSP. No se pueden postear datos a otra página HTML, puesto que son recursos estáticos
y no permiten incluir código del lado servidor.
Como ejemplo, incluiremos la página cliente.jsp en el proyecto Web creado previamente con NetBeans.
Figura 27

Para incluir el fichero cliente.jsp en el proyecto hay que incluir un fichero de tipo JSP de la categoría «Web». Si
ejecutamos la aplicación se mostrará primero la página index.html. Al pulsar el botón «Obtén cliente» se
postearán los datos del formulario a la página cliente.jsp. La página cliente.jsp incluye en su cuerpo texto
estático y una expresión ${param.Id}. Este tipo de expresiones es conocido como expresiones EL y forman
parte del código del lado cliente que es interpretado por el contenedor de servlets. Se utiliza esta expresión
para recuperar el valor de un parámetro de formulario que coincida en su atributo name con Id.

30
Figura 28

En las siguientes unidades se analizará con más extensión el funcionamiento de los servlets, páginas JSP y
expresiones EL para procesar datos posteados y otro tipo de datos recibidos desde una solicitud.
4.8. Organizar una página usando tablas.
Hemos visto que HTML5 define etiquetas semánticas como <header>, <footer> y otras para organizar el
contenido de las páginas. Pero estás etiquetas no establecen una distribución física de su contenido.
La manera más sencilla de distribuir el contenido de una página es usar tablas. El siguiente código muestra
una plantilla de uso de una tabla:
Cuerpo de la página Aspecto en el navegador

<body>
<table border="1">
<thead>
<tr>
<th>Cabecera 1</th> Figura 29
<th>Cabecera 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Celda 1 1</td>
<td>Celda 1 2</td>
</tr>
<tr>
<td>Celda 2 1</td>
<td>Celda 2 2</td>
</tr>
</tbody>
</table>
</body>
El significado de cada etiqueta es el siguiente:
Etiqueta Significado
table Es la etiqueta contenedora de todas las demás.
thead Es opcional. Define el bloque de filas de cabecera de la tabla.
tbody Es opcional. Define el bloque de filas de contenido de la tabla
tr Define una fila de la tabla.
td Define una celda normal dentro de una fila.
th Define una celda que resalta su contenido.
Las tablas más sencillas de HTML se pueden definen con sólo tres etiquetas: <table>, <tr> y <td>.
A continuación se muestra cómo utilizar una tabla para diseñar una página con dos paneles y un elemento
<footer />. Los paneles se definirán con elementos <div /> y se ubicarán dentro de las celdas de la tabla:
Cuerpo de la página Aspecto en el navegador

31
<body>
<table cellspacing="3">
<tr>
<td>
<div style="padding:1em; background-color: lightgray">
<a href="inicio.html">Inicio</a><br />
<a href="informes.html">Informes</a><br />
Figura 30
<a href="ventas.html">Ventas</a><br />
</div>
</td>
<td>
<div>
Contenido de la página inicial
</div>
</td>
</tr>
<tr>
<td colspan="2">
<div style="background-color: lightgray">
Pie de página
</div>
</td>
</tr>
</table>
</body>
Un bloque <div /> define un panel rectangular dentro de la página, y permite aislar parte del contenido de la
página. Para este ejemplo se han creado dos paneles: uno para mostrar un menú de enlaces y otro para
mostrar el contenido principal de la página. Mediante el atributo style se ha aplicado un color de fondo
(background-color) y un margen interno (padding).
La tabla se ha definido con dos filas. En la primera fila se usan dos celdas, pero en la segunda fila se usa una
única celda que ocupa todos el ancho de dos celdas (esto se indica con colspan="2").
Téngase en cuenta que dentro de una celda podemos anidar otras tablas completas. Esta característica permite
organizar el contenido de cada panel y sección con un diseño más complejo.
4.9. Validar datos de formulario usando atributos HTML5
Cuando creamos un formulario y lo añadimos al sitio web, los datos recolectados pueden variar en términos
de calidad y precisión. De hecho, los formularios están abiertos para recoger cualquier tipo de dato del
usuario, tanto si es válido como si no. Incluso una entrada de usuario puede ser malévola, diseñada para
sondear cómo reacciona la aplicación a datos inesperados.
La validación es el proceso de comprobar los datos para detectar errores obvios. La validación ocurre en dos
lugares:
• En el cliente. Cuando el usuario completa el formulario, algún dato puede ser validado por marcado
HTML y código JavaScript.
• En el servidor. Cuando el formulario envía los datos, el recurso que los recibe en el servidor debe
verificarlos antes de procesarlos.
Es habitual validar en el lado cliente el formato y rango de valores de los datos, mientras que en el servidor se
validan los datos para comprobar que cumplen con las reglas del negocio.
4.9.1. Asegurarse de que los campos no están vacíos.
Para asegurarse que el usuario introduce datos en campos obligatorios, hay que añadir el atributo required a
un elemento <input>. Si el usuario intenta enviar los datos del formulario antes de proporcionar un valor, su
petición será ignorada. El siguiente ejemplo muestra cómo usar el atributo required:
<input type="text" name="Id" required />
Cómo el navegador informa al usuario de que un campo obligatorio está vació es cosa del navegador. Por
ejemplo, Internet Explorer resalta los campos vacíos con un borde rojo y muestra un mensaje.

32
Figura 31

4.9.2. Validación de campos numéricos.


Con HTML5 podemos controlar un rango de valores para los campos numéricos. El siguiente código es un
ejemplo:
<input name="porcentaje" type="number" min="0" max="100" />
El usuario puede escribir cualquier cosa en el campo, pero sólo serán válidos los números entre 0 y 100. Si el
usuario deja el campo en blanco no se aplicará la validación. Por eso es importante marcar el campo con el
atributo required para obligar al usuario a que escriba un valor.
Nota:El campo de tipo number también soporta otros atributos como step, el cual especifica un
incremento válido para los valores numéricos escritos en el campo. Estos atributos no están actualmente
implementados en Internet Explorer 10.
4.9.3. Validación de campos de texto.
HTML5 proporciona campos de entrada como tel (teléfono) y email (correo electrónico) que esperan datos
con un formato específico, pero también podemos aplicar patrones personalizados mediante expresiones
regulares. Por ejemplo, si necesitamos un código de pedido con el patrón 99XXX, donde 9 es cualquier dígito y
X es cualquier letra en mayúsculas, podemos usar el atributo pattern para especificar la expresión regular que
valide el campo. Podemos proporcionar retroalimentación al usuario sobre el formato esperado de los datos
usando el atributo title.
<input id="orderRef" name="orderReference" type="text"
requiredpattern="[0-9]{2}[A-Z]{3}" title="2 dígitos y 3 letras en mayúsculas" />
Para que se aplique la validación del patrón debemos también aplicar la validación required.
4.9.4. Estilos en los campos para proporcionar retroalimentación.
Para ayudar al usuario a identificar los campos que deben completarse, deberíamos indicar visualmente si
estos campos son requeridos o no. Un modo de hacer esto es marcando cada campo con un asterisco. En
este ejemplo, se usa un asterisco en rojo usando CSS.
<form id="registerForm" method="post" action="registration.aspx">
<div id="campoNombre" class="campo">
<label for="nombre">Nombre: </label>
<input id="nombre" name="nombre" required="required" placeholder="Tu nombre de pila" />
<span style="color:red">*</span>
</div>
...
</form>
Un modo mejor para indicar si es un campo es requerido es usando CSS para dar estilo al campo de acuerdo
con el atributo required. Para usar validación cambiando dinámicamente el color del borde, creando
retroalimentación instantánea en cualquier navegador, podemos asignar el borde de campos con datos no
válidos en rojo, mientras los campos con datos válidos estarán con borde verde. La siguiente técnica trabaja
detectando el estado de validación del campo usando las seudo-clases valid einvalid, y proporcionando una
regla para el color del borde en cada estado.
<style type="text/css">
input {
border: solid 1px;
}
input:invalid {
border-color: red;
}
input:valid {

33
border-color: black;
}
</style>

5. Fundamentos de CSS
HTML define la estructura y contexto de una página web, mientras CSS define cómo deben aparecer en el
navegador. En este capítulo veremos los fundamentos de CSS, como crear algunos estilos básicos, y cómo
asociar estos estilos a elementos de una página HTML.
5.1. Fundamentos de la sintaxis de CSS.
CSS es el acrónimo de Cascading Style Sheets. CSS proporciona un modo estándar de definir cómo un
navegador debe mostrar el contenido de una página web. CSS permite asociar reglas de presentación a
fragmentos de HTML basadas en selectores que referencian elementos HTML por nombre, id o clase.
También permite varias cómo se presenta una página de acuerdo a factores del dispositivo sobre el cual se
muestra, desde un monitor grande a un Smartphone, y aún sobre un lector de audio o una impresora.
Cada regla CSS tiene la misma estructura básica:
selector {
propiedad1:valor;
propiedad2:valor;
...
propiedadN:valor;
}
Este ejemplo muestra las cuatro partes de cada regla CSS:
• Unselectordefine el elemento o conjunto de elementos destinos del estilo. El estilo especificado por la
regla CSS es aplicado a todos los elementos de la página web que casen con el selector. Un selector CSS
puede especificar el tipo del elemento como div para seleccionar todos los elementos <div>, o el nombre de
los atributos de una instancia específica de un elemento. Podemos también seleccionar varios tipos de
elementos como p,div para seleccionar todos los elementos <p> y <div>. Se puede usar * para seleccionar
todos los elementos. También son posibles otras expresiones de selectores.
• Un par de llaves que encierran la reglas para los elementos del selector. Una regla define como renderizar
el elemento seleccionado; contiene pares de propiedad-valor separados por punto y coma.
• Una propiedad define el aspecto visual de elemento seleccionado a cambiar.
• Un valor especifica el estilo a aplicar sobre una propiedad. Los valores pueden ser dependientes de la
propiedad. Pueden ser nombres de colores, valores de tamaño en porcentajes, píxeles, o puntos, o el
nombre de una fuente de letra, etc.
Podemos también añadir comentarios a nuestras hojas de estilo usando delimitadores/* */. El navegador
ignorará los comentarios. Se pueden usar fuera o dentro de una regla CSS.
Todas las reglas CSS tienen la misma sintaxis básica. Más allá de eso, la primera clave para CSS es saber las
propiedades que se aplican a conjuntos de elementos. Este ejemplo muestra el uso de algunas propiedades de
texto específicas:
/* Objetivo: títulos de nivel 1; los renderia con texto rosa grande usando la fuente Segoe UI */
h1 {
font-size: 42px;
color: pink;
font-family: 'Segoe UI';
}
/* Objetivo: texto enfatizado; lo renderiza como itálico y con fondo amarillo */
em {
background-color: yellow; /* El amarillo de un buen color de resalte */
font-style: italic;
}
En este ejemplo, las dos reglas se trasladan como sigue:
• Cada elemento <h1> debe tener texto de tamaño 42px, rosa y fuente Segoe UI.
• Cada elemento <em> debe tener un color de fondo amarillo y el texto en cursivas.
Cuando se escribe CSS, note que cualquier secuencia de espacios en blanco es tratado como un único espacio
en blanco.

34
5.2. Añadiendo estilos a una página HTML.
HTML permite asociar las reglas CSS a nuestras páginas web de tres maneras:
• Escribiendo las reglas específicas para un elemento en su atributo style.
<p style="font-family : Candara; fontsize:12px; "> ... </p>
• Escribiendo un conjunto de reglas específicas para una página dentro del elemento <head> usando
etiquetas <style>.
<style type="text/css">
p{
font-family : Candara; font-size: 12px;
}
</style>
• Escribiendo las reglas dentro de ficheros (con extensión .css) de hojas de estilo, y después
referenciándolos en el marcado de la página usando la etiqueta <link>. El lugar más habitual para poner
etiquetas <link> es dentro del elemento <head>.
<link rel="stylesheet" type="text/css" href="mystyles.css" media="screen">
El elemento <link> tiene cuatro atributos CSS relevantes:
Atributo Significado
href Especifica una URL que identifica la localización del fichero de hoja de estilo.
rel Indica el tipo de documento que referencia el elemento <link>; se asigna a
stylesheetcuando enlazamos hojas de estilo.
media Indica el tipo de dispositivo destino para la hoja de estilo; posibles valores son: speech para
sintetizadores de voz, print para impresoras, handheld para dispositivos móviles, screen
para pantallas de ordenador, y all (por defecto) para todos los propósitos.
type Indica el tipo MIME del documento que está siendo referenciado; el tipo correcto para la
hojas de estilo es text/css, el cual es el valor por defecto para este atributo.
Los atributos type y media tienen la misma función para los elementos <style>así como para sus homónimos
del elemento <link>.
Nótese que los estilos se aplican en orden, de arriba abajo, tal como la página es analizada y procesada, Los
últimos estilos sobreescriben los primeros cuando se aplican al mismo elementos. Por ejemplo, si definimos
estilos en un elemento <head>, y entonces añadimos un <link> que referencia una hoja de estilo con estilos
diferentes para los mismos elementos, entonces los estilos de la hoja de estilos reescriben los definidos
directamente en el elemento <head>. Sin embargo, si definimos los estilos en un elemento <head> después
de un <link> que referencia una hoja de estilos, entonces los estilo definidos directamente reescribirán los de
la hoja de estilos. Si definimos estilos en línea como parte de un elemento, estos siempre reescriben cualquier
otro definido previamente.
5.3. Cómo trabajan los selectores CSS.
Los selectores CSS especifican el contenido sobre el que se aplicará el estilo usando el conjunto de reglas
asociadas. Comprender cómo trabajan los selectores CSS es la clave para definir hojas de estilos reutilizables y
extensibles.
La especificación CSS proporciona muchos modos diferentes de seleccionar elementos o conjuntos de
elementos en una página web sobre los que aplicar reglas de presentación.
El siguiente listado resume los selectores básicos y el conjunto de elementos que identifican.
• El selector de elemento, identifica el grupo de elementos de la página con ese nombre. Por ejemplo, h2 { }
retorna el conjunto de todos los títulos de nivel 2 de la página.
• El selector de clase, precedido por un punto, retorna el conjunto de elementos de la página que tienen
asignado el atributo class al especificado. Por ejemplo, .myClass { } retorna el conjunto de elementos que
tienen asignado el atributo class a "myClass".
• El selector de id, precedido por una almohadilla, retorna el conjunto de elementos de la página que tienen
asignados el atributo id al especificado. Por ejemplo, #thisId { } retorna el conjunto de elementos cuyo
atributo id está asignado a"thisId".
El selector de clase puede retornar un conjunto de varios tipos de elementos HTML (por ejemplo, <p>,
<section> y <h3>) si tienen asignado el mismo valor en el atributo class. Lo mismo ocurre para el selector de
id, aunque este selector debería retornar un único elementos ya que el atributo id en una página debería ser
único en cada elemento (aunque no es obligatorio).

35
Hojas de estilo se escriben a menudo con los selectores menos específicos primero y los selectores más
específicos por último.
Los selectores de elemento son los menos específico, seguidos por los selectores de clase y los selectores de
id, y por último las combinaciones de los tres.
h2 { /* selector de elemento */
font-size: 24px;
}
.red { /* selector de clase */
color: red;
}
#alert { /* selector de id */
background-color: red;
color: white;
}
Podemos combinar los selectores usando concatenación. En el siguiente ejemplo, las dos reglas combinan
selectores para identificar un conjunto de elementos más específicos.
El selector h2.blue retorna los elementos <h2> con su atributo class al valor "blue", y h2#toc retorna los
elementos <h2> con su atributo id al valor "toc".
h2.blue {
color: blue;
}
h2#toc {
font-weight: bold;
}
Nótese que estos dos conjuntos pueden tener elementos en común, en cuyo caso las propiedades CSS y los
valores de ambas reglas se aplicarán.
La siguiente tabla muestra ejemplo de varias formas de concatenar selectores y el conjunto de elementos que
el navegador retorna.
Selector CSS Significado
h2.blue Retorna cualquier elemento <h2>de la clase "blue"
h2#blue Retorna cualquier elemento <h2>con el id "blue"
section, h2 Retorna cualquier elemento <h2>y cualquier elemento <section>
section h2 Retorna cualquier elemento <h2>anidado en un elemento <section> a cualquier nivel
section > h2 Retorna cualquier elemento <h2>anidado inmediatamente bajo el elemento <section>
section + h2 Retorna cualquier elemento <h2>inmediatamente después del elemento <section> de
forma que comparten el mismo elemento padre
section ~ h2 Retorna cualquier elemento<h2>después del elemento <section> de forma que
comparten el mismo elemento padre
5.3.1. El selector comodín.
El selector comodín * retorna todos los elementos del documento. Este selector se usa raramente por sí
mismo, pero podemos usarlo en combinación con otros elementos. Por ejemplo, la siguiente regla retorna
todos los elementos anidados en un elemento <aside>, y les aplica transparencia.
aside * { opacity : 0.6; }
5.3.2. Selector de atributos.
Podemos afinar más cualquier selector inspeccionando la declaración de atributos y sus valores dentro del
elemento. El atributo seleccionado se indica entre corchetes, añadido al selector y puede tener cualquiera de
las siguientes formas:
Selector CSS Significado
input[type] Retorna cualquier elemento<input>que usa el atributo typesin valor
input[type="text"] Retorna cualquier elemento<input>que usa el atributo type asignado al valor
"text"
input[foo~="red"] Retorna cualquier elemento<input>con el atributo foo, que contiene una lista
de valores separados por espacios, uno de los cuales es "red"
input[type^="sub"] Retorna cualquier elemento<input>con el atributotypecuyo valor comienza por
"sub"
input[type$="mit"] Retorna cualquier elemento<input>cuyo atributo typefinaliza con "mit"
36
input[type*="ubmi"]Retorna cualquier elemento<input>con el atributo typeconteniendo "ubmi".
input[foo|="en"] Retorna cualquier elemento<input>con el atributo foo cuyo valor es "en" o
comienza con "en"
También podemos combinar selectores de atributos por concatenación. Por ejemplo, para retornar todos los
checkbox marcados por defecto, se usa el siguiente selector:
input[type="checkbox"][selected] { }
5.4. El modelo de cajas de CSS.
Para determinar el diseño de una página HTML, los navegadores tratan cada elemento de la página como un
conjunto de cajas anidadas. El modelo de cajas CSS permite especificar el tamaño de cada caja, y así modificar
el diseño de cada elemento de la página.
El modelo de cajas pone el contenido dentro de cuatro cajas: contenido, relleno, borde y margen.
Figura 32

En la caja central está el contenido, con texto e imágenes. Se usan las propiedades height ywidth para asignar
el alto y ancho de la caja en píxeles.
Alrededor de la caja de contenido está el relleno. Se usa la propiedad padding para asignar el ancho de la caja
de relleno.
Alrededor de la caja de relleno está el borde, que también actúa como una línea visible alrededor del
contenido y relleno. Se usa la propiedad border para asignar el ancho, color y estilo de borde.
Alrededor de la caja de borde está el margen. Se usa la propiedadmargin para asignar el ancho del margen.
El siguiente ejemplo muestra cómo usar el modelo de cajas CSS para dibujar un borde alrededor de un texto
de título, y asignar un margen alrededor del borde para no interferir con otros elementos de la página.
h2.highlight {
height : 100px;
width : 500px;
padding : 10px;
border : 2px dotted blue;
margin : 25px 0 25px 0; /* Podría también escribirse: 25px 0 */
}
Las propiedades margin ypadding son ambas propiedades de formato corto. CSS define actualmente
propiedades individuales para el top, right, bottom, y left de cada caja. Podemos escribir la definición del
relleno de la siguiente manera:
padding-top: 10px;
padding-right : 10px;
padding-bottom : 10px;
padding-left : 10px;
La propiedad border es también de formato corto para el width, style y color. En el ejemplo previo, el borde es
asignado a 2px de ancho, línea de puntos y color azul. Se puede poner en la forma completa:
border-width: 2px;
border-style: dotted;
border-color: blue;
Usando las propiedades border-width, border-style, yborder-color se asume que queremos asignar los valores a
los cuatro laterales de la caja. Si ese no es el caso o sólo queremos asignar uno de los lados, podemos usar las
propiedades border-left-style, border-left-width, border-left-color,border-right-style, border-right-widthy así otras.
Por ejemplo:
p.example {
padding: 10px;

37
border-bottom: solid 1px black;
border-left-style: dotted;
border-left-width: 2px;
}
Las propiedades border-top, border-right, border-bottom, y border-left también tienen su formato corto, como
border, pero para el width, style, y color de los respectivos lados de la caja de borde.
Más allá de las propiedades principales del modelo de caja, CSS define varias otras propiedades para controlar
cómo se debe ver el contenido en el flujo de los elementos de la página. Específicamente:
Propiedad CSS Significado
visibility Permite dejar en blanco los elementos seleccionados, pero dejando el espacio
vacío que ocupan en la página.
display Permite asignar cómo mostrar los elementos seleccionados en la página. Esto
incluye ocultar los elementos seleccionados, o quitarlos completamente de la
página.
position Permite asignar el método de posicionamiento de los elementos seleccionados.
Hay cuatro posibles valores: static (por defecto), fixed, absolute, yrelative.
float Permite hacer que los elementos seleccionados floten sobre el resto de contenido
respecto al borde derecho o izquierdo.
overflow Permite determinar qué ocurre cuando el contenido de un elemento es demasiado
grande para la caja que lo contiene.
box-sizing Permite asignar cómo el ancho y alto se aplican sobre una caja del modelo. Si se
asigna a content-box (por defecto), trabajan como se ha descrito previamente. Si se
asigna a border-box, el ancho y alto se aplican a la totalidad del contenido, relleno,
borde y margen en conjunto.
5.5. Estilos de fondo en CSS.
Muchos sitios web usan una imagen de fondo, color o patrón para proporcionar más colorido y carácter a las
páginas. CSS permite asignar un fondo a cualquier elemento de nivel de bloque usando las siguientes
propiedades:
Propiedad CSS Significado
background-image Permite especificar la URL de una imagen que se usará de fondo. Se puede
usar una URL absoluta o relativa. Si se usa una ruta relativa debe serlo
respecto a la localización de la hoja de estilo o la página web que define el
estilo.
background-image:url('../images/pattern.jpg');
background-size Permite asignar el alto y ancho de la imagen de fondo. Se usan valores
específicos en píxeles o en porcentajes.
background-size: 40px 60px; /* 40px ancho, 60px alto */
background-color Permite asignar el color de fondo de un elemento. Se puede especificar un
color como un valor RGB (rojo-verde-azul) o como una de las 147
constantes predefinidas.
background-color : green;
background-color : #00FF00;
background-color : rgb(0, 255, 0);
background-position Permite asignar la posición de la imagen de fondo bajo el elemento. Esta
propiedad tiene dos valores: el primero para el eje x y el segundo para el eje
y. Se asignan ambos valores como valores absolutos (top, left, bottom, right,
center), porcentajes o píxeles.
/* Imagen adosada a la esquina superior izquierda */
background-position : left top;
/* Imagen adosada a la esquina inferior derecha */
background-position : 100% 100%;
/* Imagen a 8 px de la izquierda y 8 px de arriba */
background-position : 8px 8px;
background-origin Permite asignar a qué caja del modelo de cajas la posición del fondo
debería ser relativa. Posibles valores son content-box (por defecto),
38
paddingbox, y border-box.
background-origin : border-box;
background-repeat Permite asignar cómo debe repetirse una imagen de fondo si es más
pequeña que el elemento seleccionado. Posibles valores son repeat (por
defecto), repeat-x, repeat-y y no-repeat.
background-repeat : repeat-x; /* Repite imagen horizontalmente */
background-repeat : no-repeat; /* No repite la imagen */
background-attachment Permite asignar si la imagen de fondo se desplaza hacia arriba y hacia abajo
o permanece fija en su lugar. Posibles valores son scroll (por defecto) y
fixed.
background-position : fixed;
CSS también proporciona una forma corta en la propiedadbackground, que permite asignar todos los valores
descritos. Por ejemplo:
article { background : transparent repeat-x url('fluffycat.jpg'); }
/* O bien: */
article {
background-color : transparent;
background-repeat : repeat-x;
background-image : url('fluffycat.jpg');
}
5.6. Aplicando herencia en hojas de estilo y HTML.
Como sugiere le frase Hojas de Estilo en Cascada, los elementos de una página puede ser afectados por varias
transformaciones en cascada dependiendo de la relación entre los elemento y las hojas de estilo asociadas con
la página. Para crear hojas de estilo con éxito, es importante comprender dos conceptos: herencia entre
elementos HTML y cómo aplicar varias reglas CSS en cascada a los elementos HTML.
5.6.1. Herencia HTML.
En una página web, los elementos HTML heredan algunas propiedades de sus elementos contenedores a
menos que se especifique otra cosa. Esto es de gran importancia; sin la herencia, tendríamos que replicar las
mismas reglas para cada elemento simple de la página.
Consideremos el escenario en el cual queremos que todos los textos de la página usen la fuente Candar.
Podemos asignar el elemento <body> de la página a esta fuente, y la herencia aplicará este tipo de texto a cada
elemento anidado dentro del cuerpo a menos que otra regla la sustituya.
body {
font-family: Candara;
}
Si no existiese la herencia, deberíamos asignar esta propiedad a cada elemento que contenga texto. Se podría
acabar escribiendo muchos estilos repetidos, tal como se muestra en el siguiente ejemplo, lo cual es muy
difícil de mantener.
h1, h2, h3, h4, h5, h6 {
font-family: Candara;
}
p{
font-family: Candara;
}
...
Nota:No se heredan todas las propiedades CSS del contenedor en su hijos, porque no tendría sentido.
Por ejemplo, si asignamos una imagen de fondo a un elemento <article>, seguramente no querremos que
sus secciones y párrafos hijos muestren la misma imagen de fondo.
5.6.2. Reglas en cascada.
Un único elemento en una página HTML puede casar con más de un selector de una hoja de estilo y puede
estar sujeto a varias reglas diferentes de estilo. El orden en el cual se aplican estas reglas podría provocar que
el elemento se renderice de formas diferentes. El mecanismo de cascada es la forma en la cual las reglas de
estilo se derivan y se aplican cuando varias reglas que entran en conflicto se aplican sobre varios elementos; se
asegura de que todos los navegadores muestren el elemento de la misma forma.
Hay tres factores que los navegadores deben tener en consideración cuando aplican reglas de estilo:

39
1. Importancia. Podemos asegurar la importancia de una propiedad aplicada a un conjunto de elementos
añadiendo el operador de importancia (!important).
h2 { font-weight : bold !important; }
2. Especificidad. Las reglas de estilo con menos especificidad se aplican primer, otras reglas menos
específicas a continuación, y así hasta aplicar las reglas más específicas.
3. Orden de origen. Si existen reglas de estilo para selectores de igual especificidad, se aplican en el orden
en que se definen en la hoja de estilo.
5.7. Organizar una página usando paneles y estilos.
Hemos visto cómo organizar una página usando tablas. El uso de tablas fuerza un diseño muy rígido en la
página ya que cada celda ocupa una posición determinada dentro de la tabla.
Podemos utilizar también el modelo de cajas y los estilos de posicionamiento para ubicar paneles en
posiciones fijas o flotantes de la página.
Cuerpo de la página Aspecto en el navegador

<head>
<style type="text/css">
.menu {
padding: 1em;
background-color: lightgray;
position: absolute;
left: 0ex; width: 9ex; top: 0ex; bottom: 2em
}
.contenido {
border: 3px solid lightgray;
Figura 33
position: absolute;
left: 11em; right: 0em; top: 0em; bottom: 2em
}
.pie {
padding: 4px;
background-color: lightgray;
position: absolute;
height: 1em; right: 0ex; left: 0ex; bottom: 0ex;
}
</style>
</head>
<body>
<div class="menu">
<a href="inicio.html">Inicio</a><br />
<a href="informes.html">Informes</a><br />
<a href="ventas.html">Ventas</a><br />
</div>
<div class="contenido">
Contenido de la página inicial
</div>
<footer class="pie">
Pie de página
</footer>
</body>
En el bloque de estilos se han definido estilos para tres clases, las cuales se aplican a los paneles del cuerpo.
El posicionamiento absoluto (position: absolute) permite flotar un elemento en una posición absoluta
respecto a los borde la página. Para establecer la distancia entre los borde de cada estilo se han usado
unidades de medida em (tamaño del tipo de letra actual) y ex (la altura de la letra x minúsculas).Otras
unidades de mediada son cm (centímetros),mm (milímetros), in (pulgadas o 2,54 cm), pc (picas o 1/6 in), pt
(puntos o 1/72 in), px (pixeles o 1/96 in), ch (el ancho del carácter cero), rem (tamaño de letra el tipo por
defecto).

40
6. Fundamentos de JavaScript
HTML y CSS proporcionan la estructura, semántica e información de presentación para una página web. Sin
embargo, estas tecnologías no describen cómo el usuario interacciona con la página usando un navegador.
Para implementar esta funcionalidad, todos los navegadores modernos incluyen un motor de JavaScript que
soporta el uso de código script en las páginas. También implementan el Document Object Model (DOM), un
estándar W3C que define cómo un navegador debe reflejar una página en memoria para permitir al motor de
script acceder y alterar el contenido de la página.
6.1. Ejecución de código en el lado cliente usando scripts.
HTML permite definir el diseño de nuestras páginas web, pero aparte del elemento <form>no proporciona
nada más para interaccionar con el usuario. Además, el diseño definido usando marcado HTML tiende a ser
bastante estático. Podemos añadir comportamientos dinámicos a una página escribiendo código script. El
código script es un conjunto de instrucciones que se ejecutan por lotes mediante interpretación; esto quiere
decir que no requiere de una compilación previa.
Las páginas HTML permiten embeber código script donde se pueden escribir instrucciones con el lenguaje
JavaScript. Hay varios modos de incluir instrucciones JavaScript en nuestras páginas web, y todas involucran
el elemento <script>:
• Escribir el JavaScript sobre la página como contenido del elemento <script>.
<script type="text/javascript">
alert('Soy una línea de JavaScript');
</script>
• Guardar el JavaScript en un fichero aparte y referenciarlo usando el atributo src del elemento <script>.
<script type="text/javascript" src="alertame.js"></script>
• Referenciar un fichero JavaScript ubicado en otro sitio web.
<script type="text/javascript" src="http://ajax.contoso.com/ajax/jQuery/jquery-1.7.2.js">
</script>
El elemento <script> tiene tres atributos:
Atributo Significado
type Identifica el lenguaje script usado; el valor por defecto estext/javascript.
src Identifica un fichero script para descargar; no hay que usarlo si escribimos código script en
el contenido del elemento.
charset Identifica la codificación de caracteres (por ejemplo, utf-8, Shift-JIS) del fichero script
externo; si no estamos usando el atributo src, no se asigna este atributo.
Siempre hay que especificar ambas etiquetas de <script>, el comienzo y el final, aunque estemos vinculando
un fichero script externo y no tengamos contenido en el elemento.
Es normal para una aplicación web dividir la funcionalidad JavaScript en varios scripts. Además, muchas
aplicaciones web usan ficheros JavaScript externos. El orden en el cual se añaden enlaces a ficheros JavaScript
es importante, y para asegurarnos de que están en el ámbito debemos añadir enlaces a scripts que definen
objetos y funciones antes de los scripts que usan esos objetos y funciones.
Navegadores antiguos no siempre soportan JavaScript, y algunos usuarios pueden deshabilitarlo en los
navegadores modernos. En estos casos, algunas de la características de nuestras páginas web que usen
JavaScript no funcionarán correctamente. Podemos alertar a los usuarios de esto usando el elemento
<noscript>. Este elemento indica a los navegadores que muestren un mensaje, avisando al usuario de que la
página no opera correctamente a menos que habilitemos JavaScript.
<body>
<noscript>Esta página usa JavaScript. Por favor, habilítelo en su navegador</noscript>
...
Resto de la página
...
<script src="MyScripts.js"></script>
</body>
Consejo: En general, es una buena práctica añadir enlaces a scripts como los últimos elementos anidados
dentro del elemento <body>.

41
6.2. Sintaxis de JavaScript.
JavaScript tiene una sintaxis sencilla para escribir instrucciones, declarar variables y añadir comentarios.
Aunque es un lenguaje diferente de Java comparten una sintaxis similar en algunos aspectos. Como Java es un
lenguaje sensible a mayúsculas y minúsculas, pero la mayor diferencia con Java es su forma de tratar con
variables y objetos.
6.2.1. Instrucciones.
Una instrucción es una simple línea de JavaScript. Representa una operación para ser ejecutada. Por ejemplo,
la declaración de una variable, la asignación de un valor, o la llamada a una función.
El siguiente fragmento de código muestra algunos ejemplos de instrucciones:
var unaVariable = 3;
contador = contador + 1;
HazAlgo();
Todas las instrucciones de JavaScript deberían ser escritas en una única línea y terminar con un punto y coma.
La excepción a esta regla es que podemos romper un string muy grande en varias líneas (por legibilidad)
usando la barra diagonal. Por ejemplo:
document.write("Una increible realidad \
es lo grande que es el mundo");
Nota:El finalizador de punto y coma actualmente es opcional. Sin embargo, si no terminamos la
instrucción con punto y coma, JavaScript intenta discernir si debería estar puesto, algunas veces con
resultados inesperados.
Podemos combinar instrucciones en bloques, delimitados entre llaves. Esta sintaxis es usada por funciones,
bloques if, while o for.
Los comentarios en JavaScript son similares a los de Java. JavaScript soporta dos estilos diferentes de
comentar: comentarios de varias líneas que empiezan con /* y finalizan con */, y comentarios de una sola
línea que empiezan con // y finalizan con la línea.
6.2.2. Variables, tipos de datos y operadores.
Hay tres formas de declarar una variable en JavaScript:
1. Dando un nombre y un valor (es la forma menos recomendable).
saludo = "Hola";
2. Declarándola sin dar un valor usando la palabra clave var. Hasta que la variable es asignada a un valor,
JavaScript retornará su valor como indefinido.
var misterio;
3. Combinando ambas formas (que es el estilo más recomendado).
var codigo = "Spang";
A diferencia de Java y otros lenguajes habituales de programación, no podemos especificar el tipo de una
variable en JavaScript. Se declara una variable con la palabra clave var, y entonces JavaScript intenta discernir
el tipo de la variable. JavaScript reconoce tres tipos simples:
1. String (string): Cualquier secuencia de caracteres encerrados entre comillas dobles. Se utilizan caracteres
de escape con la barra diagonal para los caracteres como \" (doble comilla), \' (comilla simple), \; (punto y
coa), \\ (barra diagonal), \& (ampersand). También se usa una barra diagonal para partir un string en varias
líneas.
var simple = "Juan Pérez y José";
var escape = "\"Juan Pérez \& José\"";
var muyLargo = "Este texto es demasiado \
largo para una sola línea.";
2. Número (number): Cualquier número entero o decimal. Si se encapsula un número entre comillas dobles
será tratado como un string.
var answer = 42;
var actuallyAString = "42"; // es tratado como un string
3. Booleano (boolean): Un valor lógico true ofalse.
var canYouReadThis = true;
JavaScript también convierte datos entre tipos, lo cual puede llevar a confusión si no somos cuidadosos. Por
ejemplo, el valor numérico 0 se avalúa a false en expresiones booleanas, así que es importante usar el
operador correcto cuando comparamos los valores de variables.

42
Recordemos que si declaramos una variable sin asignarle valor, la variable queda indeterminada (y retornará el
valor simbólico undefined). Podemos también declarar una variable y asignarle el valor null:
var variableConValorNull = null;
Asignar una variable a null indica que su valor no existe, en vez de que no tiene asignado un valor. Es
importante notar la diferencia.
Podemos determinar el tipo actual de un dato en una variable usando el operador typeof:
var data = 99;
...
if (typeof data == "number") {
// el dato es numérico
}
JavaScript utiliza los mismos operadores habituales que Java y otros lenguajes de programación. Sin embargo,
hay una serie de cuestiones a tener en cuenta con respecto a los operadores JavaScript y cómo convierten
valores entre tipos cuando el intérprete JavaScript ejecuta las expresiones.
• Si añadimos un número a un string, el resultado es un string.
x = 10 + 10; // x es asignado al número 20;
y= "10" + 10; // y es asignado al string "1010";
z = "Ten" + 10; // x es asignado al string "Ten10";
• 0, "" (el string vacío), undefinedynull son evaluados a false en expresiones booleanas. Siempre se usa ===
cuando comparamos cualquiera de estos valores.
var zero = 0;
var emptyString = "";
var falseVar = false;
zero == falseVar; // retorna true;
zero === falseVar; // retorna false;
emptyString == False; // retorna true;
emptyString === False; // retorna false;
6.2.3. Funciones.
La definición de funciones explícitas en JavaScript tiene siempre la misma sintaxis:
function unNombre ( argumento1, argumento2, ..., argumentoN ) {
instrucción1;
instrucción2;
...
instrucciónN;
}
Hay cuatro partes en la definición de una función:
• La palabra clave function indica que comienza la declaración de la función.
• El nombre de la función. Se usa para invocar la función, y es sensible a mayúsculas y minúsculas.
• Una lista de variables separadas por comas, llamadas parámetros, a través de los cuales podemos pasar
valores al interior de la función. Aunque la función no tenga parámetros se deben dejar los paréntesis que
delimitan la lista de parámetros.
• Una serie de instrucciones encapsuladas entre llaves.
Podemos usar el nombre del parámetro dentro de la función como la variable que contiene el valor pasado
como argumento. Pero los argumentos también están disponibles mediante un array llamadoarguments. Se
puede acceder al primer argumento usando la expresión arguments[0], al segundo argumento usando la
expresión arguments[1], etc. Este mecanismo nos da un modo de definir métodos que pueden tener un
número indeterminado de parámetros. Podemos saber cuántos argumentos son pasados consultando el valor
de arguments.length.
Una función que calcula un resultado puede usar la instrucción return para pasarlo como valor de retorno de
la llamada a la función. ¿Qué ocurre cuando el valor es retornado dependiendo de cómo la función es
llamada? Por ejemplo, si queremos calcular el precio a pagar en un hotel, podemos usar la siguiente función e
invocarla:
function CalcularPreciol(numeroDeNoches, precioPorNoche, extras) {
return (numeroDeNoches * precioPorNoche) + extras;
}
...
// en otro sitio del script invocamos la función

43
var TotalCantidad = CalcularPrecio(10, 100, 50);
6.3. Navegación usando JavaScript.
Para navegar a través de las páginas web habitualmente se utilizan hiperenlaces, mediante el elemento HTML
<a />, y formularios, pulsando un botón de tipo submit dentro de un formulario.
Es posible emular estos dos tipos de navegación mediante código JavaScript. La técnica más sencilla es
modificando la página actual de la ventana del navegador. Esto se hace asignando la propiedad location del
objeto window. Por ejemplo:
window.location = "http://www.google.es";
Provoca una redirección automática a la página web de Google. También se puede utilizar:
window.location.href = "http://www.google.es";
Otra forma es utilizando el método navigate() del objeto window, como en el siguiente ejemplo:
window.navigate("otraPagina.html");
Pero el método navigate() no es reconocido por todos los navegadores.
Con estas dos técnicas es posible especificar tanto URLs absolutas como relativas al mismo sitio web.
También podemos emular la pulsación de un botón de formulario mediante código JavaScript. Por ejemplo:
<form action="Default.aspx" method="post">
Un dato <input type="text" name="dato" />
<input type="submit" value="enviar" />
</form>
<script type="text/javascript">
document.forms[0].submit(); // se postean los datos
</script>
La invocación del método subtmit() en el código scriptprovoca
la inmediata llamada al action del formulario.
También se puede emular la pulsación de un enlace invocando su método click():
<a id="enlace1" href="http://www.google.es">google</a>
<script type="text/javascript">
document.getElementById("enlace1").click();
</script>
Este código provoca la inmediata redirección a la URL indicada en el enlace <a />.
6.4. Usando tipos de objetos.
JavaScript permite escribir aplicaciones web orientadas a objetos. Como muchos otros lenguajes modernos, se
permite definir objetos que tienen propiedades, métodos y eventos.
JavaScript proporciona varios tipos de objetos predefinidos, incluyendo:
• El tipo String, que permite manipular cadenas de texto. El tipo String proporciona propiedades como
length, que retorna el número de caracteres del string, y métodos como concat, que podemos usar para
juntar strings, así como métodos para convertir un string a mayúsculas o minúsculas, o buscar un substring
dentro del string.
var eventoSaludo = new String('Bienvenidos a nuestra conferencia');
var len = eventoSaludo.length;
Nótese que se usa el operador new para instanciar variables usando tipos de objetos.
• El tipo Date, que permite trabajar con fechas. JavaScript representa internamente un
dato de fecha como
los milisegundos transcurridos desde el 01/01/1970, y todos los métodos de fecha usan la zona horaria
GMT+0 (UTC), independientemente de que nuestro ordenador muestre una fecha en la zona horaria
apropiada.
var hoy = new Date(1346454000); // Número de milisegundos desde 01/01/1970
var hoy = new Date("September 1, 2012");
var hoy = new Date(2012, 8, 1); // Enero es 0, ..., Diciembre es 11.
• El tipo Array, que permite crear y trabajar con arrays de valores de longitud dinámica
en base cero. Los
arrays proporcionan métodos para buscar elementos dentro del array, para cambiar el orden de los
elementos, o tratar el array como una estructura de pila o cola.
var arrayVacioDeTresElementos = new Array(3);
var arrayDeEstaciones = new Array("Primavera", "Verano", "Otoño", "Invierno");
var terceraEstacion = arrayDeEstaciones[3]; // Invierno
var valores = new Array();
valores.push("Valor 1"); // se ubica en el índice 0
valores.push("Valor 2"); // se ubica en el índice 1

44
valores[valores.length] = "Valor 3"; // se ubica en el índice 2
También podemos usar la notación literal para crear y poblar un array:
var arrayDeEstaciones = ["Primavera", "Verano", "Otoño", "Invierno"];
Podemos determinar si un objeto es miembro de un array usando la función indexOf(). Se debe especificar
el elemento buscado, y la función retorna el índice de la primera aparición del elemento dentro del array, o -
1 si no se encuentra. Por ejemplo, el siguiente código asigna la variable ubicacionOtoño to 2:
var ubicacionOtoño = arrayDeEstaciones.indexOf("Otoño");
• El tipoRegExp, que permite crear y trabajar con expresiones regulares para casar strings. Se usa el método
test() para determinar si un string casa con la expresión regular.
var re = new RegExp("[dh]og");
if (re.test("dog")) {...}
JavaScript también define algunos tipos de objetos singleton. No se usan estos tipos para declarar variables, si
no que se usan las funcionalidades que proporciona el tipo. Los objetos singleton son:
• El objeto Math da acceso a varias constantes (como Pi y E) y funciones matemáticas (como seno, coseno,
raíz cuadrada, y un generador de números seudo-aleatorio) como propiedades y métodos estáticos. Por
ejemplo:
var e = Math.E;
var coseno = Math.cos(45);
var semilla = Math.random();
• El objeto Global contiene funciones y constantes globales para las constantes undefined, NaN, eInfinity.
6.5. Definiendo objetos usando JSON.
La Notación de Objetos JavaScript, o JSON como es conocida normalmente, es una sintaxis para representar
una o más instancias de un objeto y los valores de sus propiedades como un string. La sintaxis básica es como
sigue:
var miObjecto = {
"propiedad1" : "valor1",
"propiedad2" : "valor2",
... ,
"propiedadN" : "valorN"
};
Por ejemplo, podemos crear un objeto Asistente que represente los asistentes de una conferencia, como sigue:
var unAsistente = {
"nombre" : "Juan Pérez",
"puesto" : "1"
};
Hay unas pocas reglas básicas:
• Los nombres de propiedades y valores se separan con dos puntos.
• Cada para nombre-valor se separa con comas.
• Todos los nombres de propiedades y valores se encapsulan con comillas dobles.
• Están prohibidas los comas en matrices y objetos.
• La lista de propiedades se encapsula entre un par de llaves.
También se puede crear un objeto sin ninguna propiedad y posteriormente añadírselas:
var unAsistente = {};
unAsistente.nombre = "Juan Pérez";
unAsistente.puesto = 1;
Convertir un objeto JSON a su representación string se denomina serialización a formato JSON. Es fácil
realizar esta serialización usando el método JSON.stringify():
var str = JSON.stringify(unAsistente);
El proceso inverso, la deserialización, permite recuperar un objeto JSON a partir de su formato serializado.
Para esto se utiliza el método JSON.parse():
var copiaAsistente = JSON.parse(str);
Una colección de objetos serializados se indica mediante una lista separada con comas de objetos serializados
encerrados entre un par de corchetes. Por ejemplo, aquí tenemos dos objetos Asistente serializados en JSON.
var listaDeAsistentes = [
{ "nombre": "Juan Pérez", "puesto": "1" },
{ "nombre": "Martín López", "puesto": "2" }
];

45
JSON ha visto incrementada su importancia como el formato de facto para pasar datos en solicitudes AJAX
entre una página web y un servidor web.
6.6. El modelo de objetos del documento (DOM).
Todos los navegadores actuales implementan un modelo de objetos llamado DOM, definido por W3C para
representar la estructura de una página web. DOM proporciona una API programática, permitiendo escribir
código JavaScript que realice tareas comunes como encontrar el título de la página, cambiar el contenido de
una lista, añadir nuevos elementos a la página, y mucho más. DOM define las propiedades que un script
puede cambiar para un elemento de una página, y qué acciones podemos hacer sobre el documento.
6.6.1. Búsqueda de elementos.
Después de que una página ha sido cargada, una acción común es encontrar un elemento o un conjunto de
elementos para consultarlo o manipularlo. Por ejemplo, podemos necesitar obtener una referencia a una lista
para poblarla con elementos recuperados de un servicio web. DOM representa varias partes de un
documento como un conjunto de arrays:
• El arrayforms contiene detalles de todos los formularios del documento.
• El array images contiene todas las imágenes del documento.
• El arraylinks contiene todos los híper-enlaces del documento.
• El arrayanchors contiene todas las etiquetas <a>del documento con un atributo name.
• El arrayapplets contiene todos los applets del documento.
Todas estas colecciones son propiedades hijas del objeto document. Este objeto representa al documento.
Podemos usar la notación del punto para acceder a cada colección, a cualquier propiedad, método o evento
definido en DOM. Para acceder a elementos individuales de una colección, se puede usar índice o una
referencia al valor del atributo name. Por ejemplo, si tenemos el siguiente formulario en una página:
<form name="contactForm">
<input type="text" name="nameBox" id="nameBoxId" />
</form>
Podemos acceder al objeto DOM que representa al formulario de las siguientes formas:
document.forms[0] // forms es un array en base cero
document.forms["contactForm"]
document.forms.contactForm
document.contactForm
También se puede acceder al objeto DOM que representa la caja de texto dentro del formulario de varias
formas, incluyendo las siguientes:
document.forms.contactForm.elements[0]
document.forms.contactForm.elements["nameBox"]
document.forms.contactForm.nameBox
document.contactForm.nameBox
Alternativamente, DOM define métodos en el objeto document que recuperan elementos según el valor de
sus atributos ID yname. Podemos usar estos métodos para encontrar rápidamente elementos determinados,
sin tener que recorrer toda la ruta de dependencias de los elementos. Estos métodos incluyen:
• document.getElementById( IdString ), que retorna el elemento cuyo atributo ID coincide con el argumento.
• document.getElementsByName( NameString ), que retornar un array de elementos cuyos atributos name
coinciden con el argumento.
El siguiente ejemplo muestra cómo obtener una referencia a un cuadro de texto nameBoxId:
var userNameBox = document.getElementById("nameBoxId");
var username = userNameBox.value;
6.6.2. Añadir, eliminar y manipular objetos en DOM.
Después de encontrar un elemento o conjunto de elementos, el siguiente paso es normalmente modificarlos
de alguna forma, añadiendo nuevos elementos hijos, modificando los elementos existentes o eliminándolos.
El API DOM Core define varios métodos para crear nuevos objetos en un documento, incluyendo:
• document.createElement(nombreEtiqueta)
• document.createTextNode(string)
• document.createAttribute(nombre, valor)
• document.createDocumentFragment
Todos estos métodos crean un objeto de nodo DOM, el cual es una representación genérica de un elemento,
texto o atributo en DOM. Después de crear el nodo DOM, hay que añadirlo al modelo. El modo más simple

46
es usando el método document.getElementbyId() para recuperar el elemento contenedor en el cual queremos
incluir nuestro nodo, y entonces usar uno de los siguientes métodos sobre este elemento contenedor:
• appendChild(nuevoNodo), añade un nuevo nodo como el último hijo del elemento seleccionado.
• insertBefore(nuevoNodo, nodoExistente), añade el nuevo nodo en el modelo DOM después del nodo
existente, pero como un nodo hermano.
• replaceChild(nuevoNodo, nodoExistente), reemplaza el nodo existente por el nuevo nodo.
• replaceData(desplazamiento, longitud, string), reemplaza el texto en un nodo de texto seleccionado. El
parámetro desplazamiento especifica a partir de qué carácter se aplica el reemplazo, longitud indica cuántos
caracteres se reemplazan, y string especifica el texto a insertar.
Para usar estos métodos efectivamente y asegurar el destino donde añadir nuevos nodos, necesitamos
también movernos a través del árbol del documento. Podemos usar las siguientes propiedades para navegar a
través de DOM:
• childNodes, retorna los nodos hijos de un nodo seleccionado.
• firstChild, retorna el primer hijo de un nodo seleccionado.
• lastChild, retorna el último hijo de un nodo seleccionado.
• nextSibling, retorna el siguiente nodo hermano de un nodo seleccionado.
• parentNode, retorna el nodo contenedor de un nodo seleccionado.
• previousSibling, retorna el anterior nodo hermano de un nodo seleccionado.
El siguiente ejemplo muestra cómo modificar una lista de una página:
<!-- Marcado HTML -->
<ul id="ListaHotel">
<li>Habitación A</li>
<li>Habitación B</li>
</ul>
<script>
// Código JavaScript para modificar los elementos de la lista ListaHotel
var list = document.getElementById("ListaHotel");
// Crea una nueva habitación
var newItem = document.createElement("li");
newItem.textContent = "Habitación C";
// Añade la nueva habitación al final de la lista
list.appendChild(newItem);
</script>
DOM también define métodos para quitar nodos del árbol del documento, incluyendo:
• removeChild(nodo), eliminar el nodo destino.
document.removeChild( document.getElementById("ListaHotel").firstChild );
• removeAttribute(nombreAtributo), elimina el atributo destino del nodo elemento.
var list = document.getElementById("ListaHotel");
list.removeAttribute("id");
• removeAttributeNode(nodo), elimina el nodo atributo destino del nodo elemento.
var list = document.getElementById("ListaHotel");
list.removeAttribute(list.attributes[0]);
Para limpiar un nodo de texto en vez de quitarlo completamente, se puede asignar a un string vacío.
6.6.3. Manejando eventos de DOM.
HTML define varios eventos desencadenados por el usuario que podemos asociar a una acción JavaScript.
Por ejemplo, cuando un usuario mueve el ratón por encima de un icono de ayuda, se lanza el evento
mouseover. Cuando se lanza un evento, si hemos asignado un oyente para el evento, el navegador lo ejecutará.
Muchos elementos HTML proporcionan devoluciones de llamada que podemos usar para capturar varios
eventos y definir código que actúe como un oyente. Estas devoluciones de llamada normalmente tienen el
nombre onNombreEvento donde NombreEento es el nombre del evento. Por ejemplo, podemos manejar el
evento mouseover de un elemento <img>usando la devolución de llamada onmouseover, tal como sigue:
<img src="helpicon.gif" id="helpIcon" onmouseover="window.alert('Algún texto de ayuda');" />
Nota: Una devolución de llamada es una referencia a una función que se ejecuta como resultado de que
se complete otra acción. En este caso de manejar eventos para un elemento HTML, el navegador provoca
la devolución de llamada cuando se desencadena el evento correspondiente.

47
También podemos asignar el manejador de evento como una propiedad del nodo que representa la imagen en
DOM, tal como sigue:
document.images.helpIcon.onmouseover = function() { window.alert('Algún texto de ayuda'); };
Este ejemplo muestra una instancia de una función anónima; la palabra clave function es seguida por un par
de paréntesis y el código encapsulado entre llaves. No se requiere el nombre de la función porque la función
sólo será invocada cuando se desencadene la devolución de llamada onmouseover.
Sin embargo, este método sólo permite asignar un oyente al evento. El modelo DOM también permite añadir
y quitar dinámicamente varios oyentes de eventos en elementos del documento usando los siguientes
métodos:
• addEventListener(nombreEvento, funcionOyente, propagacion), añade la función oyente al elemento asociado
al evento referenciado. Podemos pasar la función oyente como una función con nombre o como una
función anónima.
var helpIcon = document.getElementById("helpIcon");
// Añadimos un oyento de evento para el evento mouseover usando una función
function MuestraAyuda()
{
window.alert('Algún texto de ayuda');
}
helpIcon.addEventListener("mouseover", MuestraAyuda, false);
// Alternativamente, podemos usar una función anónima
helpIcon.addEventListener("mouseover", function() { window.alert('Algún texto de ayudat'); }, false);
• removeEventListener(nombreEvento, funcionOyente, propagacion), quita una función oyente del elemento
para el evento referenciado.
helpIcon.removeEventListener("mouseover", MuestraAyuda, false);
Algunos eventos en DOM se propagan, significando esto que si el evento ocurre sobe un elemento (y es
manejado o no), el evento se disparará también sobre el elemento contenedor, y se irá propagando por los
elementos contenedores hasta el evento sea rechazado por uno de ellos. Tanto addEventListener
comoremoveEventListener tienen un tercer parámetro opcional que indica si debe propagarse o no el evento.
El siguiente ejemplo muestra cómo enlazar un pequeño script, el cual copia el contenido de una caja de texto
a una zona de la pantalla, cuando hace clic sobre un botón de formulario.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Enlazando eventos con JavaScript</title>
</head>
<body>
<form>
<p><label>Escribe tu nombre: <input type="text" id="NameBox" /></label></p>
<input type="button" id="submit" value="Púlsame para enviar" />
</form>
<div id="areaDeGracias"></div>
<script type="text/javascript">
function diGraciasu() {
var userName = document.getElementById("NameBox").value;
var gracias = document.createElement("p");
gracias.textContent = "Gracas " + userName;
document.getElementById("areaDeGracias").appendChild(gracias);
}
document.getElementById("submit").addEventListener("click", diGracias);
</script>
</body>
</html>
6.7. Validando campos mediante JavaScript
Los tipos de campos y atributos de HTML5 son útiles para realizar validación simple de los datos. Sin
embargo, para tipos de validación más complejos, o donde se necesite referenciar varios campos en un
proceso de validación, podemos necesitar usar código JavaScript.

48
Podemos usar manejadores de eventos para validar campos de formulario con JavaScript. Cuando el usuario
confirma un formulario, se lanza el evento submit, y podemos capturarlo para validar los datos antes de que
sean enviados al servidor para su procesamiento. Si la validación sucede, el manejador del evento debe
retornar true y el formulario enviará los datos; si la validación falla, el manejador del evento debe retornar
false para anular la confirmación.
El siguiente ejemplo de código muestra un formulario que ejecuta el método validarEdadcuando el usuario
postea los datos. El atributo onsubmit del formulario especifica el código JavaScript que será ejecutado
cuando ocurra el evento submit:
<body>
<form id="registrationForm" method="post" action="registro.aspx"
onsubmit="return validarEdad();" >
Tu edad <input type="number" id="edad" name="edad" />
<input type="submit" value="Enviar" />
</form>
<script type="text/javascript">
function validarEdad() {
var edad = document.getElementById("edad").value;
return (edad >= 18);
}
</script>
</body>
En este ejemplo se valida que la edad introducida por el usuario sea mayor que 17. En otros escenarios la
función referenciada en el atributo onsubmit debería examinar cada campo para validarlo. Si algún campo
fallase, la función retornará false, y si no retornará true. Esta forma de validar permite realizar una
comprobación más comprensiva de cada campo, y permite validar datos entre campos.
Cada campo de un formulario también lanza un evento input, y podemos capturarlo para realizar sólo la
validación sobre cada campo de forma independiente, en vez de sobre todo el formulario. Esta validación
ocurre a medida que el usuario introduce los datos, y si los datos no son válidos podemos asignar el indicador
CustomValidity para decirle al formulario que no puede enviar los datos hasta que sean corregidos. Por
ejemplo, si el formulario contiene un campo con un id deconfirmar-edad, podemos validar el dato introducido
en el campo de la siguiente manera:
<body>
<form id="registrationForm" method="post" action="registro.aspx">
Tu edad <input type="number" id="edad" name="edad" oninput="validarEdad();" />
<input type="submit" value="Enviar"/>
</form>
<script type="text/javascript">
function validarEdad() {
var campo = document.getElementById("edad");
if (edad >= 18) {
campo.setCustomValidity("");
} else {
campo.setCustomValidity("Debe ser mayor de edad");
}
}
</script>
</body>
En este ejemplo, si la edad introducida es menor que 18 la función setCustomValidity()muestra un mensaje de
error personalizado e impide que los datos sean enviados al servidor. Si el dato es válido, se pasa un string
vacío a la función setCustomValidity()reiniciando el manejador del error, y los datos serán enviados al servidor.

PRÁCTICA
01.Cread una aplicación Web con una página inicial ("index.html") donde se muestre un formulario que
permita introducir dos números y seleccionar una operación (como mínimo suma, resta, multiplicación y
división).

49
Utiliza código JavaScript para gestionar el clic sobre cada botón de operación. Una función de JavaScript
debe encargarse de recuperar los valores escritos en los controles de edición, realizar la operación
correspondiente y mostrar el resultado.
Incluye opciones de validación para por si falta uno de los operandos o si la operación no es posible.

02. Cread una aplicación web que permita añadir nombres de producto a una lista no ordenada:

Si hay algo escrito en el cuadro de edición, al pulsar el botón «Agregar a la lista» hay que añadir el nuevo
producto al final de la lista posterior. Usa código JavaScript para hacer esto, almacenando los productos en un
array.
Al pulsar el botón «Enviar al servidor» hay que enviar el array en formato JSON al servidor Web. Para ello
crea una página JSP llamada "productos.jsp", y úsala para postear el array de productos. En la página
"productos.jsp" simplemente muestra el array en su formato serializado.

50
UNIDAD 12. LA TECNOLOGÍA SERVLET
1. Fundamentos sobre Servlets
Los servlets son clases de Java que se ejecutan en un servidor como respuesta a una solicitud desde una
página web en un ordenador cliente, y que (aparte de otros procesos) normalmente generan una página web
de respuesta en el ordenador cliente.
1.1. El contenedor web.
Un contenedor web (o también llamado contenedor de servlets) es un módulo que permite mantener la
arquitectura multicapa de Java EE. Aquellos servidores web que soportan la plataforma Java EE, como
Apache Tomcat, implementan este módulo.
Este módulo especifica dentro de un servidor un entorno de ejecución para componentes web que incluye
seguridad, concurrencia, gestión del ciclo de vida, procesamiento de transacciones, despliegue y otros
servicios. Un contenedor web suministra los mismos servicios que un contenedor de páginas JSP así como de
otros tipos de recursos de la plataforma J2EE.
Un modelo muy simplificado de cómo funciona un contendor web viene dado por el siguiente escenario:
• Una página web, que contiene un formulario, es mostrada en un navegador cliente (como «Internet
Explorer» o «Chrome»).
• El usuario rellena el formulario y pulsa el botón para postear los datos. Los datos son posteados mediante
una solicitud Post o Get, y enviados a un recurso especificado por la ruta de la solicitud.
• En el servidor web, el contendor web determina el servlet asociado a la ruta solicitada.
•El servlet recupera los datos posteados, los procesa y genera una página web de respuesta hacia el
navegador cliente.
Figura 1

1.2. El API Servlet.


Dentro del modelo cliente/servidor de la tecnología Web de Java EE, los servlets son las piezas
fundamentales para poder generar recursos dinámicos. Son capaces de recuperar cualquier información
enviada por un cliente y generar una respuesta dinámicamente.
Al tratarse de clases de Java se benefician de la gran capacidad de Java para ejecutar métodos en ordenadores
remotos, para conectarse a base de datos, aplicar seguridad en la información, etc.Además son ejecutados por
el Servidor Web, el cual controla su ciclo de vida, y por tanto garantiza que el código que ejecute el servlet se
hará de forma segura en el contexto general de ejecución del servidor web.
Además de lo dicho, los servlets aportan las siguientes características:
• Son independientes del servidor y de su sistema operativo. La independencia de plataforma es una de las
características del lenguaje Java.
• Un servlet puede invocar a otro servlet. De esta forma se puede distribuir de forma más eficiente la carga
de trabajo que supone procesar una solicitud HTTP.
• Pueden obtener fácilmente información acerca del cliente (la que le permite el protocolo HTTP), tal como
su dirección IP, el puerto, el método de llamada, el tipo de navegador, etc.
• Permite gestionar cookies y sesiones, de forma que se puede guardar información en el lado cliente
(mediante cookies) y en el lado servidor (mediante variables de sesión).

51
• Permiten interactuar con bases de datos en arquitecturas de 3 capas. Esto habilita que las solicitudes se
puedan adaptar a operaciones sobre una base de datos.
• Al poder generar una respuesta de forma dinámica, permiten crear contenedores que aporten una
estructura común a las páginas, o trozos reutilizables para embeber en varias páginas.
La tecnología de los servlets está basada en dos paquetes:
•javax.servlet. Este paquete define 6 interfaces y 3 clases para la implementación de servlets genéricos, sin
especificación de protocolo.
•javax.servlet.http. Define clases específicas para el protocolo http (el usado para navegar por páginas
web).
1.2.1. La clasebase «HttpServlet».
Los servlets que se utilizan en aplicaciones Web HTTP deben ser clases que extiendan a la clase base
javax.servlet.HttpServlet. El siguiente trozo de código muestra los paquetes que se utilizan normalmente al
crear un servlet:
import javax.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
public class MiServlet extends HttpServlet {
....
}
Para dotar de funcionalidad a un servlet se han de redefinir una serie de métodos de la clase base:
▪ public void init (ServletConfig config)
Se ejecuta la primera vez que el servidor web debe instanciar al servlet. A través del argumento config se
accede a los parámetros de inicialización del servlet que se establecieron al configurarlo. El método
config.getServletContext() retorna un objeto de tipo ServletContext, que permite acceder a la información
del servidor web.
Importante. Este método debe completar su ejecución antes de que el servlet pueda recibir cualquier
solicitud. El contenedor de servlets no podrá activar el servlet para que gestione una solicitud si el método
init() lanza una ServletException, o si no se ejecuta en un periodo de tiempo definido por el servidor web.
▪ public void destroy ()
Será llamado por el servidor web cuando la instancia del servlet esté a punto de ser descargada de
memoria. En este método se han de realizar las tareas necesarias para conseguir una finalización
apropiada, como cerrar archivos y flujos de entrada de salida externos a la petición, cerrar conexiones
persistentes a bases de datos, etc.
▪ public void service (ServletRequest request, ServletResponse response)
El método service() se ejecuta cada vez que el servlet es llamado y sus argumentos nos permiten obtener
información de la petición (request) y un flujo de salida para escribir la respuesta (response). Según el
método de solicitud HTTP, este métodoservice(), a su vez, invoca alguno de los siguientes métodos.
▪ protected void doGet (HttpServletRequest request, HttpServletResponse response)
▪ protected void doPost (HttpServletRequest request, HttpServletResponse response)
▪ protected void doTrace(HttpServletRequest reques, HttpServletResponse response)
▪ protected void doOptions(HttpServletRequest request, HttpServletResponse response)
▪ protected void doDelete(HttpServletRequest request, HttpServletResponse response)
▪ protected void doPut(HttpServletRequest request, HttpServletResponse response)
▪ protected void doHead(HttpServletRequest request, HttpServletResponse response)
Cada uno de estos métodos se ejecuta en respuesta a un tipo de solicitud HTTP concreto. Por ejemplo,
si se utiliza el verbo POST será invocado el método doPost(), si se utiliza el verbo DELETE será invocado el
método doDelete(), etc.
El método service() es el encargado en primer lugar de procesar una solicitud. Pero como ya incorpora una
funcionalidad para redirigir la solicitud a uno de los métodos do_(), se recomienda no reescribir service() y
reescribir los métodos do_() apropiados para procesar el tipos de solicitudes que nos interese.
1.2.2. Interfaz «HttpServletRequest».
La interfaz java.servlet.http.HttpServletRequest proporciona los métodos para recuperar la información de la
petición desde el navegador cliente.
Métodos de HttpServletRequest:
▪ String getHeader(String cabecera)
Devuelve el valor de la cabecera http indicado de la página web de llamada o null si no se suministró.

52
▪ Cookie[] getCookies()
Devuelve una array con las "cookies" del cliente.
▪ Enumeration getHearderNames()
Obtiene una enumeración de todos los nombres de cabecera de la página web de esta petición particular.
▪ int getMethod ()
Devuelve el método de petición principal (normalmente GET o POST, pero son posibles cosas como
HEAD, PUT, y DELETE).
▪ URI getRequestURI ()
Devuelve la URI (la parte de la URL que viene después del host y el puerto, pero antes de los datos de
los parámetros enviados).
▪ String getRemoteHost ()
Devuelve el nombre del ordenador que realizó la petición.
▪ String getParameter (String parámetro)
Devuelve el primer valor asociado al parámetro indicado o null si dicho parámetro no existe.
▪ String[] getParameterValues(String parámetro)
Devuelve un array con los valores asociados al parámetro especificado o null si dicho parámetro no
existe.
▪ Enumeration getParameterNames ()
Devuelve una enumeración de los nombre de los parámetros empleados en la petición.
1.2.3. Interfaz «HttpServletResponse».
La interfaz java.servlet.http.HttpServletResponse proporciona métodos para realizar la respuesta al cliente que
originó la petición.
Métodos de HttpServletResponse:
▪ PrintWriter getWriter ()
Obtiene un stream a través del cual transmitir la página web de respuesta al cliente.
▪ void setContentType (String tipo)
Permite establecer el tipo MIME (tipo de documento) de la respuesta.
▪ void setContentLenght (int len)
Permite especificar la longitud de la respuesta. Si no se indica el propio motor de servlets calculará la
longitud de la respuesta.
1.3. Creación y registro de un servlet.
Para que los servlet puedan ser utilizados como recursos dinámicos en una aplicación web deben ser
registrados en el contenedor web. Para ver cómo se hace esto crearemos primero un proyecto web llamado
"WebAplicación" usando NetBeans, y le añadiremos el archivo descriptor "web.xml".
Figura 2

En la página inicial index.html se ha añadido un enlace <a /> a través del cual podremos invocar un servlet.
1.3.1. Cómo añadir un servlet a un proyecto web.
Comenzaremos añadiendo un servlet al proyecto "WebAplicación" usando la plantilla que incorpora NetBeans.
Al ser clases de Java, los servlets deben crearse en el nodo «Source packages». Sobre este nodo hay que abrir
el menú contextual y pulsar sobre «Nuevo|Servlet». Si no aparece la opción «Servlet» hay que seleccionar la
opción «Otros» y en la categoría «Web» seleccionar la plantilla «Servlet».

53
Figura 3

En el cuadro de diálogo «Nuevo Servlet» pondremos como nombre de la clase "ServletSaludo", y la


ubicaremos en un nuevo paquete con el nombre "servlet".
Figura 4

Tras pulsar el botón «Siguiente», el asistente nos ofrece las opciones para configurar y registrar el servlet en el
contenedor web.
1.3.2. Registro de un servlet en el archivo descriptor.
El mecanismo tradicional para registrar un servlet es hacerlo en el fichero descriptor de la aplicación. En el
cuadro de diálogo de «Nuevo Servlet» simplemente tenemos que marcar la opción «Add information to
deployment descriptor (web.xml)». Si existe el fichero /WEB-INF/web.xml se registrará la información que
establezcamos en este asistente, pero si no existe el servlet no será registrado en el contenedor web.

54
Figura 5

Para configurar un servlet debemos proporcionar un nombre de servlet (como este nombre se utiliza para
información interna de registro podemos utilizar el propio nombre de la clase), y un patrón URL. Este patrón
URL es la ruta de la URI que mapeará las solicitudes con el servlet. Para este ejemplo se ha asignado /saludo.
Por motivos que se explicarán a continuación es importante que un patrón URL comience con una barra /.
Tras pulsar el botón «Finalizar» se creará el servlet y quedará registrado en el fichero descriptor. El fichero
web.xml quedará como sigue:
<?xml version="1.0" encoding="UTF-8"?>
<web-app …………… >
<servlet>
<servlet-name>ServletSaludo</servlet-name>
<servlet-class>servlet.ServletSaludo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletSaludo</servlet-name>
<url-pattern>/saludo</url-pattern>
</servlet-mapping>
………………….
</web-app>
Como se ve, la clase del servlet se registra con una etiqueta <servlet>, donde se especifica el nombre del
servlet y su nombre de clase cualificado (incluyendo los paquetes donde está contenida).
La etiqueta <servlet-mapping>se encarga de mapear un servlet declarado en <servlet> con su patrón URL (la
ruta a la que quedará asociado). Pero un mismo servlet puede tener asociados varios elementos <servlet-
mapping>, esto quiere decir que podemos asociar un servlet con varios patrones URL. Por ejemplo,
añadiremos el código necesario para asociar también el servlet ServletSaludo con la URI "/saluda".
<?xml version="1.0" encoding="UTF-8"?>
<web-app …………… >
<servlet>
<servlet-name>ServletSaludo</servlet-name>
<servlet-class>servlet.ServletSaludo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletSaludo</servlet-name>

55
<url-pattern>/saludo</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ServletSaludo</servlet-name>
<url-pattern>/saluda</url-pattern>
</servlet-mapping>
………………….
</web-app>
Cada una de las rutas especificadas en el patrón URL podrá ser usada en las páginas para invocar el servlet.
1.3.3. Registro de un servlet mediante anotaciones.
Desde el JDK 7 también se pueden registrar los servlets mediante anotaciones en la propia clase. Para
conseguir esto, en el cuadro de diálogo de «Nuevo Servlet» simplemente tenemos que dejar desmarcada la
opción «Add information to deployment descriptor (web.xml)».
Figura 6

Para ese ejemplo, en el fichero "ServletSaludo.java"se añadirán las siguientes anotaciones sobre la clase:
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "ServletSaludo", urlPatterns = {"/saludo"})
public class ServletSaludo extends HttpServlet {
……………….
}
La anotación javax.servlet.annotation.WebServlet posee los siguientes atributos:
Atributo Descripción
name Un string con el nombre del servlet. Este nombre es usado internamente por el
contenedor web y se corresponde con la etiqueta <servlet-name>.
urlPatterns Un array de strings con los patrones URL asociados con el servlet.
loadOnStartup Un entero que indica el orden de carga del servlet.
56
initParams Un array de objetos @WebInitParam con parámetros de inicialización del servlet.
asyncSupported Un valor booleano que indica si el servlet soporta operaciones asíncronas.
smallIcon Un string con la ruta con un icono pequeño para representar el servlet.
largeIcon Un string con la ruta con un icono grande para representar el servlet.
description Un string con una descripción larga del servlet.
displayName Un string con una descripción corta del servlet.
Si queremos que el servlet también mapee con la ruta "/saluda" debemos modificar la anotación WebServlet de
la siguiente manera:
@WebServlet(name = "ServletSaludo", urlPatterns = {"/saludo", "/saluda"})
1.3.4. Reglas acerca del mapeado de patrones URL.
Los patrones URL que mapean rutas con los servlets deben cumplir con las siguientes reglas:
• Para que el servlet case con una URI exacta debe empezar siempre con una barra (/). Por ejemplo:
/gestion/Informes.do CORRECTO
gestion/Informes NO CORRECTO
• Para que el servlet case con cualquier archivo de una carpeta (real o virtual) se puede usar el asterisco (*)
como caracter comodín.
/gestion/* CORRECTO
gestion/* NO CORRECTO
• Para que el servlet case con cualquier archivo (real o virtual) con una extensión determinada también
podemos usar el caracter comodín asterisco (*). Si se usa el asterisco al inicio de la URI, ésta no debe
empezar con una barra (/). Por ejemplo:
*.do CORRECTO
/*.do NO CORRECTO
Importante. Un url-pattern sólo puede incluir un caracter asterisco (*), y éste sólo puede aparecer al
principio o al final de la URI. Por tanto no es correcta la URI: /gestion/*/Informe
Los patrones URL usados para mapear un servlet se pueden corresponder con rutas físicas existentes o con
rutas virtuales no existentes. Por ejemplo, si un servlet declara el siguiente patrón URL:
<url-pattern>/gestion/*</url-pattern>
Podemos usar la siguiente URI para invocar el servlet:
http://localhost:8080/gestion/Informe1.jsp
Tanto la carpeta gestion como el archivo Informe1.jsp pueden o no existir en el disco del servidor. Sin
embargo, para el contenedor web es importante saber cuál es la localización virtual de un servlet,
independientemente de la ubicación física del fichero .class. Por ello el patrón URL exacto de un servlet debe
comenzar con la barra /, para indicar al contenedor web que la ruta comienza desde la carpeta raíz de la
aplicación. Esto es importante cuando hagamos redirecciones desde un servlet usando rutas relativas.
Para el mapeado de una URI solicitada con el patrón URL se aplican las siguientes reglas:
1) Cuando se invoca un servlet mediante una URI, el contenedor de servlets mira primero por un patrón
que case exactamente, y si no lo encuentra mira por un patrón que case una carpeta, y si no existe mira por
un patrón que case una extensión.
2) Si una solicitud de URI casa con más de un patrón de carpeta, el contenedor de servlets elige el que sea
más específico. Por ejemplo, si se han definido los dos siguientes patrones:
<url-pattern>/foo/bar/*</url-pattern>
<url-pattern>/foo/*</url-pattern>
Y se invoca la ruta /foo/bar/miRecurso, entonces el contenedor elige al primer patrón porque es más
específico que el segundo.
1.4. Ejecución de un servlet.
Siguiendo con el proyecto de ejemplo previo, vamos a analizar la clase "ServletSaludo" y ver cómo ejecutar el
servlet. Una vez eliminados los comentarios, el servlet creado previamente contiene el siguiente código:
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;

57
import javax.servlet.http.HttpServletResponse;
public class ServletSaludo extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet ServletSaludo</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet ServletSaludo at " + request.getContextPath() + "</h1>");
out.println("</body>");
out.println("</html>");
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
public String getServletInfo() {
return "Short description";
}
}
La plantilla generada por NetBeans reescribe por defecto los método doGet() y doPost(), para que servlet
pueda procesar solicitudes GET y POST. Si no queremos que el servlet procese uno de los verbos HTTP basta
con no reescribir el método do_() correspondiente. Como una facilidad, la plantilla generada para la clase hace
que tanto doGet() como doPost() ejecuten el mismo código de proceso haciendo una llamada a un método
protegido processRequest().
Se pueden reescribir otros métodos de proceso como doDelete(), doHead(), etc. Todos estos métodos de
proceso reciben dos objetos como parámetro:
• Un objeto HttpServletRequest, que nos dará acceso a cualquier información recibida con la solicitud.
• Un objeto HttpServletResponse, que nos dará acceso a un canal a través del cual enviar una respuesta
dinámica al navegador. También proporciona métodos para redirigir la solicitud y agregar datos a la
respuesta.
El método processRequest() recibe estos dos parámetros, y los utiliza para generar de forma dinámica, como
respuesta, una sencilla página HTML. A continuación se comenta el significado de las instrucciones de este
método:
• response.setContentType("text/html;charset=UTF-8")
El primer paso para generar una respuesta dinámica desde un servlet es especificar el tipo de contenido o
tipo MIME que se enviará al navegador. En este caso se especifica que el navegador recibirá un documento
HTML con la codificación de caracteres UTF-8.
•PrintWriter out = response.getWriter()
Para poder enviar una respuesta dinámica al navegador cliente hay que recuperar un canal de tipo stream a
través del cual podamos escribir el contenido. Tanto el método getWriter() como getOutputStream() del
objeto HttpServletResponse permiten obtener este canal.
• out.println("<!DOCTYPE html>")
Se utiliza el canal de escritura para enviar el contenido de una página HTML con sus etiquetas
constituyentes.

58
Una vez enviada la etiqueta de cierre </html> es necesario cerrar el canal de escritura. En este código el
bloque try () {} se encarga de invocar automáticamente el método close() del stream de escritura.
El código de ejemplo de este servlet simplemente envía al navegador una página con un mensaje. Veamos
ahora cómo ejecutar el servlet.
La opción más directa para probar un servlet es ejecutarlo directamente usando el menú «Ejecutar fichero» o
pulsando las teclas Mayus+F6:
Figura 7

Se abre un cuadro de diálogo que nos permite escribir la ruta con la que queramos invocar el servlet con el
método GET HTTP. Tras aceptar se ejecutará el servlet y su resultado se mostrará en un navegador web:
Figura 8

También podemos ejecutar el servlet invocándolo a través de un enlace. Si ejecutamos la aplicación web se
abrirá en el navegador la página index.html, desde la cual podemos navegar al servlet pinchando en el enlace:

59
Figura 9

1.5. Ruta de contexto para localizar servlets.


El proceso de solicitud de un servlet implica que el servidor web debe saber localizar el servlet. A esto se le
denomina enrutamiento de la petición al servlet, y consiste de dos pasos:
• El contenedor de servlets identifica la aplicación web a la que pertenece la petición.
• Luego se localiza el servlet (perteneciente a la aplicación web) apropiado para manejar la petición.
Ambos pasos requieren que el contenedor de servlets divida la URL de la petición en tres partes:
• Context path (ruta de contexto): El contenedor de servlets intenta concordar la mayor parte posible de la
URI con el nombre de una aplicación web disponible. Si no existe concordancia, se asocia la petición con la
aplicación web por defecto.
Para el servidor Tomcat la aplicación web por defecto es la que está instalada en la carpeta webapps/ROOT.
• Servlet path (ruta del servlet): El contenedor de servlets intenta concordar la mayor parte del resto de la
URI con un patrón URL definido para el servlet. Si no se encuentra ninguna concordancia, se retorna una
página de error.
• Path info (información de ruta). Es el resto de información de la URI, es decir, el resto luego de haber
determinado el servlet que manejará la petición. En caso de que la URI posea parámetros, el path info es
todo el resto de la misma hasta la aparición del primer carácter '?', el cual indica el comienzo de la cadena de
consulta con los parámetros.
La interfazjavax.servlet.http.HttpServletRequest provee tres métodos para obtener esta información:
getContextPath(), getServletPath(), getPathInfo(). Además, el método getQuerySting() permite obtener la parte
correspondiente a la cadena de consulta.
Como ejemplo supongamos que en la aplicación "WebAplicacion" creamos un servlet asociado al patrón URL
"/saludo/*". Si recibimos una solicitud como:
http://localhost:8084/WebAplicacion/saludo/don?nombre=Pedro
Obtendremos la siguiente información de la URI:
getContextPath() = "/WebAplicacion"
getServletPath() = "/saludo"
getPathInfo() = "/don"
getQuerySting() = "nombre=Pedro"
Una vez identificada la ruta de contexto, el contenedor de servlets sigue un algoritmo para determinar el
servlet que manejará la petición. Este algoritmo verifica, en ciertos pasos ordenados, la concordancia de la
URI con los patrones URL de los servlets. Una vez hallada la concordancia el algoritmo finaliza. Los pasos
que se siguen son:
• El contenedor intenta coincidir la URI completa. En este caso considera que el path info es null.
• Se intenta coincidir la URI recursivamente a través del separador / quitando elementos de atrás para
adelante. Si hay una coincidencia, la parte que concuerda es el servlet path y la parte restante es el path info.

60
• Si el último nodo de la URI contiene una extensión, el contenedor de servlets intenta encontrar un servlet
que maneje todas las peticiones con esa extensión. En este caso, la URI completa es el servlet path, y el path
info es null.
• Si el contenedor sigue sin encontrar una coincidencia, envía la petición al servlet por defecto. Si no existe
un servlet por defecto, se devuelve un mensaje de error.

2. Contexto de solicitud y respuesta de los servlets


Un servlet existe dentro de un contenedor web, el cual se encarga de redirigir solicitudes hacia los métodos de
proceso del servlet. El contenedor web es el encargado de crear los objetos HttpServletRequest y
HttpServletResponse que reciben los métodos el servlet. Estos dos objetos encapsulan toda la información que
necesita para su ejecución el servlet: parámetros de solicitud, cabeceras, cookies y atributos de contexto.
2.1. Inicialización de los servlets.
Los servlets usados en las aplicaciones web deben estar declarados en el archivo de configuración web.xml. El
contenedor de servlet de la Aplicación Servidor instancia cada servlet registrado usando el método
Class.forName(ClaseDelServlet).newInstance(). Para ello es preciso que la clase del servlet posea un constructor
público sin argumentos.
2.1.1. Ciclo de vida de los servlets.
El contenedor web inicializa un servlet cargando su clase, invocando su constructor sin argumentos, e
invocando el método init().
El método init() (el cual puede sobrescribir el programador) solo es invocado una vez en la vida del servlet, y
siempre antes de que éste pueda dar servicio a cualquier petición cliente.
El método init() brinda acceso a los objetos ServletConfig y ServletContext, los cuales el servlet necesita para
obtener información sobre su propia configuración y la de la aplicación web.
El contenedor finaliza la vida de un servlet invocando su método destroy().Esta instancia ya no podrá ser
reutilizada. El contenedor decide destruir un servlet si éste no es solicitado por un largo tiempo.
La mayoría del tiempo de vida de un servlet se pasa ejecutando el método service() para cada petición de un
cliente. Por defecto cada petición a un servlet se ejecuta en un hilo separado y sólo existe una instancia de una
clase servlet en particular.Una vez destruido, el recolector de basura se encargará de liberar la memoria
ocupada por el servlet.
Figura 10

2.1.2. Configuración de la precarga de un servlet.


Como se ha visto, la primera vez que un cliente invoca un servlet el contenedor instancia su clase e inicializa
el servlet (asignando el ServletContext, invocando oyentes, etc.).
Si queremos que un servlet sea cargado en tiempo de despliegue (cuando el servidor web arranca nuestra
aplicación), en vez de la primera solicitud, podemos usar el elemento <load-on-startup> del archivo
descriptor. Un valor no-negativo dentro de esta etiqueta le dice al contenedor que inicialice el servlet cuando
se despliega la aplicación web, o cuando es recargada.
Si queremos precargar varios servlets podemos controlar el orden en que son inicializados mediante el valor
de la etiqueta <load-on-startup>. Por tanto los valores de esta etiqueta determinan el orden en que son
precargados los servlets.

61
Por ejemplo, las siguientes declaraciones de servlets fuerza que primero sea precargado el servlet1 y después
del servlet2:
<servlet>
<servlet-name>servlet1</servlet-name>
<servlet-class>foo.TestUno</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>servlet2</servlet-name>
<servlet-class>foo.TestDos</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
2.2. Parámetros de inicialización de un servlet.
El contenedor web permite asociar datos de manera exclusiva para cada servlet. Estos datos pueden ser
declarados en el elemento <servlet> del fichero descriptor o en la anotación @WebServlet. La interfaz
javax.servlet.ServletConfig ofrece métodos que permiten recuperar los parámetros de inicialización asociados a
un servlet. Cada servlet tiene asociado un objeto de este tipo que puede ser obtenido mediante el método
getServletConfig().
La interfazServletConfig declara métodos para leer los parámetros de inicialización del servlet:
▪ String getInitParameter(String name), retorna el valor del parámetro especificado o null si no existe.
▪ Enumeration getInitParameterNames(), retorna una enumeración con los nombres de los parámetros.
▪ ServletContext getServletContext(), retorna el objeto ServletContext para este servlet.
▪ String getServletName(), retorna el nombre del servlet como está declarado en el archivo de configuración.
Para declarar parámetros de inicialización en el archivo web.xml deben usarse etiquetas <init-param>. En este
ejemplo se declaran parámetros para acceder a una base de datos:
<web-app>
<servlet>
<servlet-name>servlet1</servlet-name>
<servlet-class>servlet1</servlet-class>
<init-param>
<param-name>driverclassname</param-name>
<param-value>sun.jdbc.odbc.JdbcOdbcDriver</param-value>
<description>Clase del controlador</description>
</init-param>
<init-param>
<param-name>dburl</param-name>
<param-value>jdbc:odbc:MySQLODBC</param-value>
<description>Url de la conexión</description>
</init-param>
</servlet>
</web-app>
Las etiquetas para definir un parámetro de inicialización son:
<param-name>, el nombre del parámetro.
<param-value>, el valor del parámetro.
<description>, una descripción opcional para el parámetro.
Para declarar parámetros de inicialización mediante anotaciones se utiliza el código siguiente en la clase del
servlet:
@WebServlet(name = "servlet1", initParams = {
@WebInitParam(name = "driverclassname", value = "sun.jdbc.odbc.JdbcOdbcDriver"),
@WebInitParam(name = "dburl", value = "jdbc:odbc:MySQLODBC")})
public class Servlet1 extends HttpServlet {
……………..
}
Estos parámetros podrían ser leídos en el método init() del servlet y utilizados para crear una conexión a base
de datos.
@Override
public void init(ServletConfig config) throws ServletException {

62
String driver = config.getInitParameter(driverclassname);
String url = config.getInitParameter(dburl);
// …….. resto de código para crear una conexión …………..
}

Importante. No se pueden modificar los parámetros de inicialización desde el código de un servlet.


2.3. El contexto de aplicación de un servlet.
La interfaz javax.servlet.ServletContext ofrece información sobre el entorno en el cual se ejecuta un servlet,
incluyendo la aplicación web y el contenedor de servlets.Cada aplicación web posee un único objeto de
contexto que es accesible en los servlets mediante los métodosHttpServlet.getServletContext()y
HttpServlet.getServletConfig().getServletContext().
2.3.1. Acceso a recursos de la aplicación desde el contexto de los servlets.
La interfaz ServletContext declara dos métodos para acceder a los recursos definidos en la aplicación web:
▪ java.net.URL getResource(String path), retorna la URL del recurso especificado (el path debe comenzar con
/ y no ser una ruta absoluta).
▪ java.io.InputStream getResourceAsStream(String path), retorna un InputStream enlazado al recurso. Es
equivalente a getResource(path).openStream().
Podemos utilizar estos métodos para enviar el contenido de un fichero ubicado en el sitio web como
respuesta al navegador. Por ejemplo, el siguiente servlet recibe como parámetro el nombre de un fichero de
texto, lo busca en la carpeta "/ficheros" del sitio web y retorna su contenido:
package servlet;
import java.io.*;
import java.net.URL;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletFichero", urlPatterns = {"/fichero"})
public class ServletFichero extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String nombreFichero = request.getParameter("nombre");
if (nombreFichero == null) {
throw new ServletException("Falta el nombre del fichero");
}
response.setContentType("text/plain");
ServletContext context = getServletContext();
URL url = context.getResource("/ficheros/" + nombreFichero);
if (url == null) {
throw new ServletException("No se encuentra el fichero");
}
try (OutputStream out = response.getOutputStream();InputStream in = url.openStream()) {
int b;
while ((b = in.read()) >= 0) {
out.write(b);
}
}
}
}
Este código primero recupera un parámetro de solicitud llamado "nombre" y si no existe lanza una excepción.
Se utiliza el parámetro "nombre" para localizar un fichero ubicado a partir de la carpeta raíz de la aplicación. El
método getResource() retornará null si no existe el fichero y se lanzará una excepción, sino se envía el
contenido del fichero al navegador leyendo byte a byte.
Otro método interesante de la interfaz ServletContextes:
▪String getRealPath(String path), retorna la ruta absoluta de disco correspondiente a una ruta relativa del sitio
web.

63
Este método recibe como argumento una ruta relativa a la carpeta raíz del sitio web, y debe comenzar con
una barra /. Este método puede resultar interesante si necesitamos crear un objeto File con la ruta de un
recurso del sitio Web. Por ejemplo, podemos modificar el servlet previo de la siguiente manera:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String nombreFichero = request.getParameter("nombre");
if (nombreFichero == null) {
throw new ServletException("Falta el nombre del fichero");
}
response.setContentType("text/plain");
ServletContext context = getServletContext();
File url = new File(context.getRealPath("/ficheros/" + nombreFichero));
if (!url.exists()) {
throw new ServletException("No se encuentra el fichero");
}
try (OutputStream out = response.getOutputStream();
InputStream in = new FileInputStream(url)) {
byte[] bytes=new byte[(int) url.length()];
in.read(bytes);
out.write(bytes);
}
}
2.3.2. Parámetros de inicialización de la aplicación.
Al igual que se pueden crear parámetros de inicialización asociados a un servlet específico, se pueden crear
parámetros de inicialización disponibles en todos el contexto del sitio web. Estos parámetros de contexto se
declaran en el archivo descriptor de la siguiente manera:
<web-app>
<context-param>
<description>Ubicación de imágenes</description>
<param-name>imagenes</param-name>
<param-value>/imagenes</param-value>
</context-param>
</web-app>
Cada parámetro debe ir encapsulado en un elemento <contex-param> en el primer nivel del nodo raíz <web-
app>. Se puede recuperar el valor de un parámetro de contexto fácilmente mediante el objeto ServletContext:
String rutaImagenes = getServletContext().getInitParameter("imagenes")
También podemos recorrer todos los parámetros de contexto mediante el método
ServletContext.getInitParameterNames().Este método retorna una enumeración con todos los nombres de los
parámetros.
Enumeration<String> parametros= this.getServletContext().getInitParameterNames();
while (parametros.hasMoreElements()) {
String nombre = parametros.nextElement();
String valor = getServletContext().getInitParameter(nombre);
}
2.4. Cabeceras HTTP.
La etiqueta <head> permite definir información de cabeceras de las páginas HTML. Estas cabeceras
informan al navegador web sobre ciertos aspectos o comportamientos de la página. Por ejemplo, una
cabecera típica de un documento HTML puede ser la siguiente:
<!DOCTYPE html>
<html>
<head>
<title>Título del documento</title>
<meta http-equiv="Refresh" content="15">
<meta http-equiv="Expires" content="Tue, 20 Jan 2010 16:25:27 GMT">
<meta name="Generator" content="EditPlus">
<meta name="Author" content="El nombre del autor">
<meta name="Keywords" content="palabra1, palabra2, etc.">
<meta name="Description" content="Una descripción sobre el documento">
</head>

64
<body>
</body>
</html>
En estas cabeceras situadas dentro del elemento <head /> reside información acerca del documento, y
generalmente no se aprecia cuando se navega por él. Estos elementos son opcionales.
La etiqueta <title> permite especificar el título del documento, el cual aparecerá en la barra de título del
navegador.
La etiqueta <meta> permite especificar varios tipos de información. Por ejemplo, la etiqueta <meta http-
equiv="Refresh" content="15"> hace que el navegador vuelva a cargar la página activa al cabo de 15 segundos.
La etiqueta <meta htpp-equiv="Expires" ... > permite forzar la expiración inmediata en la caché del navegador
de la página recibida, lo que provoca que la página sea solicitada de nuevo al servidor cada vez, en lugar de
cargar la copia que ya existe en la máquina del cliente.
La etiqueta <meta name= "Keywords" ... > permite especificar una lista de palabras para ayudar a los
buscadores de Internet como Google, Yahoo, etc. a encontrar nuestras páginas.
Es importante tener en cuenta que podemos usar todos los elementos <meta> que necesitamos, pero sin
repetirlos.
Las cabeceras de tipo <meta> pueden ser obtenidas mediante código en un servlet y algunas de ellas pueden
ser establecidas desde código.
2.4.1. Obteniendo las cabeceras de la petición en un servlet.
La clase HttpServletRequest proporciona métodos para obtener los nombres y valores de las cabeceras de la
petición:
▪ String getHeader(String headerName), para retornar uno de los valores asociado a la cabecera especificada.
▪ Enumeration getHeaderValues(String headerName), para retornar una enumeración de strings con los
valores asociados a la cabecera especificada.
▪ Enumeration getHeaderNames(), para retornar una enumeración con los nombres de las cabeceras.
Como ejemplo, el siguiente código de un servlet recupera todas las cabeceras de la página que lo invoca y los
muestra como respuesta:
@WebServlet(name = "ServletCabeceras", urlPatterns = {"/cabeceras"})
public class ServletCabeceras extends HttpServlet {
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Enumeration<String> cabeceras = request.getHeaderNames();
response.setContentType("text/html");
try (PrintWriter out = response.getWriter()) {
out.println("<html><body>");
while ( cabeceras.hasMoreElements() ) {
String nombre = cabeceras.nextElement(); // el nombre de la cabecera
out.println("<b>" + nombre + ": </b>"); // se escribe el nombre de la cabecera
out.println( request.getHeader(nombre) + "<br>"); // se escribe el valor de la cabecera
}
out.println("</body></html>");
}
}
}
Un posible resultado de invocar este servlet puede ser:

65
Figura 11

2.4.2. Leer y escribir cabeceras de página desde un servlet.


La clase HttpServletResponse proporciona métodos para enviar cabeceras de respuesta:
▪ void setHeader(String name, String value), para enviar una cabecera y su valor.
▪ void setIntHeader(String name, int value), lo mismo para enviar un valor entero.
▪ void setDateHeader(String name, long millisecs), lo mismo para enviar un valor de fecha.
▪ void addHeader/addIntHeader/addDateHeader se usan para asociar múltiples valores con la misma
cabecera.
▪ boolean containsHeader(String name), indica si una cabecera está ya puesta o no.
Seleccionar las cabeceras de respuesta normalmente depende de la selección de códigos de estado. Se pueden
usar para especificar cookies, para suministrar la fecha de modificación (para el caché), para instruir al
navegador sobre la recarga de la página después de un intervalo designado, para decir cuánto tiempo va a
estar el fichero usando conexiones persistentes, y otras muchas tareas.
La forma más general de especificar cabeceras es mediante el método setHeader(). El método setDateHeader()
permite enviar una fecha en milisegundos, y el método setIntHeader() permite enviar un entero. Se usan
addHeader(), addDateHeader(), y addIntHeader() para añadir una nueva cabecera.
Finalmente, HttpServletResponse también suministra unos métodos de conveniencia para especificar cabeceras
comunes:
▪ El método setContentType() selecciona la cabecera "Content-Type", y se usa en la mayoría de los Servlets.
▪ El método setContentLength() selecciona la cabecera "Content-Length", útil si el navegador soporta
conexiones HTTP persistentes (keep-alive).
▪ El método addCookie() selecciona un cookie.
▪ El método sendRedirect() selecciona la cabecera "Location" así como se selecciona el código de estado 302.
La tabla siguiente describe las cabeceras de respuesta más comunes y sus significados:
Cabecera Interpretación/Propósito
Allow ¿Qué métodos de petición (GET, POST, etc.) soporta el servidor?
Content-Encoding ¿Qué método se utilizó para codificar el documento? Necesitamos decodificarlo
para obtener el tipo especificado por la cabecera Content-Type.
Content-Length ¿Cuántos bytes han sido enviados? Esta información es sólo necesaria si el
navegador está usando conexiones persistentes. Nuestro servlet debería escribir el
documento en un ByteArrayOutputStream, preguntar su tamaño cuando se haya
terminado, ponerlo en el campo Content-Length, luego enviar el contenido con
byteArrayStream.writeTo(response.getOutputStream().
Content-Type ¿Cuál es el tipo MIME del siguiente documento? Por defecto en los servlets es
text/plain, pero normalmente especifican explícitamente text/html.
Date ¿Cuál es la hora actual (en GMT)? Usamos el método setDateHeader() para
especificar esta cabecera.

66
Expires ¿En qué momento debería considerarse el documento como caducado y no se
pondrá más en el caché?
Last-Modified ¿Cuándo se modificó el documento por última vez? El cliente puede suministrar
una fecha mediante la cabecera de petición If-Modified-Since. Ésta es tratada como
un GET condicional, donde sólo se devuelven documentos si la fecha Last-
Modified es posterior que la fecha especificada. De otra forma se devuelve una
línea de estado 304 (Not Modified). De nuevo se usa el método setDateHeader()
para especificar esta cabecera.
Location ¿Dónde debería ir el cliente para obtener el documento? Se selecciona
indirectamente con un código de estado 302 (SC_FOUND), mediante el método
sendRedirect() de HttpServletResponse.
Refresh ¿Cuándo (en segundos) debería pedir el navegador una página actualizada? En
lugar de recargar la página actual, podemos cargar otra con setHeader("Refresh",
"5; URL=http://host/path").
Referer Contiene la URL de la página que realizó la solicitud del recurso actual.
Server ¿Qué servidor soy? Los servlets normalmente no usan esto; lo hace el propio
servidor.
Set-Cookie Especifica una cookie asociada con la página. Se debe usar el método addCookie()
de HttpServletResponse.
WWW-Authenticate ¿Qué tipo de autorización y domino debería suministrar el cliente en su cabecera
Authorization? Esta cabecera es necesaria en respuestas que tienen una línea de
estado 401 (CS_UNAUTHORIZED). Por ejemplo response.setHeader("WWW-
Authenticate", "BASIC realm=\"executives\"").

2.5. Tipo de contenido de la respuesta.


El método setContentType() de la clase HttpServletResponse permite enviar la cabecera "Content-Type" al
navegador cliente para definir el tipo de contenido de la respuesta generada desde el servlet. Los navegadores
y otros programas cliente pueden utilizar esta cabecera para determinar la correcta visualización de un
documento.
2.5.1. Tipos MIME.
Un navegador puede administrar varios tipos de archivos, incluyendo documentos PDF, documentos Word,
animaciones de Flash, etc.; y mostrarlos directamente en el navegador. Para ello debemos transmitir al
navegador el tipo MIME apropiado que describa el contenido del stream de respuesta. El tipo MIME es una
descripción estandarizada de contenidos, independiente de la plataforma, similar al de las extensiones de
archivos bajo Windows. Los tipos MIME consisten en dos componentes de información separados por una
barra:
• El primer componente es el tipo de medio (o formato). En esencia, el valor es text para cualquier clase de
texto, audio para todos los documentos de audio, image para imágenes, video para cualquier tipo de imagen
animada, y application para datos binarios específicos de la aplicación.
• El segundo componente es el tipo de documento específico. El valor por defecto es text/html e indica una
página web formada por texto.
En la siguiente tabla se describen los tipos MIME más habituales:
Tipo MIME Descripción
text/html HTML (Protocolo de transferencia de Hipertexto)
text/xml XML (Lenguaje Extensible de MarkUp)
text/vnd.wap.wml WAP (Protocolo para Aplicaciones Inalámbricas). Páginas para mostrar en
dispositivos móviles incluyendo teléfonos celulares.
text/plain Texto normal. La información de Content-Encoding determina el código de
página a ser utilizado para la interpretación del archivo de texto.
text/richtext Un archivo de texto en formato enriquecido
application/pdf Documento PDF que puede visualizarse con Adobe Acrobat Reader.
application/octet-stream Estrictamente datos binarios, incluyendo aplicaciones que se ofrecen para
descarga
application/msword Documento de Microsoft Word para Windows

67
application/rtf Un archivo de texto en formato RTF
application/zip Archivo ZIP
application/vnd.ms-excel Archivo de Microsoft Excel
application/excel Archivo de Microsoft Excel
audio/mpeg Archivo MP3
image/jpeg Imágenes JPEG
image/png Imágenes en formato PNG
image/bmp Imágenes en formato Windows BMP
La cabecera Content-Disposition se complementa con la cabecera Content-Type para forzar la descarga del
documento con un nombre. Por ejemplo, si queremos asignar un nombre al fichero, solo tenemos que enviar
la siguiente cabecera:
response.setHeader("Content-Disposition","attachment; filename=archivo.extension");
Esta cabecera provoca normalmente que aparezca un cuadro de diálogo solicitándonos qué hacer con el
fichero.
2.5.2. Asociación de extensiones con tipos MIME.
Podemos asociar extensiones de archivos con los tipos MIME en el archivo descriptor de la aplicación Web
(web.xml). En el siguiente ejemplo se asocia la extensión mpg con un documento de vídeo:
<web-app>
<mime-mapping>
<extension>mpg</extension>
<mime-type>video/mpeg</mime-type>
</mime-mapping>
</web-app>
2.5.3. Enviar archivos binarios al cliente.
Como alternativa a getWriter(), el método getOutputSteam() de HttpServletResponse retorna un objeto
ServletOutputStream que podemos usar para enviar un archivo binario al cliente.
Por ejemplo, el siguiente método doGet() de un servlet muestra cómo enviar un fichero de tipo JAR al
navegador cliente:
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("application/jar"); // se pone el tipo de contenido MIME
File f = new File("test.jar");
try (OutputStream out = response.getOutputStream();
InputStream in = new FileInputStream(f)) {
byte[] bytes=new byte[(int) f.length()];
in.read(bytes);
out.write(bytes);
}
}
Si, en otro ejemplo, quisiésemos enviar un archivo de Word deberíamos usar las siguientes cabeceras:
res.setContentType("application/msword");
res.addHeader("Content-Disposition", "attachment; filename=midocumento.doc");

3. Métodos de solicitud en servlets


Como se ha visto, los servlets permiten capturar solicitudes de URIs que casen con sus patrones URL y son
capaces de generar una respuesta dinámica. En este apartado veremos las diversas técnicas que se usan en los
navegadores para realizar solicitudes y cómo un servlet puede responder a ellas.
3.1. Procesos HTTP en los servlets.
Para cada solicitud con un método de llamada HTTP desde el cliente (GET, POST, HEAD, ...), la clase
HttpServlet declara un método respectivo: doGet(), doHead(), doPost(), doPut(), doDelete(), doOptions(), y
doTrace().
Para entender el orden de ejecución de estos métodos, la secuencia de eventos que se producen cuando se
procesa un requerimiento en la clase HttpServlet es la siguiente:
1) El contenedor de servlet llama al método service() de la clase servlet.
2) Este método service() llama al método service() de la clase baseHttpServlet.

68
3) El método service() de la clase base analiza el requerimiento y determina el método de llamada.
Dependiendo de ello invoca el método do_() correspondiente.
Si se rescribe el código del método service() se pierde esta funcionalidad base, y el programador deberá
implementar su propio código de proceso.
3.2. Solicitudes GET HTTP.
Desde un navegador se pueden realizar solicitudes con el método GET HTTP de varias formas:
• Escribiendo directamente una URI en la barra de direcciones del navegador.
• Pinchando un enlace creado con la etiqueta <a href="">.
• Incrustando una imagen con la etiqueta <img src= "" />. Debemos tener en cuenta que cuando el
navegador carga la página, si se encuentra con una etiqueta <img> realiza una solicitud GET sobre la ruta
especificada en su atributo src.
• Usando código JavaScript. Veremos ejemplos de ello con otros métodos de solicitud.
Un ejemplo de servlet que procesa solicitudes GET se ha analizado previamente con la aplicación
"WebEjemplo" y el servlet "ServletSaludo".
3.2.1. Solicitudes GET con paso de parámetros.
Las solicitudes GET HTTP permiten pasar datos adicionales en la parte de la URI denominada cadena de
consulta. La cadena de consulta es la última parte de una URI y tiene la siguiente sintaxis.
?clave=valor&clave=valor&clave=valor
La cadena de consulta consta de un signo de interrogación ? y de una lista de parámetros separados por un &.
Cada parámetro consta de una clave y un valor opcional separados por un signo igual. La clave del parámetro
será el nombre por el cual se le identifique. Si no se especifica el valor se recibirá un string vacío.
Una cuestión importante sobre los parámetros de la cadena de conexión es que una URI no admite espacios
en blanco, por ello podemos usar la codificación %20 en su lugar. Por ejemplo, la siguiente consulta permite
pasar el nombre y apellidos de una persona en el parámetro nombre:
?nombre=Pedro%20Salgueiro
Una cuestión importante es que varios parámetros dentro de la misma cadena de consulta pueden tener la
misma clave, y que ésta es sensible a mayúsculas y minúsculas.
3.2.2. Obteniendo los parámetros GET desde un servlet.
Se utiliza el objeto HttpServletRequestpara obtener los parámetros de consulta enviados con la URI. Esta clase
proporciona los siguientes métodos:
▪ String getParameter(String clave), permite recuperar el valor asociado a un parámetro especificado. Si no
existe ningún parámetro con la clave especificada retorna el valor null.
▪ String[] getParameterValues(String clave), permite recuperar todos los valores asociados a un parámetro
especificado (útil para listas con varios elementos seleccionados). Si no existe ningún parámetro con la clave
especificada retorna el valor null.
▪ Enumeration<String> getParameterNames(), permite obtener una enumeración con los nombres de los
parámetros.
▪ Map<String, String[]> getParameterMap(), retorna un mapa donde cada elemento es un parámetro con su
clave y lista de valores.
Para ver cómo trabajan estos métodos supongamos creado el siguiente servlet en la aplicación
"WebAplicacion":
package servlet;
import java.io.*;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletCapturaParametros", urlPatterns = {"/captura"})
public class ServletCapturaParametros extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
Enumeration<String> claves = request.getParameterNames();
while (claves.hasMoreElements()) {

69
String clave = claves.nextElement();
out.println(clave + "=" + request.getParameter(clave));
}
}
}
}
El servlet ServletCapturaParametros sólo ha reescrito el método doGet() y por ello cualquier solicitud a este
servlet que no utilice el verbo GET provocará una página de error.
Este servlet captura todos los parámetros de la cadena de consulta e itera sobre ellos para mostrar la clave y el
valor. Primero se establece el tipo de contenido que se enviará al navegador:
response.setContentType("text/plain;charset=UTF-8");
El tipo MIME "text/plain" le indica al navegador que recibirá un documento de texto sin formato. Mediante
un bucle se itera sobre las claves que devuelve la enumeración request.getParameterNames(). En cada iteración
se escribe en una línea el nombre de la clave, y el valor asociado con ella. Se obtiene este valor usando el
método request.getParameter().
Por ejemplo, si ejecutamos este servlet con la URI:
http://localhost:8084/WebAplicacion/captura?nombre=Pedro%20Salgueiro&edad&pais=España
Obtendremos el siguiente resultado en el navegador:
nombre=Pedro Salgueiro
edad=
pais=España
Al no pasar ningún valor en el parámetro edad se obtiene un string vacío. Hemos dicho que podemos poner
varios parámetros con el mismo nombre, pero si ejecutamos este servlet con la URI:
http://localhost:8084/WebAplicacion/captura?aficion=Lectura&aficion=Bricolage
Obtendremos el siguiente resultado en el navegador:
aficion=Lectura
Aunque hemos pasado con parámetros con el nombre aficion, el método getParameter() sólo permite
recuperar el valor del primer parámetro con ese nombre. Cuando sepamos que existen parámetros con el
mismo nombre podemos utilizar el método getParameterValues() en sustitución de getParameter().
Podemos modificar el servlet de la siguiente manera:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(request.getParameterMap());
Enumeration<String> claves = request.getParameterNames();
while (claves.hasMoreElements()) {
String clave = claves.nextElement();
out.println(clave + "=" + Arrays.asList(request.getParameterValues(clave)));
}
}
}
Para obtener ahora el resultado:
aficion=[Lectura, Bricolage]
3.3. Solicitudes POST HTTP.
Desde un navegador se pueden realizar solicitudes con el método POST HTTP de varias formas:
• Posteando datos mediante un formulario.
• Usando código JavaScript. Veremos ejemplos de ello con otros métodos de solicitud.
El objetivo del método POST HTTP es realizar solicitudes donde se postean datos. Pero al contrario que el
método GET, los datos posteados con POST no forman parte de la URI solicitada sino que se embeben en al
cuerpo de la solicitud y por tanto se ocultan para el usuario. Esto es importante cuando se postean
contraseñas.
Para que un formulario postee datos debe incluir al menos un control HTML que tenga asignado su atributo
name a algún valor. Se creará un parámetro por cada control HTML, utilizando el nombre del control como
clave del parámetro, y el contenido del control como valor del parámetro.

70
3.3.1. Un formulario que postea datos simples.
A continuación agregaremos al proyecto de ejemplo "WebAplicacion" una nueva página llamada "login.html"
donde incluiremos un formulario para solicitar un nombre y contraseña de usuario:
Figura 12

En la etiqueta <form> se ha especificado el método POST y una acción llamada registro. Esta acción se
corresponderá con el patrón URL de un servlet llamado ServletRegistro, definido de la siguiente manera:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletRegistro", urlPatterns = {"/registro"})
public class ServletRegistro extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("Tipo de contenido: " + request.getContentType());
out.println("Contenido del cuerpo: " + request.getReader().readLine());
}
}
}
Inicialmente este servlet procesará las solicitudes POST devolviendo un documento de texto sin formato.
Como ejemplo práctico se utiliza el método request.getContentType() para obtener el tipo de contenido del
cuerpo recibido con la solicitud. Para recuperar el contenido del propio cuerpo disponemos de los métodos
request.getReader() y request.getInputStream(); estos dos métodos retornan un stream de lectura. En este caso
se utiliza request.getReader() para leer una línea de la cabecera, la que contiene los datos posteados.

71
Figura 13

Podemos comprobar que el tipo MIME para contenido de formulario es "application/x-www-form-urlencoded",


y que el formato de los parámetros de formulario es el mismo que el de los parámetros de las cadenas de
consulta.
Aunque recuperar el contenido del cuerpo de la solicitud puede resultar útil, lo habitual es recuperar los
parámetros de formulario de la misma manera que se recuperan los parámetros de las cadenas de consulta:
usando los método getParameter(), getParameterValues() y getParameterMap() del objeto HttpServletRequest.
Modificaremos el servlet para que sólo valide a un usuario si coincide su nombre con su contraseña.
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String user = request.getParameter("user");
String password = request.getParameter("password");
if (user==null || password==null || user.equest || user.isEmpty() || password.isEmpty())
throw new ServletException("Falta el usuario o la contraseña");
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
if (user.equals(password))
out.println("El usuario se ha validado");
else
out.println("En usuario no ha sido validado");
}
}
Primero se recuperan los dos parámetros, pero se comprueba que existan ambos. Recordemos que el método
getParameter() retorna el valor null si el parámetro especificado no existe. También se comprueba que
contengan algún valor. Si falla uno de los parámetros se lanza una excepción, la cual provocará una página de
error en el navegador.
Tras comparar los datos se devuelve un texto al navegador.
3.3.2. Un formulario que incluye botones de radio y cajas de selección.
En el siguiente ejemplo veremos cómo postean datos los botones de radio (radio button) y las cajas de
selección (check boxes). Para ello incluiremos una nueva página "menu.html" en el proyecto "WebAplicacion".
<!DOCTYPE html>
<html>
<head>
<title>Selección de menú</title>
</head>
<body>
<form method="POST" action="menu">
<table>
<tr>
<th>Plato:</th><th>Condimentos:</th>

72
</tr>
<tr>
<td>
<input type="radio" name="plato" value="Paella" checked />Paella<br>
<input type="radio" name="plato" value="Tortilla" />Tortilla<br>
<input type="radio" name="plato" value="Paella" />Ensalada
</td>
<td>
<input type="checkbox" name="condimento" value="sal" />Sal<br>
<input type="checkbox" name="condimento" value="pimienta" />Pimienta<br>
<input type="checkbox" name="condimento" value="azafran" />Azafrán
</td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="anotar" /></td>
</tr>
</table>
<form>
</body>
</html>
Esta página permitirá seleccionar un plato y sólo uno, y qué condimentos deben incluir.
Se han utilizado botones de radio para especificar los platos, ya que cuando forman un grupo permiten
realizar selecciones excluyentes, de forma que sólo uno de los botones esté seleccionado a la vez. Podemos
establecer la selección de un botón de radio incluyendo el atributo checked. Para formar un grupo de botones
de radio hay que asignarles el mismo nombre, pero teniendo en cuenta que sólo se posteará el valor del botón
seleccionado, por tanto podremos recuperarlo con el método getParameter(). El valor del botón queda
establecido en su atributo value.
Se han utilizado cajas de selección para seleccionar los condimentos. En este caso a todas las cajas se les ha
puesto el mismo nombre, pero como las cajas de selección no son excluyentes se podrán recibir varios
parámetros con el mismo nombre, por tanto habrá que recuperarlos con el método getParameterValues(). El
valor del cuadro de selección queda establecido en su atributo value.
Figura 14

El servlet ServletMenu recibirá los datos posteados y mostrará un mensaje como respuesta:
package servlet;
import java.io.*;
import java.util.Arrays;

73
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletMenu", urlPatterns = {"/menu"})
public class ServletMenu extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String plato = request.getParameter("plato");
String[] condimentos=request.getParameterValues("condimento");
String strCondimentos = condimentos==null? "nada" : Arrays.asList(condimentos).toString();
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("Se ha anotado: " + plato);
out.println("Debe estar condimentado con: " + strCondimentos);
}
}
}
Para este ejemplo no se han realizado validaciones, simplemente se recuperan los datos de cada parámetro.
Sin embargo hay que tener en cuenta que si no existe un parámetro el método getParameterValues() retorna el
valor null.
3.3.3. Un formulario que postea ficheros.
La especificación HTML5 incluye ahora un elemento <input type="file" />, el cual permite seleccionar uno o
varios ficheros del lado cliente y subirlos al servidor.El elemento <input type= "file" />, por defecto,
simplemente postea el nombre o nombres de los ficheros seleccionados. Estos nombres se pueden recuperar
en el lado servidor de la misma forma que se recupera el valor de cualquier otro control de formulario.
Pero si queremos que el contenido de los ficheros seleccionados se reciba en el servidor debemos modificar el
elemento <form /> para que incluya el atributo enctype con el valor"multipart/form-data".
Por ejemplo, vamos a añadir una nueva página "documento.html" en el proyecto "WebAplicacion":
<!DOCTYPE html>
<html>
<head>
<title>Subida de documentos</title>
</head>
<body>
<form method="POST" action="documento" enctype="multipart/form-data">
Un documento <input type="file" name="doc" />
<br/>
<input type="submit" value="Subir" />
</form>
</body>
</html>
Por defecto, el elemento <input type="file" /> solo permite subir un único fichero. Crearemos ahora el servlet
"ServletDocumento", que será el encargado de procesar el fichero subido.
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
import javax.servlet.http.Part;
@MultipartConfig(location = "E:/temp", maxFileSize = Integer.MAX_VALUE)
@WebServlet(name = "ServletDocumento", urlPatterns = {"/documento"})
public class ServletDocumento extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
try {

74
// Se recupera el objeto Part que representa el fichero subido
Part fichero = request.getPart("doc");
// Se guarda el fichero en la ruta especificada en la anotación MultipartConfig
fichero.write(fichero.getName());
out.println("Se ha subido el fichero: " + fichero.getSubmittedFileName());
out.println("Su contenido es: ");
// Se lee el contenido del fichero y se muestra
byte[] contenido = new byte[(int) fichero.getSize()];
fichero.getInputStream().read(contenido);
out.println(new String(contenido));
} catch (Exception ex) {
out.println(ex);
}
}
}
}
Hay que destacar en este código el uso de la anotación javax.servlet.annotation.MultipartConfig. Esta anotación
habilita al servlet para recibir ficheros posteados, los cuales pude recuperar mediante el método getPart() o
getParts(). La anotación MultipartConfig permite especificar, entre otros, el tamaño máximo permitido para
poder recuperar un fichero, y la carpeta por defecto para grabar los ficheros en el servidor.
Como alternativa a la anotación MultipartConfig podemos configurar estas opciones en el archivo descriptor,
en el elemento que registra al servlet:
<servlet>
<servlet-name>ServletDocumento</servlet-name>
<servlet-class>servlet.ServletDocumento</servlet-class>
<multipart-config>
<location>E:/temp</location>
<max-file-size>2147483647</max-file-size>
</multipart-config>
</servlet>
Como se ha recibido un único fichero se utiliza getPart() especificando el nombre del parámetro. Un objeto
Part representa el fichero subido y proporciona métodos para recuperar su contenido y grabarlo directamente
en una carpeta del servidor.
Figura 15

3.4. Otras solicitudes HTTP.


Además de GET y POST es posible realizar solicitudes usando otros verbos como PUT, DELETE, TRACE, etc.

75
Estos otros métodos de solicitud no se utilizan para navegar entre páginas, tal como si lo hacen GET y PUT.
Por eso los navegadores Web y el estándar HTML sólo soportan directamente GET y PUT. Los demás
métodos requieren realizar una solicitud desde instrucciones de código que puedan recuperar la respuesta.
La siguiente tabla resume el uso de los demás métodos HTTP:
Método Significado
HEAD Se utiliza para realizar solicitudes como GET, pero sin que el servidor devuelva el cuerpo del
mensaje. Es decir, sólo se devuelve la información de cabecera.
PUT Se utiliza para enviarun recurso identificado en la URI desde el cliente hacia el servidor.
OPTIONS Se utiliza para pedir información sobre las características de comunicación proporcionadas
por el servidor. Le permite al cliente negociar los parámetros de comunicación.
TRACE Inicia un ciclo de mensajes de petición. Se usa para depuración y permite al cliente ver lo que
el servidor recibe en el otro lado.
DELETE Se usa para solicitar al servidor que borre un recurso identificado en la URI.
CONNECT Este método se reserva para uso con proxys. Permitirá que un proxy pueda dinámicamente
convertirse en un túnel. Por ejemplo para comunicaciones con SSL.
El estándar HTML5 incluye un objeto denominado XMLHttpRequest, que podemos utilizar en el código
JavaScript para realizar solicitudes especificando el método HTTP.
Para probar el uso de este objeto primero crearemos un servlet llamado "ServletTest" donde iremos
reescribiendo los métodos do_(). Inicialmente reescribiremos el método doGet() para que retorne un mensaje
de texto plano:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletTest", urlPatterns = {"/test/*"})
public class ServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("Solicitud GET recibida");
}
}
}
3.4.1. Cómo hacer solicitudes con «XMLHttpRequest».
El objeto XMLHttpRequest permite realizar solicitudes HTTP mediante código y permite retornar texto, JSON,
y datos XML.
Se usa el siguiente código JavaScript para crear un objeto XMLHttpRequest:
var request = new XMLHttpRequest();
Podemos usar este objeto para iniciar una solicitud remota. Para hacer esto se deben asignar propiedades que
especifiquen la URI y el tipo de dato que será solicitado; esto permitirá que el servidor que reciba la solicitud
pueda reconocer los datos que le son enviados. El siguiente código muestra como solicitar el servlet
"ServletTest" con el método GET:
request.open( "GET", "test" );
request.send()
El método open() define el método HTTP y la uri que será solicitada (en este ejemplo se usa una URI relativa,
ya que suponemos que la página y el servlets se están ejecutando dentro de la misma aplicación web), y la
indicación de que la solicitud se debe realizar asíncronamente. Para transmitir la solicitud se invoca el método
send(), el cual admite opcionalmente el contenido del cuerpo de la solicitud. Las llamadas al método send()
son asíncronas por defecto, por ello debemos gestionar el evento readystatechange para recuperar la
respuesta:
request.onreadystatechange = function() {
// Si se ha recibido una respuesta el código será 4
if (request.readyState==4) {

76
alert(request.responseText);
}
}
La propiedad readyState que puede tener uno de los siguientes valores:
0: El objeto XMLHttpRequest no ha sido abierto.
1: El objeto XMLHttpRequest ha sido abierto.
2: El objeto XMLHttpRequest ha enviado una solicitud.
3: El objeto XMLHttpRequest ha empezado a recibir una respuesta.
4: El objeto XMLHttpRequest ha finalizado de recibir una respuesta.
La respuesta de la solicitud se puede recuperar con las propiedades responseText o responseXml. Podemos
también gestionar los errores de la comunicación:
request.onerror = function (error) {
alert(erro.message);
}
Por tanto si creamos ahora una página "otrosMetodos.html" con el siguiente contenido:
<!DOCTYPE html>
<html>
<head>
<title>Solicitud por código</title>
</head>
<body>
<div id="contenido"></div>
<script type="text/javascript">
var request = new XMLHttpRequest();
request.open("GET","test",true );
request.send();
request.onreadystatechange = function() {
if (request.readyState==4) {
document.getElementById("contenido").innerHTML=request.responseText;
}
}
request.onerror = function (error) {
alert(erro.message);
}
</script>
</body>
</html>
Y si abrimos la página en un navegador el resultado será el siguiente:
Figura 16

3.4.2. Solicitudes HEAD HTTP.


Una solicitud HEAD debería retorna toda la información que ofrecería la misma solicitud con GET pero sin el
contenido.
Primero añadiremos el método doHead() al servlet ServletTest:
@Override
protected void doHead(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
String contenido="Solicitud GET recibida";

77
response.setContentLength(contenido.length());
}
El método define la cabecera con el tipo de contenido y la longitud del mismo. Ahora en la página HTML
recuperaremos todas estas cabeceras:
<script type="text/javascript">
var request = new XMLHttpRequest();
request.open("HEAD","test",true );
request.send();
request.onreadystatechange = function() {
// Si se ha recibido una respuesta
if (request.readyState==4) {
document.getElementById("contenido").innerHTML = request.getAllResponseHeaders();
}
}
</script>
Y si abrimos la página en un navegador el resultado será el siguiente:
Figura 17

3.4.3. Solicitudes PUT HTTP.


Una solicitud PUT debería utilizarse para enviar un recurso al servidor. El contenido del recurso se envía
normalmente en el cuerpo de la solicitud. Un uso habitual de este método era para subir ficheros al servidor.
Como ejemplo añadiremos el método doPut() al servlet ServletTest, de forma que debe recuperar el contenido
del cuerpo de la solicitud y retornarlo como respuesta:
protected void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String respuesta = "Se ha recibido: ";
if (request.getContentType().startsWith("text/plain")) {
byte[] cuerpo = new byte[request.getContentLength()];
request.getInputStream().read(cuerpo);
respuesta += new String(cuerpo);
} else {
respuesta = "Tipo de contenido incorrecto";
}
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(respuesta);
}
}
En este caso el método espera recibir un contenido de texto plano y si no es así retorna un mensaje de error.
Ahora en la página HTML enviamos un texto en el cuerpo de la solicitud:
<script type="text/javascript">
var request = new XMLHttpRequest();
request.open("PUT","test",true );
request.setRequestHeader("Content-Type", "text/plain");
request.send("Un texto de ejemplo");
request.onreadystatechange = function() {
if (request.readyState==4) {
document.getElementById("contenido").innerHTML=request.responseText;

78
}
}
A destacar la instrucción request.setRequestHeader("Content-Type", "text/plain"), que especifica el tipo de
contenido que recibirá el servlet.
3.4.4. Solicitudes DELETE HTTP.
Por su parte, el método DELETE debería ser utilizado para solicitar eliminar un recurso del servidor. La
identificación del recurso suele enviarse parte de la información de ruta de la URI, aunque también se puede
utilizar el cuerpo de la solicitud.
Como ejemplo añadiremos el método doDelete() al servlet ServletTest, de forma que debe recuperar un
identificador concatenado con la ruta y retornarlo como respuesta:
protected void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String id = request.getPathInfo();
String respuesta = "Falta el ID";
if (id != null) {
respuesta = "Se debe eliminar el recurso de ID " + id.replace("/", "#");
}
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(respuesta);
}
}
Ahora en la página HTML concatenaremos una ID en la URI:
<script type="text/javascript">
var request = new XMLHttpRequest();
request.open("DELETE","test/23",true );
request.send();
request.onreadystatechange = function() {
if (request.readyState==4) {
document.getElementById("contenido").innerHTML=request.responseText;
}
}
</script>
Y si abrimos la página en un navegador el resultado será el siguiente:
Figura 18

3.4.5. Solicitudes OPTIONS HTTP.


El método OPTIONS debe ser utilizado para que el servidor informe de qué métodos HTTP soporta y
retornarlos en una cabecera de respuesta denominada "Allow". El método doOptions() de los servlets ya
implementa esta funcionalidad, así que sólo reescribiremos el código JavaScript:
<script type="text/javascript">
var request = new XMLHttpRequest();
request.open("OPTIONS","test",true );
request.send();
request.onreadystatechange = function() {
if (request.readyState==4) {
document.getElementById("contenido").innerHTML=request.getResponseHeader("Allow");
}

79
}
</script>
Y si abrimos la página en un navegador el resultado será el siguiente:
Figura 19

4. Redirigiendo la respuesta en servlets.


Desde un servlet se dispone de dos técnicas para redirigir la respuesta a otro recurso, en vez de generar la
respuesta como instrucciones HTML: usando el método sendRedirect() o usando un objeto
RequestDispatcher.
El contenedor web proporciona varios mecanismos para gestionar errores y excepciones que se producen
durante la ejecución de los servlets y páginas JSP. Estos mecanismos están basados también en redirecciones
internas del servidor.
4.1. Redirección del navegador.
Un uso habitual de un servlet es procesar datos enviados con la solicitud y tomar decisiones de redirección a
otros recursos del mismo sitio u otro sitio web distinto. La primera técnica de redirección consiste en enviarle
al navegador cliente como respuesta la cabecera de redirección "Location". Esta técnica se puede simplificar
mediante el uso del método sendRedirect(String) de la clase HttpServletResponse.
Por ejemplo, supongamos que en una aplicación web hemos creado una carpeta llamada "informes" con varias
páginas que muestran informes. En la página index.html se ponen enlaces para ver cada informe, tal como se
muestra en la siguiente imagen:
Figura 20

Todos los enlaces comienzan por la palabra informe seguida de una barra y el tipo de informe. Se crea un
servlet que capturará estos enlaces con el patrón "/informe/*":
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletInformes", urlPatterns = {"/informe/*"})
public class ServletInformes extends HttpServlet {

80
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String tipoInforme = request.getPathInfo();
String rutaCompleta = "informes" + tipoInforme + ".jsp";
response.sendRedirect(rutaCompleta);
}
}
El servlet usará el último trozo de información de ruta para determinar la página a la que tiene que redirigir al
navegador. Por ejemplo si ejecutamos la aplicación y pulsamos en el enlace "informe/gestion" se asignarán lo
siguientes valores en el servlet:
tipoInforme = "/gestion"
rutaCompleta = "informes/gestion.jsp"
El método sendRedirect() recibe por tanto
una ruta relativa al sitio web, lo cual puede parecer correcto. Pero
podremos comprobar que el enlace no funciona, y en la barra del navegador se mostrará algo como:
http://localhost:8084/WebAplicacion/informe/informes/....../informes/gestion.jsp………..jsp
Hay que tener en cuenta que al aplicarse una ruta relativa ésta se establece respecto a la ubicación virtual del
propio servlet. Para este ejemplo, a todos los efectos es como si el servlet estuviese ubicado en la carpeta
"informe/" y se correspondiese con un archivo llamado "gestion", y por tanto la redirección se solicita para
"informe/informes/gestion.jsp". Pero esta nueva solicitud del navegador es capturada otra vez por el servlet que
vuelve a generar otra ruta de redirección y así sucesivamente.
Para evitar este tipo de problemas de redirección es mejor crear siempre rutas relativas a la carpeta raíz de la
aplicación web. Esto se consigue comenzando la ruta por una barra / seguida de la ruta de contexto:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String tipoInforme = request.getPathInfo();
String ruta = request.getContextPath() + "/informes" + tipoInforme + ".jsp";
response.sendRedirect(ruta);
}
Ahora ya tenemos una ruta de redirección que no es relativa a la ubicación virtual del servlet.
Nota.Si invocamos sendRedirect() después de enviar una página dinámica al cliente se generará una
excepción del tipo java.lang.IllegalStateException.
4.2. Coordinando servlets usando «RequestDispatcher».
El uso del método sendRedirect() puede ser adecuado en aquellos casos en los que queremos informar al
navegador cliente de que lo queremos redireccionar. Pero esto obliga al navegador a que tenga que realizar
dos solicitudes para acceder a un recurso final.
4.2.1. Redirecciones internas en el servidor.
Como alternativa a las redirecciones externas, podemos aplicar un redirección interna en el propio servidor,
sin que el navegador cliente se entere. El navegador solicita un recurso y se le devuelve otro recurso
redireccionado internamente, pero a todos los efectos el navegador tiene la sensación de que se le ha devuelto
el recurso solicitado originalmente.
Las redirecciones internas se realizan usando un objeto de tipo javax.servlet.RequestDispatcher. Para obtener
un objeto de esta interfaz disponemos de los métodos:
▪ ServletRequest.getRequestDispatcher(String ruta), que admite rutas relativas al recurso.
▪ ServletContext.getRequestDispatcher(String ruta), que no admite rutas relativas (ruta debe empezar con "/")
Estos dos métodos retornan una instancia que queda asociada a la ruta del recurso. Y ahora disponemos de
dos métodos en el objeto RequestDispatcher:
▪ void forward(ServletRequest request, ServletResponse response)
Redirige la respuesta al recurso indicado en la ruta. Este redireccionamiento se produce en el servidor, sin
que el navegador cliente tenga constancia de ello.
▪ void include(ServletRequest request, ServletResponse response)
Permite incluir el recurso invocado en la ruta como parte de la respuesta actual. Todos los cambios que se
hagan en la cabecera o códigos de estado desde el recurso incluido serán ignorados.
Como ejemplo modificaremos la aplicación previa para realizar redirecciones internas:
package servlet;
import java.io.*;

81
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletInformes", urlPatterns = {"/informe/*"})
public class ServletInformes extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String tipoInforme = request.getPathInfo();
String rutaRaiz = "/informes" + tipoInforme + ".jsp";
RequestDispatcher despachador = request.getRequestDispatcher(rutaRaiz);
despachador.forward(request, response);
}
}
A destacar que ya no necesitamos concatenar la ruta de contexto. Al comenzar la ruta por / el método
getRequestDispatcher() considera ya la ruta como absoluta respecto a la carpeta raíz de la aplicación.
El servlet incluido o redireccionado puede acceder a la información de la petición original a través de diversos
atributos del contexto de la solicitud. Los nombres de los atributos dependen de si se ha invocado el método
forward() o include(), y los mismos pueden ser:
Nombres de atributos para include() Nombres de atributos para forward()
javax.servlet.include.request_uri javax.servlet.forward.request_uri
javax.servlet.include.context_path javax.servlet.forward.context_path
javax.servlet.include.servlet_path javax.servlet.forward.servlet_path
javax.servlet.include.path_info javax.servlet.forward.path_info
javax.servlet.include.query_string javax.servlet.forward.query_string
Los valores de estos atributos son los mismos que los métodos: getRequestURI(), getContextPath(),
getServletPath(), getPathInfo() y getQueryString de la interfaz HttpServletRequest. A estos atributos se acceden
como los atributos regulares, a través del método request.getAttribute(nombreAtributo).
4.2.2. Diferencias entre forward e include.
Para entender la diferencia ente usar el método forward() y el método include() supongamos dos servlets:
Servlet1 y Servlet2. Desde Servlet1 se redirigirá internamente a Servlet2.
El código de Servlet1 es el siguiente:
package servlet;
import java.io.*;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "Servlet1", urlPatterns = {"/servlet1"})
public class Servlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Se añade contenido general al principio de la respuesta
response.setContentType("text/plain");
try (PrintWriter out = response.getWriter()) {
out.println("Inicio de servlet 1");
// Se redirige al informe concreto, el cual finaliza la respuesta
RequestDispatcher despachador = request.getRequestDispatcher("/servlet2");
despachador.forward(request, response);
// Se añade contenido final a la respuesta
out.println("Final de servlet 1");
}
}
}
Y el código de Servlet2 es el siguiente:
package servlet;
import java.io.*;

82
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "Servlet2", urlPatterns = {"/servlet2"})
public class Servlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.println("Cuerpo de servlet 2");
}
}
Si invocamos Servlet1 obtendremos como resultado en el navegador:
Cuerpo de servlet 2
Es decir, la respuesta se completa en el servlet redirigido mediante forward(), ignorando el contenido previo y
posterior del canal de salida.
Si se cambia forward() por include() se obtiene como resultado en el navegador:
Inicio de servlet 1
Cuerpo de servlet 2
Final de servlet 1
Es decir, el contenido escrito al canal de salida desde el servlet redireccionado forma parte del contenido de la
salida del servlet invocado inicialmente.
4.3. Enviando códigos de estado para condiciones de error.
El protocolo HTTP define códigos de estado para condiciones de error como Recurso no encontrado, Recurso
movido permanentemente, o Acceso no autorizado. La clase HttpServletResponse provee los métodos sendError(int
status_code) y sendError(int status_code, String message), y las constantes SC_XXX para enviar códigos de
estado al cliente.
Por ejemplo, si un usuario no debe acceder a nuestro recurso según el valor de una variable estática podemos
invocar el código siguiente desde el método doPost() o doGet() de un servlet:
public class MiServlet extends HttpServlet {
private static int estado = 0;
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException {
if (estado > 0) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Página no accesible en este momento");
} else {
// proceso normal del servlet
}
}
}
Cuando el navegador cliente recibe este código de estado muestra una página de error con el mensaje
apropiado al usuario.
4.4. Captura y procesamiento de excepciones.
Tal como ocurre con cualquier programa Java, los servlets pueden recibir o generar excepciones durante su
ejecución. Estas excepciones se pueden gestionar desde el propio servlet o se puede implementar un
mecanismo en el contenedor web para redirigir un error o una excepción hacia una página de respuesta.
4.4.1. Gestión de excepciones enviando código de error.
Podemos usar una estructura try…catch…finally para captura excepciones y en los bloquescatch enviar un error
al navegador cliente mediante el método response.sendError(tipoError, mensaje). Para los tipos de error
podemos usar las constantes HttpServletResponse.SC_*.
También podemos usar el método response.setStatus(estado, mensaje) para enviar un estado de error al
navegador cliente. (Aunque se considera un método obsoleto para esta funcionalidad.) Lo habitual es usar
solamente setStatus(estado) para retornar los códigos SC_OK o SC_MOVED_TEMPORARILY.
La diferencia entre ambos métodos es que sendError() fuerza al contenedor de servlets a enviar una página de
error, mientras que setStatus() envía un código de estado al navegador sin mostrarlo.

83
Como ejemplo, el siguiente servlet analiza si se recibe un parámetro llamado "dir". Si es así devuelve un código
de OK, sino devuelve un código de error de solicitud mal formada:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "Servlet1", urlPatterns = {"/servlet1"})
public class Servlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (request.getParameter("dir")==null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,"Falta el parámetro 'dir'.");
} else {
response.setStatus(HttpServletResponse.SC_OK);
}
}
}
Si en un navegador solicitamos "/servlet1" obtendremos la siguiente página de error:
Figura 21

Si solicitamos "/servlet?dir" recibiremos una página en blanco.


4.4.2. Gestión de excepciones mediante redirecciones con «RequestDispatcher».
Como se ha visto, podemos usar RequestDispatcher para redireccionar a otro recurso. En este contexto
podemos capturar una excepción y redireccionar directamente a una página de error mediante los métodos
include() y forward().
Para ilustrar un ejemplo partiremos del siguiente servlet. Este servlet espera un usuario como parámetro y lo
evalúa.
package servlet;
import java.io.*;
import javax.security.sasl.AuthenticationException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletAutentifica", urlPatterns = {"/autentifica"})
public class ServletAutentifica extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String usuario= request.getParameter("usuario");
if (usuario == null) { // miramos si hay un usuario

84
request.getRequestDispatcher("/errores/faltaUsuario.html").forward(request, response);
} else if (!usuario.equals("Pedro")) {
request.getRequestDispatcher("/errores/usuarioNoValido.html").forward(request, response);
} else {
response.sendRedirect("paginaPrivada.jsp");
}
}
}
Si el parámetro "usuario" no existe se redirige a una página con un mensaje personalizado. Si el usuario no es
"Pedro" también se redirige a otra página con un mensaje personalizado.
Para que en el navegador se muestren páginas amigables de error crearemos las dos páginas HTML de error:
PÁGINA «/errores/faltaUsuario.html»:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<b>Falta el usuario, retorne a <a href="index.html">inicio</a></b>
</body>
</html>
PÁGINA «/errores/usuarioNoValido.html»:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<b>El usuario no es válido, retorne a <a href="index.html">inicio</a></b>
</body>
</html>
Aunque éste no es el caso, puede ocurrir que el recurso invocado, a su vez, genere una excepción, la cual es
relanzada por estos dos métodos. Por este motivo se aconseja capturar las posibles excepciones que relance el
método forward():
try {
request.getRequestDispatcher("/errores/faltaUsuario.html").forward(request, response);
} catch (RuntimeException re) {
// realizar las acciones adecuadas y relanzar la excepción
throw re;
} catch(IOException ie) {
// realizar las acciones adecuadas y relanzar la excepción
throw ie;
} catch(ServletException se) {
// extraer la excepción real
Throwable t = se.getRootCause();
// realizar las acciones adecuadas y relanzar la excepción
throw se;
}
4.4.3. Gestión de excepciones declarativamente.
Los servidores web como Tomcat permiten registrar las páginas de error personalizadas de manera
declarativa, de forma que serán invocadas automáticamente cuando se produzca un código de error o una
excepción determinada. Este registro se realiza en el archivo descriptor web.xml. Debemos hacer el mapeado
de error o excepción con la página de error en la sección <error-page>, la cual cuenta con los siguientes
elementos:
<error-code> especifica un código de error Http (como 404). Debe ser único para cada mapeo de error.
<exception-type> especifica el tipo de excepción que se va a mapear. Debe ser único para cada mapeo.
<location> especifica el recurso que será lanzado al generarse la excepción. Debe empezar por /.

85
Los servlets sólo relanzan directamente las excepciones del tipo ServletException, IOException y
RuntimeException o sus subclases. Cualquier otro tipo de excepción deberá ser capturada en un bloque
try…catch y relanzada como una ServletException.
Modificaremos el servlet del ejemplo previo para que la página de error "/errores/faltaUsuario.html" sea
redireccionada cuando se lance una Exception, y que la página de error "/errores/usuarioNoValido.html" sea
redireccionada cuando se devuelva el código de error 403.
package servlet;
import java.io.*;
import javax.security.sasl.AuthenticationException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletAutentifica", urlPatterns = {"/autentifica"})
public class ServletAutentifica extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String usuario= request.getParameter("usuario");
if (usuario == null) { // miramos si hay un usuario
throw new ServletException(new Exception("Falta el usuario"));
} else if (!usuario.equals("Pedro")) {
response.sendError(response.SC_FORBIDDEN); // código 403: usuario no válido
} else {
response.sendRedirect("paginaPrivada.jsp");
}
}
}
Si el parámetro "usuario" no existe se lanza una excepción con un mensaje personalizado; esta excepción es
relanzada a través de una ServletException. Si el usuario no es "Pedro" se devuelve un código de error 403, y si
el usuario es correcto se redirige a una página privada para este usuario.
Ahora registraremos las páginas de error en el fichero descriptor web.xml:
<web-app>
<error-page>
<error-code>403</error-code>
<location>/errores/usuarioNoValido.html</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/errores/faltaUsuario.html</location>
</error-page>
</web-app>
También podemos utilizar el editor de NetBeans para registrar las páginas de error:

86
Figura 22

Podemos invocar el servlet para cada situación y comprobar que seremos redirigidos a estas páginas de error.
Nota. Las páginas de error que son lanzadas automáticamente deben tener al menos 512 bytes de tamaño
para que este mecanismo funcione.

PRÁCTICA
01. Crea una aplicación web que muestre un menú de enlaces en su página HTML inicial.
El menú constará de los siguientes enlaces:
¿Cuál es la capital de España?
¿Cuánto suman 5 y 6?
¿Quién escribió el Quijote?
Cada uno de estos enlaces deberá referenciar un mismo servlet. El servlet debe responder dinámicamente con
una página con la respuesta correcta.
02. Crea una aplicación web que permita a un usuario enviar mensajes que serán guardados en un fichero
dentro del sitio web.
El sitio web constará de los siguientes elementos:
- Una página HTML llamada "formularioInicial.html" con un formulario para que el usuario introduzca un
nombre y un comentario.
- Un fichero de texto llamado "comentarios.txt" ubicado en la carpeta raíz de la aplicación. Este fichero
tendrá dos líneas por usuario: una primera línea con un nombre, seguida de otra línea con el comentario.
- Una página HTML llamada "errores.html" que indicará que se produjo un error no controlado, y ofrecerá
un enlace para ir a la página "comentarios.txt".
- El formulario enviará los datos introducidos a un servlet llamado "servlet.ServletInicial"" que guardará el
nombre y comentario en el fichero "comentarios.txt".El servlet, una vez guardados los datos, debe devolver
como respuesta una página indicando que se realizó la operación, y un enlace a la página inicial.
Si se produce alguna excepción en el servlet debe ser gestionada automáticamente por el contenedor web y
redirigir a la página "errores.html".

03. Crea un sitio web con una carpeta llamada "imágenes" ubicada en la carpeta raíz de la aplicación. Mete en
esta carpeta varias imágenes.
Crea un servlet llamado "servlet.SirveImagen" que debe tener asociado el patrón URL "/imagen/*". Cuando
se invoque este servlet podrá indicarse en la parte final de su URL el nombre de un archivo de imagen; por
ejemplo:
http://localhost:8081/imagen/pez.jpg

87
El servlet deberá buscar la imagen en la carpeta de imágenes y si existe enviársela como respuesta al
navegador. Si la imagen no existe deberá enviar un código de error personalizado.

88
UNIDAD 13. GESTIÓN DE SERVLETS Y FILTROS
1. Gestión de sesiones
El concepto de sesión surge por la necesidad de mantener el estado durante la navegación de un cliente por
nuestro sitio web. Las sesiones se asocian a cada cliente y permiten almacenar información que es mantenida
y compartida a través de la navegación de varios recursos del sitio web.
1.1. Proceso de creación de sesiones.
Cada vez que un navegador cliente accede por primera vez a un recurso de nuestra aplicación web, el
contenedor web genera un identificador de sesión para el cliente. Dicho identificador es enviado en la
respuesta al cliente y a partir de este momento cualquier otra solicitud al mismo sitio web reenviará el
identificador de sesión al servidor. Cuando en la segunda solicitud el contenedor web reciba el identificador
de sesión establecerá un objeto de tipo HttpSession asociado al cliente. Este objeto se mantendrá hasta que
cancelemos explícitamente la sesión, se agote su tiempo de existencia, o el cliente deje de navegar por nuestra
aplicación web.
Importante. En circunstancias normales, la sesión se establece para un cliente la segunda vez que accede
a nuestro sitio web, y no en su primer acceso.
Figura 1

1.2. Información sobre la sesión.


Las sesiones de usuario son administradas mediante la clase javax.servlet.http.HttpSession, la cual provee
métodos para trabajar con la información de sesión.
▪ int getId()
Devuelve un identificador único generado para cada sesión.
▪ boolean isNew()
Retorna true siel cliente (navegador) nunca ha visto la sesión, normalmente porque acaba de ser creada
en vez de empezar una referencia a una petición de cliente entrante. Devuelve false para sesiones
preexistentes.
▪ long getCreationTime()
Devuelve la hora, en milisegundos en la que se creó la sesión.
▪ long getLastAccessedTime()
Devuelve la hora, en milisegundos, en que la sesión fue enviada por última vez al cliente.
▪ long getMaxInactiveInterval()
Devuelve la cantidad de tiempo, en segundos, que la sesión debería seguir sin accesos antes de ser
invalidada automáticamente. Un valor negativo indica que la sesión nunca se debe desactivar.
▪ void putValue (String nombre, Object valor)
▪ void putAttribute (String nombre, Object valor)
Crean una variable, si no existía ya, y le asignan un valor.
▪ Object getValue (String nombre)
▪ Object getAttribute (String nombre)
Recuperan el valor de una variable de nombre dado.
▪ String [] getValuesNames()
Retorna un array con los nombres de las variables de sesión
Para obtener la sesión actual y forzar su creación si no existiese se utiliza la siguiente instrucción:

89
HttpSession sesion = request.getSession(true);
Una vez creada una sesión podemos crear atributos de sesión y recuperarlos:
Vector items = new Vector();
sesion.setAttribute("vectorItems", items);
items = (Vector) sesion.getAttribute("vectorItems");
Si es necesario podemos forzar la destrucción de la sesión actual con la instrucción siguiente:
sesion.invalidate();
Importante. Si se invoca algún método para recuperar alguna información del objeto de sesión una vez
destruido se lanzará una excepción del tipo IllegalStateException.
1.3. Tiempo de expiración de una sesión.
En el archivo web.xml podemos establecer el tiempo de espera de una sesión antes de que se destruya por
inactividad. Por ejemplo, para establecer 30 minutos de espera:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
Un valor menor o igual a cero indica que la sesión nunca expirará.
La interfaz HttpSession también provee dos métodos para establecer este valor:
• void setMaxInactiveInterval(int seconds), establece el número máximo de segundos que debe pasar entre
llamadas del cliente antes de que se invalide la sesión (valor negativo para no expirar nunca).
• int getMaxInactiveInterval(), retorna el intervalo de expiración en segundos.
1.4. Identificadores de sesión.
Cada sesión establece un ID en el momento que se crea por la primera petición de un cliente. En ese
momento la sesión se establece en estado "new", de forma que el método HttpSession.isNew() retorna true. La
siguiente petición del cliente hará que la sesión pase al estado "joined" y el método isNew() retorne false.
Existen dos técnicas para soportar las sesiones:
▪ Usando cookies:
El contenedor de servlets usa las cabeceras http para recibir e enviar el id de sesión. Por ejemplo:
Solicitud HTTP POST
POST /servlet/testServlet HTTP/1.0
User-Agent= MOZILLA/1.0
cookie=JSESSIONID=61C4F23524521390E70993E5120263C6
Content-Type: application/x-www.formurlencoded

userid=john
Respuesta HTTP
HTTP/1.1 200 OK
Set-Cookie=JSESSIONID=61C4F23524521390E70993E5120263C6
Content-Type: application/x-www.formurlencoded

<html>
.............................
</html>
La línea JSESSIONID=61C4F23524521390E70993E5120263C6 es guardada en un archivo cookie del disco del
cliente.
▪ Usando reenvíos URL:
El id de sesión es añadido a las URLs de llamada. Por ejemplo, en un hiperenlace añadiríamos:
<a href="/servlet/AccountServlet;JSESSIONID=C084B32241B2F8F060230440C0158114">
Pero no es necesario añadir este identificador a mano, la clase HttpServletResponse provee dos métodos para
añadir el id a una URL:
• String encodeURL(String url), retorna la url con el id añadido.
• String encodeRedirectURL(String url), lo mismo para url usadas con el método sendRedirect().

90
Nótese que JSESSIONID no es enviado como un parámetro de llamada y por tanto no puede ser obtenido
con request.getParameter(). Estos dos métodos sólo añaden el id de sesión si el mecanismo de cookies no es
posible.

2. Gestión de eventos y registro de eventos.


Al igual que en las aplicaciones de escritorio los componentes Java Bean lanzan eventos y podemos
gestionarlos para responder con la ejecución de instrucciones a los mismos, el contenedor web también lanza
eventos relacionados con la aplicación y sus sesiones. Podemos gestionar estos eventos para realizar
inicializaciones y finalizaciones en cada aplicación y sesión.
La técnica para gestionar un evento es parecida a la vista en componentes Java Bean. Se crea una clase
observadora que debe implementar una interfaz determinada, y se registra una instancia de la clase
observadora ante el objeto que lanza el evento. Pero en este caso quien lanza los eventos es el contenedor
web, del cual no disponemos de su instancia. Tal como veremos a continuación, el registro de los
observadores se debe realizar en el archivo descriptor.
La gestión de estos eventos sigue los siguientes pasos:
1) Crear una clase que implemente una o varias interfaces oyentes.
2) Registrar la clase en el archivo web.xml dentro de la sección <listener>.
2.1. Eventos generados por el contexto de aplicación.
Al nivel de aplicación se generan eventos cuando se crea y destruye una aplicación, así como cuando se
modifican atributos de aplicación.
3.1.1. Eventos del arranque y destrucción de la aplicación.
Se usa la interfaz javax.servlet.ServletContextListenercuando queremos ser notificados de la inicialización y
destrucción del contexto de la aplicación. Por ejemplo, podemos crear una conexión al arrancar la aplicación
y destruirla al finalizar mediante esta interfaz. Los dos métodos que declara son:
void contextInitialized(ServletContextEvent sce), que es invocado cuando el contexto se inicializa.
void contextDestroyed(ServletContextEvent sce), que es invocado cuando el contexto se destruye.
La clase ServletContextEvent proporciona un método para obtener una referencia al ServletContext de la
aplicación. El siguiente ejemplo ilustra sobre la creación y cierre de una conexión al inicio y finalización de la
aplicación respectivamente:
package oyentes;
import java.sql.Connection;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ObservadorContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
try {
Connection c = Repositorio.obtenerConexion();
sce.getServletContext().setAttribute("conexion", c);
}catch(Exception e) { }
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
try {
Connection c = (Connection) sce.getServletContext().getAttribute("conexion");
c.close();
}catch(Exception e) { }
}
}
Podemos registrar esta clase observadora en el archivo descriptor de la siguiente manera:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<listener>
<listener-class>oyentes.ObservadorContextListener</listener-class>
</listener>
</web-app>

91
En este ejemplo, al arrancar la aplicación se obtiene una conexión desde una clase repositorio y se guarda en
un atributo del contexto de la aplicación. Esta conexión estará disponible durante la vida de la aplicación y
estará disponible desde cualquier servlet o fichero JSP. Cuando se destruye la aplicación se cierra dicha
conexión.
3.1.2. Eventos de gestión de atributos de aplicación.
Se usa la interfaz javax.servlet.ServletContextAttributeListenerpara recibir notificaciones acerca de los cambios
en la lista de atributos del contexto de aplicación. Sus métodos son:
void attributeAdded(ServletContextAttributeEvent scae), se invoca al añadir un nuevo atributo.
void attributeRemoved(ServletContextAttributeEvent scae), se invoca al quitar un atributo.
void attributeReplaced(ServletContextAttributeEvent scae), se invoca al reemplazar un atributo.
Por ejemplo, podemos controlar si el atributo "conexión" creado previamente es reemplazado y comprobar
en ese caso su disponibilidad:
package oyente;
import java.sql.Connection;
import java.sql.SQLException;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
public class ObservadorContextAttributeListener implements ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent event) {
}
@Override
public void attributeRemoved(ServletContextAttributeEvent event) {
}
@Override
public void attributeReplaced(ServletContextAttributeEvent event) {
if (event.getName().equals("conexion")) {
Connection c = (Connection) event.getServletContext().getAttribute("conexion");
try {
if (c==null || c.isClosed()) {
c = Repositorio.obtenerConexion();
event.getServletContext().setAttribute("conexion", c);
}
} catch (SQLException ex) {
}
}
}
}
Una vez registrada esta clase en el archivo descriptor, cada vez que se modifique el atributo "conexión" se
comprueba que no haya quedado a nulo o la nueva conexión esté cerrada.
2.2. Eventos generados por el contexto de las sesiones.
Se generan eventos de sesión cuando se crean y finalizan su tiempo, así como cuando cambia algún atributo
almacenado en la sesión.
2.2.1. Eventos de inicio y fin de una sesión.
Un observador que quiera recibir notificaciones del inicio y finalización de una sesión debe implementar la
interfaz javax.servlet.http.HttpSessionListener.
Esta interfaz define dos métodos:
• void sessionCreated(HttpSessionEvent se), se invoca cuando la sesión es creada.
• void sessionDestroyed(HttpSessionEvent se), se invoca cuando la sesión finalzia su tiempo.
El parámetro de tipo HttpSessionEvent proporciona el método getSession(), que devuelve el objeto HttpSession
que representa la sesión creada.
Por ejemplo, la siguiente implementación nos permite crear en cada sesión un atributo llamado "usuarioActual"
donde posteriormente podemos guardar el nombre del usuario actual de la sesión:
package oyentes;
import javax.servlet.http.HttpSessionEvent;
public class OyenteSesion implements javax.servlet.http.HttpSessionListener{
@Override

92
public void sessionCreated(HttpSessionEvent se) {
se.getSession().setAttribute("usuarioActual", "");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
}
}
Esta clase debe ser registrada en el fichero web.xml de la siguiente manera:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<listener>
<listener-class>oyentes.OyenteSesion</listener-class>
</listener>
</web-app>
Ahora, desde cualquier servlet o página JSP podemos recuperar el objeto HttpSession actual y leer o escribir
en este atributo con los métodos HttpSession.getAttribute() y HttpSession.setAttribute(), o eliminarlo con el
método HttpSession.removeAttribute().
2.2.2. Eventos de gestión de atributos de sesión.
En el contexto de una aplicación web puede ser interesante saber qué atributos de sesión se van creando y
destruyendo. A veces se puede utilizar el ciclo de vida de un atributo de sesión para desencadenar ciertas
acciones.
Un observador que quiera recibir notificaciones del ciclo de vida de los atributos de sesión debe implementar
la interfaz javax.servlet.http.HttpSessionAttributeListener.
Esta interfaz define tres métodos:
• void attributeAdded(HttpSessionBindingEvent event), es invocado cuando se añade un atributo de sesión.
• void attributeRemoved(HttpSessionBindingEvent event), es invocado cuando se elimina unatributo de sesión.
• void attributeReplaced(HttpSessionBindingEvent event), es invocado cuando un atributo de sesión cambia de
valor.
La clase HttpSessionBindingEvent proporciona los métodos:
• getName(): retorna el nombre asociado al atributo de sesión.
• getValue(): retorna el valor asociado al atributo de sesión.
• getSession(): retorna el objeto HttpSession que referencia la sesión.
La clase oyente que implemente HttpSessionAttributeListener debe ser registrada en el archivo descriptor de la
misma forma vista para la interfaz HttpSessionListener.
Un ejemplo práctico de uso de esta interfaz puede ser el siguiente. Al comenzar una sesión se crea el atributo
"usuarioActual", y varios servlets o páginas JSP puede modificar su valor. Cuando se modifica el usuario actual
queremos recuperar preferencias del usuario guardadas en un almacén de datos y dejarlas disponibles en otro
atributo de sesión llamado "preferencias":
package oyentes;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
public class OyenteAtributosSesion implements HttpSessionAttributeListener{
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
if (event.getName().equals("usuarioActual")) {
HttpSession sesion=event.getSession();
sesion.setAttribute("preferencias", preferenciasUsuario(sesion.getAttribute("usuarioActual").toString()));
}
}
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
if (event.getName().equals("usuarioActual")) {
HttpSession sesion=event.getSession();
sesion.setAttribute("preferencias", preferenciasUsuario(sesion.getAttribute("usuarioActual").toString()));
}
}
@Override

93
public void attributeRemoved(HttpSessionBindingEvent event) {
if (event.getName().equals("usuarioActual")) {
event.getSession().removeAttribute("preferencias");
}
}
private String preferenciasUsuario(String usuario) {
return ""; // debe recuperar las preferencias de usuario
}
}
En este ejemplo, cada vez que se crea o reemplaza el valor del atributo usuarioActual se leen las preferencias
del usuario y se guardan en el atributo preferencias. Si se elimina el atributo usuarioActual también se eliminan
de memoria sus preferencias.
2.2.3. Autogestión de objetos de sesión.
A veces puede interesarnos que los propios objetos que almacenamos el atributos de sesión quieran ser
conscientes de cuándo son añadidos o retirados de la sesión. Para ello la interfaz
javax.servlet.http.HttpSessionBindingListener debe ser implementada por aquellas clases cuyos objetos necesitan
recibir notificación de cuándo son añadidos o eliminados de una sesión.
Esta interfaz declara dos métodos:
• void valueBound(HttpSessionBindingEvent event), es invocado cuando un objeto es añadido a la sesión.
• void valueUnbound(HttpSessionBindingEvent event), es invocado cuando el objeto es eliminado de la sesión.
El parámetro event, de tipo HttpSessionBindingEvent es el mismo que el proporcionado en la interfaz
HttpSessionAttributeListener.
Esta interfaz se diferencia de HttpSessionAttributeListener en que la clase que la implementa no tiene que
registrarse en el archivo descriptor, ya que este tipo de eventos son recibidos por el propio objeto que se
añade o elimina de la sesión.
Siguiente con el ejemplo del usuario actual y sus preferencias, podemos crear una clase Usuario que incluya su
información de preferencias, pero que sólo las recupere cuando es añadido a una sesión:
public class Usuario implements HttpSessionBindingListener {
private String nombre;
private String preferencias;
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getPreferencias() {
return preferencias;
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
// código para recupear y asignar las preferencias del usuario
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
preferencias=null;
}
}
En este caso el método valueBound() será invocado cuando un objeto Usuario sea añadido a la sesión, y se
encargará de recuperar la información de preferencias del usuario desde algún almacén de datos.
2.2.4. Migración de sesión.
En un entorno distribuido la sesión debe ser mantenida entre todos los ordenadores que forman parte del
grupo de servidores.
En este tipo de configuración los objetos HttpSession (y sus atributos) son movidos de una máquina virtual a
otra según lo requieran las solicitudes. Por tanto, para cada sesión existe un único objeto HttpSession
compartido por todas las máquinas que forman parte del grupo de servidores.
Sin embargo existe un ServletContex por cada máquina virtual, y un ServletConfig para cada una de las
instancias de los servlets ejecutados en cada máquina virtual.
94
Podemos utilizar un observador que implemente al interfaz javax.servlet.http.HttpSessionActivationListener para
gestionar notificaciones de la migración de la sesión.
Esta interfaz declara dos métodos:
• void sessionDidActivate(HttpSessionEvent se), se invoca justo después que la sesión migra y se activa.
• void sessionWillPassivate(HttpSessionEvent se), se invoca cuando la sesión pasa a migrar.
2.3. Registro de eventos (logging).
Normalmente, usamos System.out para escribir mensajes y depurar información en la consola. En el caso de
las aplicaciones web esto no factible. Por ello la API servlet proporciona un soporte básico para registrar
información de javax.servlet.ServletContext y javax.servlet.GenericServlet (de la cual derivan los servlets). Los
dos métodos que proporcionan estas clases son:
▪ void log(java.lang.String msg), para escribir un mensaje a un archivo de registro.
▪ void log(java.lang.String message, java.lang.Throwable t), para escribir un mensaje y la pila de trazas del
objeto Throwable indicado.
La única diferencia entre los métodos log() proporcionados por GenericServlet y ServletContext es que los
primeros añaden el nombre del servlet al mensaje mientras que ServletContext no lo hace.
El archivo actual usado para los mensajes de registro depende del contenedor de servlet. Muchos
contenedores usan diferente archivos en aplicaciones web diferentes. Tomcat guarda esta información en el
archivo "conf/server.xml", ubicado en su carpeta de instalación. También podemos configurar el archivo de
registro usando un elemento <Logger> en el archivo context.xml, ubicado dentro de la carpeta META-INF de
nuestro proyecto. Los elementos Logger le indican a Tomcat hacia dónde deben ser enviados los registros:
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/WebAplicacion">
<Logger className="org.apache.catalina.logger.FileLogger"
directory="logs" prefix="localhost_log" suffix=".txt" timestamp="true"/>
</Context>
Lo anterior indica que los registros de Tomcat deben ser enviados al archivo /logs/localhost_log.txt.
El siguiente método doGet() de un servlet usa los métodos de GenericServlet para registrar una excepción:
public void doGet(HttpServletRequest req, HttpServletResponse res) {
try {
// lógica del negocio aquí
} catch(SQLException e) {
// Registramos la excepción
log("Excepción en servlet", e);
// envolvemos la excepción en una ServletException y la relanzamos
throw new ServletException("Excepción envuelta", e);
}
}
Un posible texto generado, en el archivo de registro <tomcat-root>/logs/localhost_log.2001-12-28.txt, por la
excepción podría ser:
2001-12-28 21:48:50 TestServlet: Excepción en servlet
java.sql.SQLException: sql excepción
At cgscwcd.chapter7.TestServlet.doGet (TestServlet.java:46)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:740)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
at org.apache.catalina.servlets.InvokerServlet.serveRequest(
InvokerServlet.java:446)
at org.apache.catalina.servlets.InvokerServlet.doGet(
InvokerServlet.java:180)
…………..

3. Gestión de estado
Un problema común en el desarrollo de una aplicación web es conservar el estado del usuario, o datos
relevante para la aplicación. Normalmente necesitamos conocer (y recordar) información acerca de los
usuarios, acerca de la solicitud en la que se esté trabajando, y a acerca de los datos que deben guardarse. Estos
datos de estado incluyen cosas como el rol del usuario dentro de la aplicación (como director o empleado),
datos que existen como parte de una transacción (como un carro de compra), o datos que deben almacenarse

95
(como el historial de una transacción). Efectivamente, necesitamos gestionar esta estado para proporcionar
una buena experiencia de usuario a nuestro sitio y que el usuario confíe en nuestra aplicación.
3.1. El modelo de hilos del contenedor web
En un entorno real, nuestras aplicaciones web recibirán múltiples peticiones simultáneas sobre los servlets.
Para optimizar los tiempos de respuesta el contenedor de servlets implementa un modelo de hilos de
ejecución para lanzar el código de cada petición de servlets (Multithreaded Servlet Model).
El contenedor de servlets mantiene una cola de hilos de trabajo para servir cada petición (Worker Thread Pool).
También tiene un hilo que actúa de despachador (Dispatcher Thread) para gestionar los hilos de trabajo.
Figura 2

3.1.1. Modelo multi-hilos.


Cuando el contenedor recibe una petición para un servlet, el hilo despachador selecciona un hilo de trabajo
libre para que responda a la petición. El hilo de trabajo ejecuta el método service() de la instancia del servlet.
Si se recibe otra petición, el contenedor elige otro hilo de trabajo para despacharla, sea o no sobre el mismo
servlet. De esta forma, cada petición de un servlet se ejecuta sobre un hilo independiente, y sobre una única
instancia por cada servlet.
3.1.2. Modelo simple de hilos.
Supongamos un servlet que escribe cierta información en un archivo de disco. Si este servlet recibe múltiples
peticiones simultáneas puede llegar a producirse un bloqueo puesto que cada petición requiere que se escriba
sobre el mismo archivo simultáneamente. En estos casos, interesa que las peticiones del servlet sean
secuenciales y no simultáneas.
Para conseguir esta restricción debemos hacer que nuestro servlet implemente la interfaz
javax.servlet.SingleThreadModel. (Esta interfaz no tiene ningún método.)
En este modelo, para mejorar la eficiencia, el contenedor puede crear varias instancias de un mismo servlet,
de forma que cada requerimiento es ejecutado por un hilo de trabajo sobre una instancia distinta. Esto puede
resultar peligroso cuando se trabaja con recursos compartidos.
Como alternativa a implementar SingleThreadModel se puede sincronizar el método service (o los métodos
do_() correspondientes), aunque con esta solución se puede degradar considerablemente el rendimiento de la
aplicación.
3.2. Fundamentos de gestión de estado.
El contenedor web ofrece varios lugares donde se puede almacenar la información de estado y formas
sencillas de acceder a la información de estado. Sin embargo, debe planificarse el uso de estos mecanismos
con cuidado.Si utilizamos la ubicación incorrecta, es posible que no seamos capaces de recuperar un valor
cuando es esperado.Por otra parte, la mala planificación de la gestión del estado se traduce con frecuencia en
un rendimiento inferior.
En general, se debe tener cuidado con el mantenimiento de grandes cantidades de datos de estado, ya que o
bien consume memoria del servidor, si se almacena en el servidor, o ralentizan el traslado de la página web en
el navegador, si está incluido en una página web.
Si necesitamos almacenar los valores de estado, se puede elegir entre almacenamiento de estado del lado
cliente o almacenamiento de estado de lado servidor.

96
3.2.1. Gestión de estado en el lado cliente.
Al almacenar la información de estado en el cliente, se asegura de que no se utilizan los recursos de los
servidores.Sin embargo, hay que tener en cuenta que toda la información de estado del lado cliente debe ser
enviado entre el servidor web y el navegador web, y este proceso puede ralentizar el tiempo de carga de
página. Hay que utilizar almacenamiento de estado del lado del cliente sólo para pequeñas cantidades de
datos.
Las técnicas de almacén estado en el lado cliente involucran el uso de:
• Campos de texto oculto. El elemento HTML <input type= "hidden" /> permite almacenar texto en el
código de la propia página que puede ser recuperado como parte del posteo de un formulario.
•Cookies. Las cookies son pequeños archivos de texto que gestionan los navegadores, y cuyo contenido es
devuelto como parte de la solicitud.
• Almacenes locales. El estándar HTML5 ha introducido un nuevo mecanismo para almacenar datos
grandes en el disco del cliente que sustituyen el uso habitual de cookies. Este mecanismo se gestiona
mediante código script.
•Las cadenas de consulta. Una cadena de consulta es la parte de la URI después del signo de interrogación y
se utiliza a menudo para comunicar los valores de formulario y otros datos al servidor.Puede utilizarse la
cadena de consulta para preservar una pequeña cantidad de datos de la solicitud de una página a otra.Todos
los navegadores admiten cadenas de consulta, pero algunos imponen un límite de 2.083 caracteres en la
longitud de la URL.No debe colocar ninguna información confidencial en las cadenas de consulta, ya que es
visible para el usuario, para cualquier persona observando su sesión, o cualquier persona monitoreando el
tráfico web.
3.2.2. Gestión de estado en el lado servidor.
A menudo, no es solamente práctico almacenar el estado en el cliente. El estado podría implicar más cosas y
así ser demasiado grande para transmitirse entre cliente y servidor. Quizás tenemos un estado que necesite ser
asegurado y aún cifrado no debería ser pasado a través de una red. Además, podemos tener un estado que no
es específico del cliente y sí global para todos los usuarios de la aplicación. En todos estos escenarios
seguimos necesitando almacenar el estado. Si el cliente no es la opción correcta, debemos contemplar al
servidor para la necesaria gestión del estado.
La información de estado almacenada en el lado servidor consume recursos del servidor, por lo que debe
tener cuidado de no abusar del uso de almacenamiento de estado del servidor o tendremos problemas de
rendimiento.
Los siguientes sitios almacenan la información de estado en la memoria del servidor:
• El estado de la solicitud. Se pueden almacenar datos locales creando atributos en el objeto
HttpServletRequest asociado con cada solicitud.
• Estado de aplicación.Se pueden almacenar datos compartidos creando atributos en el objeto
ServletContext asociado con cada aplicación.
• Estado de la sesión.Se pueden almacenar datos durante la navegación por un sitio web creando atributos
en el objeto HttpSessioni asociado con cada sesión.
• Tablas de bases de datos. Si el sitio utiliza una base de datos subyacente, como la mayoría de los sitios lo
hacen, podemos almacenar la información de estado en sus tablas.Este es un buen lugar para almacenar
grandes volúmenes de datos de estado que no se pueden colocar en la memoria del servidor o en el equipo
cliente.
3.3. Uso de campos de texto oculto.
Una de las técnicas más simples para guardar pequeños datos en el lado cliente es el uso de elementos HTML
<input type="hidden" />. Si embebemos estos elementos dentro de un formulario no serán mostrados en la
ventana del navegador, pero el dato que tengan asignado en su propiedad value serán enviado cuando
posteemos los datos del formulario.
Un ejemplo práctico del uso de estos campos de texto ocultos es para guardar una colección de datos que
podamos manipular mediante código script, para después devolverlos al servidor. Haremos una página que
permita editar los datos de perfil de un usuario. La página incluirá un formulario con un campo oculto donde
asignaremos un objeto con los atributos de perfil en formato JSON, también incluirá un lista desplegable para
seleccionar un atributo de perfil, y un campo de edición para editar el contenido del atributo de perfil.
<!DOCTYPE html>
<html>

97
<head>
<title>Perfiles de usuario</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
Nombre del perfil <select id="perfil" onchange="seleccionaPerfil()"></select>
<br>
Valor <input type="text" id="contenido" />
<br>
<button onclick="cambiarPerfil()">Cambiar perfil</button>
<form action="guadarPerfiles" method="post">
<input type="hidden" id="perfiles" name="perfiles"
value='{"Nombre":"","Edad":"","Profesion":"","Ciudad":""}' />
<input type = "submit" value = "Guardar perfiles" />
</form>
<script type="text/javascript">
var selPerfil = document.getElementById("perfil");
var campoPerfiles = document.getElementById("perfiles");
var perfiles = JSON.parse(campoPerfiles.value);
rellenaSelectPerfiles();

// Esta función rellena el elmento <select /> con los atributos de perfil
function rellenaSelectPerfiles() {
var claves = Object.keys(perfiles);
for(var i in claves ) {
var opcionSel = document.createElement("option");
opcionSel.innerHTML = claves[i];
opcionSel.value=claves[i];
selPerfil.appendChild(opcionSel);
}
}
// Esta función actualiza un perfil y el contenido del campo oculto
function cambiarPerfil() {
var txtContenido = document.getElementById("contenido");
perfiles[selPerfil.value] = txtContenido.value;
document.getElementById("perfiles").value = JSON.stringify(perfiles);
}
// Esta función recupera el contenido de un pefil seleccionado
function seleccionaPerfil() {
var perfil = document.getElementById("perfil");
document.getElementById("contenido").value = perfiles[perfil.value];
}
</script>
</body>
</html>
El aspecto de esta página es el siguiente:
Figura 3

98
Cuando se pulse el botón «Guardar perfiles» se posteará el contenido del campo oculto a un servlet asociado
a la acción guadarPerfiles.
3.4. Uso de cookies
Las aplicaciones Web normalmente necesitan hacer un seguimiento de los usuarios entre solicitudes de
página. Estas aplicaciones necesitan asegurarse de que el usuario que hizo la primera solicitud es el mismo que
está haciendo las solicitudes siguientes. Este tipo de seguimiento se realiza por defecto con algo llamado
cookies.
Una cookie es una pequeña cantidad de datos que se escriben en el cliente para ser guardados y después
pasados con las solicitudes a nuestro sitio. Para que las cookies persistan se escriben en archivos de texto en la
máquina del cliente. Estas cookies están pensadas para sobrevivir si el usuario cierra el navegador y después
vuelve a abrirlo. También se puede escribir cookies temporales a la memoria del navegador cliente. Estas
cookies se usan sólo durante una sesión Web dada, y se pierden cunado el navegador se cierra.
Así pues, el uso más habitual de las cookies es identificar a un usuario mientras visita varias páginas dentro de
nuestro sitio. Sin embargo, también podemos usar las cookies para almacenar información de estado u otras
preferencias del usuario.
La siguiente figura ilustra sobre cómo un cliente Web y un servidor usan las cookies. Primero (paso 1), el
cliente Web solicita una página al servidor. Si el cliente no ha visitado el servidor antes, entonces no tiene
ninguna cookie que enviar. Cuando el servidor Web responde a la solicitud (paso 2), el servidor incluye una
cookie en la respuesta; esta cookie es escrita en el navegador del cliente o el sistema de archivos. El cliente
Web entonces envía esta cookie con cada solicitud siguiente a cualquier página del mismo sitio (pasos 3 y 4).
Figura 4

3.4.1. La clase «Cookie».


Para Java, las cookies son objetos de la clase javax.servlet.http.Cookie. Se usa esta clase para crear una cookie
especificando una clave y un valor. Posteriormente se puede enviar la cookie a un navegador cliente. El valor
de una cookie puede identificar de forma aislada a un cliente, y por ello las cookies son usadas normalmente
para la gestión de sesiones. Una cookie tiene un nombre, un valor único, y atributos opcionales como un
comentario, calificadores de rutas y dominios, el tiempo de vida máximo, y un número de versión.
El método getCookies() del objeto request retorna un array de los objetos Cookie guardados en el navegador
cliente. Se puede crear una cookie con el siguiente código:
Cookie c = new Cookie("nombre", "valor");
La clase Cookie incluye los siguientes métodos.
Método Descripción
getComment() Retorna el comentario descriptivo de la cookie, o null si no se ha definido.
getMaxAge() Retorna el tiempo de vida máximo (en segundos) desde que se creó la cookie.
getName() Retorna el nombre de la cookie.
getPath() Retorna el prefijo de todas las url's para las cuales la cookie fue destinada.
getValue() Retorna el valor de la cookie como un string.
setComment(String) Asigna un comentario descriptivo sobre el propósito de la cookie.
setMaxAge(int) Asigna el tiempo de vida máximo (en segundos) de la cookie. Una cookie expira
si pasan los segundos especificados desde que se creó. Si se asigna valor cero la
cookie es eliminada.
setPath(String) Asigna la url de los recursos que pueden acceder a la cookie.
setValue(String) Asigna el valor de la cookie.
3.4.2. Cómo se crea una cookie.
Una aplicación Web crea una cookie enviándola al cliente como una cabecera en una respuesta HTTP. Desde
luego, Java hace la escritura a y la lectura de la colección de cookies una tarea relativamente sencilla. Para
99
añadir una cookie a la colección de cookies y hacer que se escriba en el navegador, se utiliza el método
response.addCookie(). Este método recibe como argumento un objeto Cookie.
Para ver cómo funciona el mecanismo de crear una cookie, vamos a crear un formulario que solicite un
nombre de usuario. El formulario posteará el nombre a un servlet que se encargará de crear una cookie que
almacene este nombre y redirigirá la respuesta a la página inicial del sitio web:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Perfiles de usuario</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<form method="post" action="registraUsuario">
<b>Escribe tu nombre: </b><input type="text" name="nombreUsuario" value="${cookie.usuario}">
<br>
<input type="submit" value="Entra">
</form>
</body>
</html>
A resaltar en el código de esta página el uso de la expresión EL ${cookie.usuario}. Esta expresión permite
recuperar fácilmente el valor asociado a una cookie denominada usuario. Si la cookie no existe no se escribirá
nada en el campo de edición, pero si existe se escribirá el último nombre guardado. Ahora crearemos o
modificaremos la cookie desde el siguiente servlet:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletRegistraUsuario", urlPatterns = {"/registraUsuario"})
public class ServletRegistraUsuario extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String usuario = request.getParameter("nombreUsuario");
Cookie cookie = new Cookie("usuario", usuario);
response.addCookie(cookie);
response.sendRedirect("index.html");
}
}
Importante. Las cookies se crean siempre añdiéndolas en la colección del objeto response, y para que se
almacenen en el cliente debe completarse la respuesta. Si la cookie ya existe en el cliente se sustituirá su
valor.
3.4.3. Cómo controlar el alcance de una cookie.
Las cookies se especifican para un dominio de sitio Web o para un directorio dentro de este dominio. Por
esta razón un navegador no debe enviar nuestras cookies a otro sitio web. Por defecto, los navegadores no
envían nuestras cookies a un sitio Web con un nombre de host diferente. Por tanto sólo tenemos que
controlar el alcance de las cookies de nuestro sitio. Podemos limitar este alcance a un directorio específico de
nuestro servidor Web o expandir el alcance al dominio completo. El alcance de nuestras cookies determina
qué páginas tienen acceso a la información embebida en la cookie. Si limitamos el alcance a un directorio,
sólo los recursos dentro de este directorio tendrán acceso a la cookie. Se controla el alcance de una cookie de
forma individualizada. Para limitar el alcance de una cookie a un directorio debemos asignar la propiedad Path
de la clase Cookie. El siguiente código muestra cómo hacer esto:
cookie.setPath("/mispaginas");

100
Ahora el navegador enviará la cookie a cualquier página de la carpeta "/mispaginas", incluido cualquier servlet
con este patrón URL. Sin embargo, las páginas fuera de esta carpeta no obtendrán la cookie, aunque estén en
el mismo servidor.
Para expandir el alcance de una cookie al dominio completo, debemos asignar la propiedad Domain de la clase
Cookie. El siguiente código muestra esto:
Cookie.setDomain("midominio.com");
Asignando la propiedad Domain a "midominio.com" provocamos que el navegador envíe la cookie a cualquier
página del dominio "midominio.com". Esto puede incluir aquellas páginas que pertenecen al sitio
www.midominio.com, intranet.midominio.com, o private.midominio.com. Similarmente, podemos asignar la
propiedad Domain a un nombre de host completo, limitando la cookie a un servidor específico.
3.4.4. Lectura de cookies.
Siguiendo con el ejemplo previo mostraremos cómo leer el contenido de una cookie. Previamente se había
creado una cookie que guardaba un nombre de usuario. Ahora crearemos un servlet que recuperará ese
nombre y mostrará una página dinámica de saludo.
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletRegistraUsuario", urlPatterns = {"/registraUsuario"})
public class ServletRegistraUsuario extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Cookie cookie = this.getCookie(request, "usuario");
String usuario = cookie == null ? "" : cookie.getValue();
try (PrintWriter out = response.getWriter()) {
out.println("<html><head></head><body>");
out.println("<p>Bienvenido: " + usuario + "</p>");
out.println("</body></html>");
}
}
private Cookie getCookie(HttpServletRequest request, String nombre) {
if (request.getCookies() != null) {
for (Cookie cke : request.getCookies()) {
if (cke.getName().equals(nombre)) {
return cke;
}
}
}
return null;
}
}
Las cookies se leen desde la colección que posee el objeto request. La clase HttpServletRequest sólo ofrece el
método getCookies() para recuperar las cookies; por eso en este ejemplo se ha creado el método getCookie()
para recuperar un cookie por nombre. A destacar que si no hay cookies asociadas el método
request.getCookies() retorna null.
3.4.5. Cómo saber si las cookies están habilitadas en el navegador cliente.
Los navegadores de Internet permiten bloquear el uso de las cookies, por eso es importante asegurarnos
desde código de si podremos o no usar cookies en nuestra aplicación web.
Podemos averiguar si el navegador cliente admite cookies consultando la cabecera "cookie" de la siguiente
forma:
String admiteCookies = request.getHeader("cookie");
if ( admiteCookies == null ) {
// El navegador cliente no admite cookies
}
Si la respuesta de la cabecera devuelve el valor nulo es que el navegador cliente no admite cookies, sino
retorna el identificador de sesión.

101
3.5. Uso de almacenes locales del lado cliente.
Las cookies sólo pueden almacenar una pequeña cantidad de datos, no más de 4 KB. Hay una importante
razón para esta limitación. Las cookies están diseñadas para ser enviadas y recibidas hacia y desde el servidor
web con cada solicitud y respuesta de una página. Si fuesen muy grandes, el ancho de banda requerido podría
impactar negativamente en la respuesta de la aplicación. Sin embargo, así como la web ha evolucionado, el
uso de la cookies también. Cada vez más se utilizan para mucho más que guardar tickets de sesión. Los
cookies han pasado a almacenar datos importantes del perfil de usuario, historiales de página y otros datos.
El mayor peligro es guardar datos de usuario en cookies que puedan ser compartidos intencionalmente entre
sitios web, y recientemente se han convertidos en famosos por traicionar la información personal sin
consentimiento. En la Unión Europea, la legislación reciente obliga a los sitios web alojados en estos países a
notificar a los usuarios si quieren usar cookies, y permitirles desactivarlas.
Otro tema del cual ser conscientes es que los usuarios a menudo abrenvarias pestañas cuando navegan por un
único sitio web. Los datos dentro de la cookies son compartidos entre estas pestañas, pero las cookies no
definen mecanismos de sincronización o concurrencia. Esto puede provocar efectos no deseados si los
valores en una cookie cambian en una pestaña y entonces se utilizan en mitad del proceso de una segunda
pestaña.
En una semántica web incrementada, las cookies son menos útiles, y plantean claramente algunas
preocupaciones significativas si las usamos mal. Las cookies nunca fueron diseñadas como una tecnología de
almacenamiento, y HTML5 introduce nuevos paradigmas de almacén en el lado cliente que cubren mucha de
las limitaciones de las cookies. Estas tecnologías incluyen el API Session Storage y el API Local Storage.
3.5.1. Persistiendo datos usando el almacén de sesión.
El almacén de sesión es un mecanismo de persistencia de los navegadores que permite almacenar datos de
texto sobre el dispositivo donde se ejecuta el navegador. Como su nombre implica, los datos se almacenan
durante la sesión del usuario actual. Cuando el usuario cierra el navegador, el almacén de sesión se vacía
automáticamente. El almacén de sesión es ampliamente soportado y es implementado por la mayoría de
navegadores, incluyendo Internet Explorer, desde 2009. Podemos acceder al almacén de sesión usando la
propiedad sessionStoragedel objeto window en código JavaScript, Podemos saber si el navegador implementa
el almacén de sesión consultado la presencia de esta propiedad, tal como sigue:
if( window.sessionStorage ) {
...
}
El almacén de sesión almacena cada dato de sesión con una clave única; podemos proporcionar la clave
cuando guardamos un valor, y se usa la misma clave para recuperar el dato. El API Session Storage
proporciona tres caminos para almacenar y recuperar datos:
• Las funciones setItem()ygetItem(). La función setItem()recibe una clave y el dato a almacenar. La función
getItem()recibe una clave y retorna el dato asociado. Si la clave no existe retorna el valor null.
sessionStorage.setItem("miClave","algún texto");
var textFromSession = sessionStorage.getItem("miClave");
• Sintaxis de array asociativo. Podemos usar la notación de array para consultar el objeto sessionStorage.
sessionStorage["miClave"] = "algún texto";
var textFromSession2 = sessionStorage["miClave"];
• Seudo-propiedades. Podemos añadir propiedades para cada clave al objeto sessionStorage.
sessionStorage.miClave = "algún texto";
var textFromSession3 = sessionStorage.miClave;
Nota:No es necesario usar el objeto windowpara acceder a sus propiedades. Podemos llamarlas
directamente, porque window es el contexto de llamada por defecto.
Si necesitamos persistir objetos en el almacén de sesión, podemos serializarlos a un string JSON usando la
función JSON.stringify().
Los objetos del almacén de sesión son también accesibles como un array de elementos con un índice de tipo
long. Podemos comprobar la propiedad lengthpara saber cuántas claves hay en al objeto sessionStorage.
Podemos recuperar la clave de cada objeto usando la función key(). Esta información puede ser muy útil para
iterar sobre los objetos, tal como se muestra en el siguiente ejemplo, el cual crea una lista de claves en el
objeto sessionStoragey las muestra en un elemento <div>:
var listDiv = document.getElementById("myList");
for(var i=0; i<sessionStorage.length; i++)

102
{
listDiv.innerHTML += "<br />" + sessionStorage.key(i);
}
Para eliminar un elemento del almacén de sesión se usa el método removeItem().
sessionStorage.removeItem("myKey");
Para vaciar el almacén de sesión se usa el método clear():
sessionStorage.clear();
Recuérdese que si no limpiamos los datos de una sesión, serán limpiados automáticamente cuando el usuario
cierre el navegador. Por consiguiente, el almacén de sesión puede está limitado a aplicaciones que no
necesitan preservar datos de usuario entre sesiones. Aquí es donde el API Local Storage resulta más útil.
3.5.2. Persistiendo datos entre sesiones usando el almacén local.
El almacén local permite almacenar datos sobre el navegador cliente, pero al contrario que el almacén de
sesión, los datos persisten después de que la sesión del usuario ha finalizado. Los datos son almacenados en el
sistema de ficheros del dispositivo que ejecuta el navegador. Se eliminan cuando la aplicación web los borra, o
cuando el usuario solicita que el navegador los borre. Este mecanismo es dependiente del navegador. Los
datos persistentes del almacén local están disponibles entre diferentes páginas, incluso aunque pertenezcan a
sitios web diferentes.
Se puede acceder al almacén local usando la propiedad localStoragedel objeto window. Se puede detectar si el
navegador soporta el almacén local consultado la presencia de esta propiedad, tal como sigue:
if( window.localStorage ) {
...
}
El API Local Storage es muy parecida al API Session Storage. Podemos almacenar y recuperar datos usando
las funciones setItem()ygetItem(), la notación de array, o seudo-propiedades.
localStorage.setItem("myKey","algún texto");
var textData = localStorage.getItem("myKey");
localStorage["myKey"] = "algún texto";
var textData = localStorage["myKey"];
localStorage.myKey = "algún texto";
var textData = localStorage.myKey;
Podemos determinar el número de elementos del almacén local usando la propiedad length, e iterar sobre los
elementos y recuperar datos usando la función key(), tal como se muestra en el siguiente ejemplo:
var listDiv = document.getElementById("myList");
for(var i=0; i<localStorage.length; i++)
{
listDiv.innerHTML += "<br />" + localStorage.key(i);
}
Para eliminar un elemento del almacén se usa el método removeItem().
localStorage.removeItem("myKey");
Para vaciar el almacén local se usa el método clear():
localStorage.clear();
Los objetos almacenados en el almacén local no tienen una restricción de tamaño específica. El almacén es
libre de crecer hasta un límite decidido por el usuario y configurado como una opción del navegador. Esto
hace que sea ideal para almacenar objetos serializados como texto usando la función JSON.stringify().
Usar almacenamiento local en lugar de confiar en las idas y venidas de un servidor tiene un impacto
significativo en la experiencia del usuario. Los datos encontrados sobre el cliente pueden mostrarse
instantáneamente, aumentado la velocidad de respuesta del sitio web.
3.5.3. Gestionando eventos de almacenamiento.
El API de almacenamiento al que se ajustan tanto el almacén de sesión y el local incluye un evento
denominado storage. Podemos usar este evento para notificar a una página web de los cambios de datos
dentro del almacén; se lanza si los datos son modificados.
El siguiente ejemplo muestra cómo suscribir este evento:
function myStorageCallback( e ) {
alert("Clave:" + e.key + " ha cambiado a " + e.newValue);
}
window.addEventListener("storage", myStorageCallback, true);
El objeto de evento pasado a la función manejadora del evento incluye las siguientes propiedades:

103
• key: El nombre de la clave modificada.
• oldValue: El valor original antes de cambiar.
• newValue: El nuevo valor.
• url: El documento cuyo script originó el evento.
• storageArea: Una referencia al almacén que fue cambiado (session o local).
El modelo de eventos permite a las páginas web escuchar eventos de almacenamiento y actualizarse a ellas
mismas cuando los datos cambien. Por ejemplo, en Internet Explore, si tenemos la misma página abierta en
varias pestañas, los datos de cada pestaña pueden sincronizarse para reducir inconsistencias.
Internet Explorer también define el evento storagecommit. Este evento se lanza cuando los datos del almacén
local son escritos a disco.
function myStorageCommitCallback( e ) {
alert("Datos escritos a disco");
}
window.addEventListener("storagecommit", myStorageCommitCallback, true);
3.5.4. Cómo interactúa el servidor con los almacenes locales.
Los servidores web no tienen acceso directo a los almacenes locales. Así como son pasadas las cookies con
los datos de la solicitud, los datos almacenados en sessionStorage y localStorage no son enviados
automáticamente al servidor. Desde código script de las páginas deberemos implementar algún mecanismo
para transmitir los datos de los almacenes locales al servidor.
Por ejemplo, en la página "altaProducto.html" se creará un formulario que permita añadir nombres de
productos a un array de JavaScript:
<!DOCTYPE html>
<html>
<head>
<title>Perfiles de usuario</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<b>Agrege un nombre de producto: </b><input type="text" id="producto">
<br>
<button onclick="agregarProducto()" >agregar</button>
<ul id="listaProductos"></ul>
<button onclick="confirmarProducto()" >confirmar</button>
<script type="text/javascript">
var productos = new Array(); // se crea un array vacío
var listaProductos = document.getElementById("listaProductos");
function agregarProducto() {
var nombre = document.getElementById("producto").value;
productos.push(nombre);
var li = document.createElement("li");
li.innerHTML=nombre;
listaProductos.appendChild(li);
}
function confirmarProducto() {
sessionStorage["productos"] = JSON.stringify(productos);
window.location="confirmarProductos.html";
}
</script>
</body>
</html>
Esta página irá almacenando nombres de productos a un array en código JavaScript. Cuando se pulse el
botón «Confirmar» se almacenará esta array, serializado a JSON, en el almacén de sesión, y se navegará a la
siguiente página "confirmarProducto.html":
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">

104
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<ul id="listaProductos"></ul>
<form method="post" action="confirmarproductos">
<input type="hidden" name="productos" id="lbProductos" />
<input type="submit" value="Aceptar" />
<input type="submit" value="Cancelar" onclick="cancelar()" />
</form>
<script type="text/javascript">
document.getElementById("lbProductos").value = sessionStorage["productos"];
var productos = JSON.parse(sessionStorage["productos"]);
var listaProductos = document.getElementById("listaProductos");
for (var i in productos) {
var li = document.createElement("li");
li.innerHTML = productos[i];
listaProductos.appendChild(li);
}
function cancelar() {
sessionStorage.removeItem("productos");
document.forms[0].action="altaProductos.html";
}
</script>
</body>
</html>
Esta página recupera el array almacenado en la sesión, lo asigna en un campo oculto dentro de un formulario,
y muestra su contenido en una lista no numerada. Al pulsar el botón «Aceptar» se postea el contenido del
campo oculto hacia un servlet. Al pulsar el botón «Cancelar» se elimina el array del almacén de sesión y se
retorna a la página previa.
Figura 5

3.6. Gestión de estado en el servidor usando atributos de ámbito.


Debemos tener en cuenta que en el modelo multi-hilos del contenedor web se trabaja sobre una misma
instancia de servlet. Cada solicitud de un cliente es gestionada mediante un hilo de trabajo que invoca el
método service() del servlet. En aras a conseguir la mayor seguridad posible en los hilos, debemos considerar
el comportamiento y ámbito de las variables o atributos que podemos usar en un servlet:
▪ Las variables de instancia son compartidas por todos los hilos. Si varios hilos modifican a la vez una
variable de objeto pueden crearse problemas de coherencia en los datos.
▪ No ocurre lo mismo con las variables locales del método service(). Cada hilo reserva memoria para la
ejecución de dicho método, de forma que cada hilo trabaja con una copia diferente de las variables locales.

105
▪ Las variables static son compartidas por todas las instancias de un servlet, por ello presentan la misma
problemática que las variables de instancia. Para garantizar hilos seguros, las variables de clase deberían
usarse sólo como si fuesen constantes.
▪ Los atributos de contexto (ServletContext) son accesibles por todos los servlets de una aplicación y sus
correspondientes hilos. Si creamos un atributo para guardar los usuarios accedidos, de forma que algún
servlet añade usuarios y otro los elimina, sería conveniente sincronizar el acceso a dicho atributo (aunque
esto podría provocar un embotellamiento).Deberían usarse para guardar datos compartidos que se
actualizan raramente.
▪ Los atributos de sesión (HttpSession) sólo son accesibles por los hilos que trabajan sobre la misma
sesión. Debemos tener en cuenta que un mismo usuario puede abrir varias ventanas en un navegador y
solicitar recursos simultáneamente sobre la misma sesión, pero sólo un hilo trabajará como máximo sobre
un mismo recurso a este nivel de sesión. En este caso, si aplicamos sincronización sobre el objeto session,
no provocaremos embotellamientos.
▪ Los atributos de solicitud (ServletRequest) son sólo accedidos por un hilo, ya que el contenedor de
servlets crea un nuevo objeto ServletRequest por cada requerimiento recibido. Para garantizar la seguridad
deberíamos usar estos atributos sin salir del alcance del método service().
3.6.1. Compartiendo datos (ámbitos de creación de atributos).
Podemos compartir datos a tres niveles mediante tres clases que nos permiten crear atributos en el
contenedor de servlets: HttpServletRequest, HttpSession y ServletContext. Un atributo es un objeto que se
almacena en uno de estos niveles o contextos y que es accedido mediante un nombre de tipo String.
Cada una de estas clases posee un método setAttribute(String name, Object value) para guardar datos en el
contenedor y un método Object getAttribute(String name) para recuperar los datos.
Figura 6

Importante. No confundir los atributos con los parámetros de solicitud. Los parámetros son enviados en
la URL o el cuerpo de la solicitud, mientras que los atributos se conservan en la memoria del servidor.
Se aplican las siguientes reglas de acceso y visibilidad en cada ámbito:
• HttpServletRequest comparte datos sólo durante la vida de una respuesta a un cliente.
Sólo se pueden recuperar estos atributos desde el servlet que es invocado.
• HttpSession comparte datos mientras un cliente está activo.
Sólo los servlets invocados en un mismo contexto de sesión tienen acceso a los atributos creados en
dicho contexto. Una vez finalizada una sesión se eliminan los atributos creados en su ámbito.
• ServletContext comparte datos durante la vida de una aplicación Web.
Los atributos creados en este ámbito son accesibles desde cualquier servlet de la aplicación
independientemente de la sesión.
Los métodos que permiten manipular atributos en estos tres ámbitos son los siguientes:
• Object getAttribute(String)
Recupera el valor de un atributo cuyo nombre es pasado por argumento.
• void setAttribute(String, Object)
Crea un atributo con el nombre y valor dados. Si ya existe un atributo con el mismo nombre se actualiza
su valor.
• void removeAttribute(String)
Elimina el atributo de nombre dado, si existe.
• Enumeration<String> getAttributeNames()
Retorna una enumeración con todos los nombres de atributos actuales.

106
3.6.2. Uso de los atributos de ámbito.
Veremos ahora un ejemplo práctico del uso de servlets y atributos de contexto para almacenar datos. En este
ejemplo implementaremos una sencilla aplicación de carro de compra que se compondrá de los siguientes
servlets:
• ServletPedidoForm: mostrará un formulario para que el usuario introduzca su número de teléfono y
seleccione un libro para añadir a su compra. El formulario ofrecerá dos botones, uno para agregar un
pedido de libro, y otro para confirmar la compra. Ambos botones harán una solicitud al siguiente servlet.
• ServletProcesaPedido: procesará tanto un nuevo pedido como la confirmación de compra. Si es
invocado para un nuevo pedido lo añadirá a un repositorio de sesión y redirigirá al servlet
ServletPedidoForm. Si es invocado para confirmar la compra guardará los datos de compra y redirigirá al
siguiente servlet.
• ServletCompra: recuperará los datos de la compra actual y los mostrará en una tabla. También mostrará
un enlace para retornar al ServletPedidoForm.
Figura 7

Primero crearemos dos clases de la lógica del negocio. La clase Pedido encapsulará los datos de un pedido de
libros:
package servicio;
public class Pedido {
private String libro;
private int unidades;
public Pedido() {
}
public Pedido(String libro, int unidades) {
this.libro = libro;
this.unidades = unidades;
}
public String getLibro() {
return libro;
}
public void setLibro(String libro) {
this.libro = libro;
}
public int getUnidades() {
return unidades;
}
public void setUnidades(int unidades) {
this.unidades = unidades;
}
}
Y una clase llamada Repositorio gestionará una lista de libros disponibles y la persistencia de los pedidos
realizados.
package servicio;
import java.util.*;
public class Repositorio {
private final static String[] LIBROS = {"Libro1", "Libro2", "Libro3", "Libro4"};
private List<Pedido> pedidos = new ArrayList<>();

107
public List<String> getLibrosSinPedir() {
List<String> libros = new ArrayList<String>(Arrays.asList(LIBROS));
for (Pedido pedido : pedidos) {
if (libros.contains(pedido.getLibro()))
libros.remove(pedido.getLibro());
}
return libros;
}
public List<Pedido> getPedidos() {
return pedidos;
}
public void addPedido(Pedido pedido) {
pedidos.add(pedido);
}
}
Por simplicidad esta clase persistirá los datos en colecciones de memoria. Cada instancia de Repositorio
gestionará una compra, de manera que varios usuarios de nuestro sitio web pueden estar trabajando con
instancias de esta clase sin interferir entre ellas. Para conseguir esto se instanciará un objeto Repositorio en el
ámbito de la sesión actual.
El servlet ServletPedidoForm se invocará ante la solicitud de la URL "pedidoForm.html" y devolverá un
formulario de pedido de libros:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import servicio.Repositorio;
@WebServlet(name = "ServletPedidoForm", urlPatterns = {"/pedidoForm.html"})
public class ServletPedidoForm extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Se deja disponible una instancia del repositorio en la sesión
Repositorio repositorio = (Repositorio) request.getSession(true).getAttribute("repositorio");
if (repositorio==null) {
repositorio = new Repositorio();
request.getSession(true).setAttribute("repositorio", repositorio);
}
// Se intenta recuperar un atributo de request llamado "telefono"
String telefono = (String) request.getAttribute("telefono");
if (telefono == null) telefono = "";
// Se genera la respuesta
response.setContentType("text/html; charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>");
out.println("<title>Pedidos de libros</title>");
out.println("</head>");
out.println("<body>");
out.println("<form method='post' action='procesarPedido'>");
out.println("<table>");
out.println("<tr>");
out.println("<td>Teléfono de contacto</td>");
out.println("<td><input type='text' name='telefono' value='" + telefono + "' /></td>");
out.println("</tr>");
out.println("<tr>");
out.println("<td>Libro</td>");
out.println("<td>");

108
out.println("<select name='libro'>");
for (String libro : repositorio.getLibrosSinPedir()) {
out.println("<option>"+libro+"</option>");
}
out.println("</select>");
out.println("</td>");
out.println("</tr>");
out.println("<tr>");
out.println("<td>Unidades</td>");
out.println("<td><input required min='1' type='number' name='unidades' value='1' /></td>");
out.println("</tr>");
out.println("<tr>");
out.println("<td><input type='submit' value='Agregar pedido' name='agregar' /></td>");
out.println("<td><input type='submit' value='Confirmar pedidos' name='confirmar' /></td>");
out.println("</tr>");
out.println("</table>");
out.println("</form>");
out.println("</body>");
out.println("</html>");
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
El formulario que devuelve este servlet muestra una lista desplegable con sólo aquellos libros que todavía no
han sido seleccionados.
El servlet ServletProcesaPedido se encarga de procesar tanto un pedido como la confirmación de la compra:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import servicio.Pedido;
import servicio.Repositorio;
@WebServlet(name = "ServletProcesaPedido", urlPatterns = {"/procesarPedido"})
public class ServletProcesaPedido extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Se recupera el repositorio de la sesión.
Repositorio repositorio = (Repositorio) request.getSession().getAttribute("repositorio");
String telefono = request.getParameter("telefono");
if (request.getParameter("agregar") != null) { // Hay que agregar un pedido
String libro = request.getParameter("libro");
int unidades = Integer.parseInt(request.getParameter("unidades"));
request.setAttribute("telefono", telefono);
Pedido pedido = new Pedido(libro, unidades);
// Se recupera el repositorio de la sesión y se añade el pedido
repositorio.addPedido(pedido);
// Se redirige al formulario pasando un atributo de request
request.setAttribute("telefono", telefono);
// y se vuelve al formulario
request.getRequestDispatcher("/seleccionLibro.html").forward(request, response);
} else { // Se ha confirmado
// .... algún código para registrar el pedido ......
// Se muestra una página de confirmación pasando el teléfono
request.setAttribute("telefono", telefono);
request.getRequestDispatcher("/confirmacion").forward(request, response);

109
}
}
}
Después de añadir un pedido redirige otra vez al servlet ServletPedidoForm, pero pasándole un atributo de
solicitud con el teléfono. De esta forma en el formulario se podrá mantener el teléfono introducido
previamente. Si se produce la confirmación de la compra se redirige al servlet ServletCompra, al que pasa
también el teléfono como un atributo de solicitud.
El servlet ServletCompra se encarga de confirmar la compra, mostrando los pedidos acumulados en el
repositorio. Recupera el repositorio del ámbito de la sesión y el teléfono del ámbito de la solicitud.
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import servicio.Pedido;
import servicio.Repositorio;
@WebServlet(name = "ServletCompra", urlPatterns = {"/confirmacion"})
public class ServletCompra extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Se recupera el repositorio y el teléfono del ámbito de la solicitud
Repositorio repositorio = (Repositorio) request.getSession().getAttribute("repositorio");
request.getSession().removeAttribute("repositorio"); // Se quita de la sesión.
String telefono = (String) request.getAttribute("telefono");
// Se genera la respuesta
response.setContentType("text/html; charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>");
out.println("<title>Confirmación de compra</title>");
out.println("</head>");
out.println("<body>");
out.println("<table>");
out.println("<tr>");
out.println("<th>Libro</th>");
out.println("<th>Unidades</th>");
for (Pedido pedido : repositorio.getPedidos()) {
out.println("<tr>");
out.println("<td>" + pedido.getLibro() + "</td>");
out.println("<td>" + pedido.getUnidades() + "</td>");
out.println("</tr>");
}
out.println("</table>");
out.println("<h3>Su compra se ha procesado. Recibirá un aviso en el número " + telefono + "</h3>");
out.println("<a href='seleccionLibro.html'>Realizar otra compra</a>");
out.println("</form>");
out.println("</body>");
out.println("</html>");
}
}
}
Las páginas generadas por estos servlets y su navegación se ilustran a continuación:

110
Figura 8

4. Filtros
En general, un filtro es un objeto que intercepta los mensajes entre un origen de datos y un destino de datos,
y que decide cuáles deben pasar de un lado al otro.
Para las aplicaciones web, los filtros son componentes que residen en el servidor web y que filtran los
requerimientos y respuestas que son pasados entre un cliente y un recurso.
Figura 9

Cuando el contenedor de servlets recibe un requerimiento para un recurso, primero mira si hay un filtro
asociado con el recurso. Si es así, remite el requerimiento al filtro. El filtro, después de procesar el
requerimiento, hace una de estas tres cosas:
• Genera la respuesta por sí mismo y se la envía al cliente.
• Pasa el requerimiento (modificado o no) al siguiente filtro asociado al recurso (si lo hay) o al recurso si es
el último filtro.
• Reenvía el requerimiento a un recurso diferente.
Una vez generada la respuesta, ésta pasa otra vez por el mismo conjunto de filtros en el orden inverso. Cada
filtro puede modificar la respuesta.
111
Se pueden usar filtros para:
• Autentificar usuarios.
• Conversión de imágenes.
• Compresión y/o encriptación de datos.
• Reconocer elementos.
• Filtrar los eventos lanzados por los recursos.
• Encadenar tipos de documento MIME.
4.1. Implementación de filtros.
Todos los filtros implementan la interfazjavax.servlet.Filter, que define tres métodos: init(), doFilter(), y
destroy().
Como ejemplo, en el proyecto web de NetBeans crearemos un filtro que capturará cualquier solicitud del sitio
web y simplemente mostrará un mensaje de saludo.
4.1.1. Cómo añadir un filtro a un proyecto web.
Comenzaremos añadiendo un filtro al proyecto "WebAplicación" usando la plantilla que incorpora NetBeans.
Al ser clases de Java, igual que los servlets, los filtros deben crearse en el nodo «Source packages». Sobre este
nodo hay que abrir el menú contextual y pulsar sobre «Nuevo|Filter». Si no aparece la opción «Filter» hay que
seleccionar la opción «Otros» y en la categoría «Web» seleccionar la plantilla «Filter».
Figura 10

En el cuadro de diálogo «Nuevo Filtro» pondremos como nombre de la clase "FiltroSaludo", y la ubicaremos
en un nuevo paquete con el nombre "filtro".

112
Figura 11

Tras pulsar el botón «Siguiente», el asistente nos ofrece las opciones para configurar y registrar el filtro en el
contenedor web.
4.1.2. Registro de un filtro en el archivo descriptor.
Los filtros se registran en el fichero descriptor de la aplicación de una forma parecida a los servlets. En el
cuadro de diálogo de «Nuevo Filtro» simplemente tenemos que marcar la opción «Add information to
deployment descriptor (web.xml)». Si existe el fichero /WEB-INF/web.xml se registrará la información que
establezcamos en este asistente, pero si no existe el filtro no será registrado en el contenedor web.
Figura 12

113
Para configurar un filtro debemos proporcionar un nombre de filtro (como este nombre se utiliza para
información interna de registro podemos utilizar el propio nombre de la clase), y un mapa de filtro. Este
mapa de filtro es la ruta de la URI que asociará las solicitudes con el filtro. Pulsando el botón «Nuevo» se
accede al asistente «Filter Mapping». Para este ejemplo se ha asignado /*, lo que implica que cualquier URI se
asociará al filtro.
Figura 13

Podemos ver que en este asistente se puede asociar también un filtro con un servlet concreto, y que permite
seleccionar opciones de redirección REQUEST, FORWARD, INCLUDE y ERROR. Hay que tener en cuenta que un
filtro captura inicialmente solo la solicitudes externas (REQUEST), procedentes normalmente desde un
navegador. Pero hemos visto que también se puede realizar solicitud de redirección internas. Si queremos que
un filtro responda a redirecciones internas debemos marcar las opciones FORWARD, INCLUDE y/o ERROR.
Tras aceptar pulsar el botón «Siguiente», el siguiente cuadro de diálogo permite crear parámetros de
inicialización para el filtro. Esto parámetros son similares a los parámetros de inicialización de los servlets.

114
Figura 14

Tras pulsar el botón «Finalizar» se creará el filtro y quedará registrado en el fichero descriptor. El fichero
web.xml quedará como sigue:
<?xml version="1.0" encoding="UTF-8"?>
<web-app …………………. >
<filter>
<filter-name>FiltroSaludo</filter-name>
<filter-class>filtro.FiltroSaludo</filter-class>
</filter>
<filter-mapping>
<filter-name>FiltroSaludo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Como se ve, la clase del servlet se registra con una etiqueta <filter>, donde se especifica el nombre del filtro y
su nombre de clase cualificado (incluyendo los paquetes donde está contenida).
La etiqueta <filter-mapping>se encarga de mapear un filtro declarado en <filter> con su mapa (la ruta a la que
quedará asociado). Pero un mismo filtro puede tener asociados varios elementos <filter-mapping>, esto quiere
decir que podemos asociar un filtro con varios patrones URL, de forma análoga a lo que se puede hacer con
un servlet.
Las sub-etiquetas que admite <filter-mapping> son las siguientes:
<filter-name>: Identifica el filtro a asociar.
<url-pattern>: Se utiliza para aplicar el filtro a las peticiones que concuerden con el patrón URL.
<servlet-name>: Se utiliza para aplicar el filtro a todas las peticiones que se realicen a un nombre de servlet
asociado.
<dispatcher>: Se utiliza para determinar el modo de invocación del filtro. Puede tomar los valores:
REQUEST, FORWARD, INCLUDE, o ERROR.
Sólo puede existir un subelemento <url-pattern> o <servlet-name> dentro un mismo <filter-mapping>.
4.1.3. Registro de un filtro mediante anotaciones.
Desde el JDK 7 también se pueden registrar los filtros mediante anotaciones en la propia clase. Para
conseguir esto, en el cuadro de diálogo de «Nuevo Filtro» simplemente tenemos que dejar desmarcada la
opción «Add information to deployment descriptor (web.xml)».

115
Figura 15

Para ese ejemplo, en el fichero "FiltroSaludo.java"se añadirán las siguientes anotaciones sobre la clase:
package filtro;
@WebFilter(filterName = "FiltroSaludo", urlPatterns = {"/*"})
public class FiltroSaludo implements Filter {
……………….
}
La anotación javax.servlet.annotation.WebFilter posee los siguientes atributos:
Atributo Descripción
filterName Un string con el nombre del filtro. Este nombre es usado internamente por el
contenedor web y se corresponde con la etiqueta <filter-name>.
urlPatterns Un array de strings con los mapas URL asociados con el filtro.
initParams Un array de objetos @WebInitParam con parámetros de inicialización del filtro.
asyncSupported Un valor booleano que indica si el filtro soporta operaciones asíncronas.
smallIcon Un string con la ruta con un icono pequeño para representar el filtro.
largeIcon Un string con la ruta con un icono grande para representar el filtro.
description Un string con una descripción larga del filtro.
displayName Un string con una descripción corta del filtro.
4.1.4. Funcionalidad del filtro.
La plantilla generada por NetBeans para crear filtros incluye mucho código de depuración. Para comprender
cómo funcionan los filtros simplificaremos este código:
package filtro;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

116
@WebFilter(filterName = "FiltroSaludo", urlPatterns = {"/*"})
public class FiltroSaludo implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig filterConfig) {
}
}
Como se puede ver, el código para crear un filtro es similar al utilizado para crear un servlet, con las siguientes
diferencias:
• El método init()es invocado por el contenedor web para indicar al filtro que está siendo usado dentro de
un servicio. El contenedor web llamará a este método sólo una vez después de instanciar el filtro, y debe
completarse para que el filtro pueda realizar su trabajo. Al igual que con los servlets, el contenedor web no
pondrá el filtro dentro del servicio si este método lanza una ServletException o no finaliza su ejecución en el
tiempo establecido por el contenedor web.
Importante. Si el método inti() lanza una ServletException impedirá que pueda ejecutarse la aplicación
web, puesto que los filtros deben desplegarse al inicio de la aplicación.
Este método incluye un parámetro del tipo FilterConfig, el cual nos da acceso a los parámetros de
inicialización del filtro. La interfaz javax.servlet.FilterConfig proporciona los siguientes métodos:
- String getFilterName(), devuelve el nombre del filtro especificado en el archivo web.xml.
- String getInitParameter(String), devuelve el valor del parámetro de inicialización especificado.
- Enumeration getInitParameterNames(), devuelve una instancia Enumeration con los nombres de los
parámetros de inicialización.
- ServletContext getServletContext(), devuelve la instancia de ServletContext asociada a la aplicación web.
• El método destroy()es invocado por el contenedor web para indicar que el filtro ha sido quitado del
servicio. Este método es sólo llamado una sola vez una vez que el filtro ha sido eliminado. Una vez
invocado este método, las nuevas invocaciones al filtro provocarán que se cree una nueva instancia.
• El método doFilter() es el encargado de aplicar la funcionalidad del filtro. A diferencia del método service()
de los servlets recibe un parámetro request de tipo ServletRequest, que es la clase base de HttpServletRequest,
puesto que los filtros no se restringen sólo a filtrar servlets http. Ocurre algo similar con el parámetro
response.
• El método doFilter()también incluye un tercer parámetro del tipo FilterChain. La interfaz
javax.servlet.FilterChain tiene un único método:
- void doFilter(ServletRequest, ServletResponse), pasa la petición al próximo componente en la cadena,
otro filtro o el recurso original.
De momento el filtro FiltroSaludo simplemente redirige la solicitud sin realizar ninguna modificación. La
instrucción:
chain.doFilter(request, response);
Se encarga de redirigir la solicitud para que sea procesada por otro filtro o se complete.
Si queremos que el filtro pare la solicitud debe retornar el contenido de la respuesta de forma dinámica, de
forma similar a como lo haría un servlet:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
response.setContentType("text/html; charset=UTF-8");
try (PrintWriter out=response.getWriter()) {
out.println("<html>");
out.println("<body>");
out.println("<h1>Saludos, un filtro ha interceptado la solicitud</h1>");
out.println("</body>");
out.println("</html>");
}

117
}
Ahora, cualquier solicitud (de una recurso existente o no existente) que se realice al sitio web será capturada
por el filtro, el cual devolverá un página con un mensaje.
4.2.¿Cómo configurar cadenas de filtros?
Las cadenas de filtros pueden ser configuradas utilizando múltiples elementos <filter-mapping>. Cuando el
contenedor de servlets recibe una petición, éste busca todos los mapeos de filtros que concuerdan la URI con
el patrón URL. Éste se considera el primer conjunto de filtros a aplicar al recurso. Luego se buscan todos los
mapeos de filtros con el nombre del servlet correspondiente. Éste se convierte en el segundo conjunto de
filtros en la cadena de filtros. En ambos conjuntos, el orden de los filtros se corresponde al orden de
declaración de los mismos.
El contenedor primero invoca a los filtros del primer conjunto antes que los del segundo.
Por tanto, para asignar un conjunto de filtros a un recurso debemos establecer las URIs apropiadas tanto en
el recurso como en los filtros. Supongamos un servlet con dos URIs:
Servlet Uris
servlet1 /sun/sun/Servlet1
*.sun
Y tres filtros, registrados en el orden indicado, con las siguientes URIs:
Filtros Uris
FiltroA servlet1
FiltroB /sun/sun/*
FiltroC /sun/*
En la siguiente tabla se muestras ejemplos de varios requerimientos del servlet y los filtros que se aplican:
Uri del requerimiento Orden de aplicación de los filtros Casa con filtro url Se invoca al servlet
sun/sun/Servlet1 FiltroB, FiltroC y FiltroA /sun/sun/* y sun/* SI
sun/Servlet1 FiltroC /sun/* NO
aaa.sun FiltroA SI
sun/sun/sun.sun FiltroB, FiltroC y FiltroA /sun/sun/* y sun/* SI

4.3. Ejecución de filtros.


La especificación de Servlets 2.4 permite invocar servlets de las siguientes maneras:
• Como un resultado del método RequestDispatcher.forward().
• Como un resultado del método RequestDispatcher.include().
• En páginas de error.
Un filtro por defecto solo es invocado con peticiones entrantes (desde el cliente). Pero el registro del filtro
permite especificar los modos de invocación de un filtro: REQUEST, FORWARD, INCLUDE, o ERROR.
• Un valor REQUEST activa el filtro para solicitudes desde el cliente. REQUEST es la opción por defecto.
• Un valor INCLUDE activa el filtro para solicitudes despachadas desde una llamada a include().
• Un valor FORWARD activa el filtro para solicitudes despachadas desde una llamada a forward().
• Un valor ERROR activa el filtro para recursos llamados desde manejadores de error.
Se pueden usar de 0 a 4 subelementos <dispatcher> dentro de un elemento <filter-mapping>. Por defecto (si
no se especifica ningún subelemento <dispatcher>) los filtros sólo se aplican para peticiones entrantes a la
aplicación. Es decir, solo el valor REQUEST es aplicado por defecto.
Si el contenedor no puede encontrar el recurso solicitado por una petición, ningún filtro correspondiente a la
misma se invoca. Los filtros solo se invocan si existe el recurso original.
4.4.¿Cómo procesar los requerimientos y modificar las respuestas?
Una de las características más potentes de los filtros es poder modificar las condiciones de la solicitud y los
resultados de la respuesta. Para hacer esto el filtro puede utilizar los parámetros de tipo ServletRequest y
ServletResponse que recibe en el método doFilter().

118
4.4.1. Cómo modificar la solicitud.
Supongamos que un filtro debe capturar las solicitudes a un servlet que recibe los datos de un cliente desde
un formulario en la página "altacliente.html". El filtro analizará los parámetros de solicitud, los validará, y en la
medida de lo posible intentará normalizarlos al formato apropiado.
El servlet puede ser el siguiente:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletAltaCliente", urlPatterns = {"/altacliente"})
public class ServletAltaCliente extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int codigo = Integer.parseInt(request.getParameter("codigo"));
String nombre = request.getParameter("nombre");
// ....Código para almacenar los datos del cliente.....
response.sendRedirect(request.getContextPath() + "/altaCliente.html");
}
}
Este servlet recupera el código y nombre de un cliente posteados desde un formulario, y los almacena.
Después vuelve a redirigir a la página del formulario para realizar más altas.
El filtro FiltroAltaCliente se encargará de validar que se reciban datos en el código y el nombre y si no es así
devolverá una excepción. También normalizará el nombre eliminando espacios en blanco innecesarios.
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
@WebFilter(filterName = "FiltroAltaCliente", servletNames = {"ServletAltaCliente"})
public class FiltroAltaCliente implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String codigo = request.getParameter("codigo");
String nombre = request.getParameter("nombre");
if (codigo==null || codigo.equals("") || nombre==null || nombre.equals(""))
throw new ServletException("Faltan datos");
// ... se debe normalizar el nombre ....
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig filterConfig) {
}
}
En el método doFilter() primero se recuperan los parámetros y se comprueba que existan y tengan datos. Si no
es así se lanza una excepción. La cuestión es que antes de seguir encadenando la solicitud con
chain.doFilter(request, response), debemos modificar el valor del parámetro "nombre". Pero esto no es algo que
se puede realizar directamente, puesto que los objetos HttpServletRequest no ofrecen ningún método para
modificar el contenido de los parámetros de solicitud, sólo ofrece métodos para recuperar los parámetros.
La estrategia a seguir para conseguir nuestro objetivo es cambiar el objeto request original por otro donde
podamos reescribir los métodos que devuelvan los parámetros de solicitud. Para facilitar la creación de
duplicados de los objetos request y response, Java provee cuatro clases envolventes:
javax.servlet.ServletRequestWrapper
javax.servlet.ServletResponseWrapper
javax.servlet.HttpServletRequestWrapper
javax.servlet.HttpServletResponseWrapper

119
Todas estas clases tienen un constructor que recibeun objetorequest o response y delegan todas las llamadas a
métodos a estos objetos. Extendiendo estas clases, y rescribiendo algunos de sus métodos, podemos
personalizar el proceso de los requerimientos y modificar las respuestas.
Por ejemplo, la siguiente clase se encarga de normalizar el parámetro "nombre" reescribiendo todos los
métodos que permiten recuperar los parámetros de solicitud.
public class RequestModificado extends ServletRequestWrapper {
public RequestModificado(ServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
if (name.equals("nombre")) {
String nombre = super.getParameter("nombre");
return nombre==null? null : nombre.trim();
}
return super.getParameter(name);
}
@Override
public String[] getParameterValues(String name) {
if (name.equals("nombre")) {
String[] nombre = super.getParameterValues("nombre");
return nombre==null? null : new String[] {nombre[0].trim()};
}
return super.getParameterValues(name);
}
@Override
public Map<String, String[]>getParameterMap() {
Map<String, String[]> mapa=super.getParameterMap();
String[] nombre = mapa.get("nombre");
if (nombre!=null) {
mapa.put("nombre", new String[] {nombre[0].trim()});
}
return mapa;
}
}
Ahora podemos agregar el código adecuado en el método doFilter() del filtro para sustituir al parámetro
request:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String codigo = request.getParameter("codigo");
String nombre = request.getParameter("nombre");
if (codigo == null || codigo.equals("") || nombre == null || nombre.equals("")) {
throw new ServletException("Faltan datos");
}
chain.doFilter(new RequestModificado(request), response);
}
4.4.2. Cómo modificar la respuesta.
El proceso para modificar la respuesta es análogo al de la solicitud. Podemos usar las clases envolventes
HttpServletRequestWrapper y HttpServletResponseWrapper para sustituir al parámetro response reescribiéndole
los métodos adecuados. Aunque no es tan habitual como en el caso de la solicitud, puede ser interesante en
determinados casos derivar el canal de salida de la respuesta a un búfer en memoria y desde el filtro recuperar
el contenido de dicho búfer para componer una respuesta personalizada.
El siguiente ejemplo muestra cómo crear una clase envolvente que permite derivar el canal de salida hacia un
ByteArrayOutputStream:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ResponseWrapper extends HttpServletResponseWrapper {
public ResponseWrapper(ServletResponse response) {

120
super(response);
}
private ByteArrayOutputStream bufer = new ByteArrayOutputStream();
private PrintWriter pwBufer = new PrintWriter(bufer);
private ServletOutputStream outBufer = new ServletOutputStream() {
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) throws IOException {
bufer.write(b);
}
};
@Override
public PrintWriter getWriter() {
// retorna nuestro PrintWriter modificado que escribe a un array de bytes
return pwBufer;
}
@Override
public ServletOutputStream getOutputStream() {
// retorna el stream que escribe a un array de bytes lo que enviemos como respuesta
return outBufer;
}
// retorna el contenido de la respuesta como una array de bytes
public byte[] toByteArray() {
return bufer.toByteArray();
}
}
Pero lo habitual en filtros que modifican la respuesta es realizar redirecciones o recuperar recursos solicitados
desde código y procesar una respuesta directa. Por ejemplo, supongamos el caso de una aplicación web con
una carpeta "imagenes" que contiene archivos de imagen. La aplicación web permite solicitar directamente las
imágenes al navegador, pero queremos crear un filtro que devuelva la imagen solicitada incrustada en una
página web:
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
@WebFilter(filterName = "FiltroImagen", urlPatterns = {"/imagenes/*"})
public class FiltroImagen implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpres = (HttpServletRequest) request;
response.setContentType("text/html; charset=UTF-8");
String rutaImagen = httpres.getContextPath() + httpres.getServletPath();
try (PrintWriter out = response.getWriter()) {
out.println("<html>");
out.println("<body style='background-color: lightblue'>");
out.println("<img src='" + rutaImagen + "'></img>");
out.println("<html>");
out.println("</body>");
out.println("</html>");
}
}
@Override
public void destroy() {

121
}
@Override
public void init(FilterConfig filterConfig) {
}
}
Este filtro captura la solicitud de la imagen y devuelve dinámicamente una página html con un elemento <img
/> que incrusta la imagen en la página. Sin embargo, si probamos este filtro solicitando una de las imágenes
de la carpeta veremos que nos devuelve la página web pero no renderiza bien la imagen.
El problema se debe a que cuando el filtro devuelve una página con una elemento <img /> incrustado, el
navegador hará una solicitud sobre el fichero referenciado en el atributo src. Como se trata de una imagen de
la carpeta "imágenes", esta solicitud volverá a ser procesada por el filtro, lo cual provocará llamadas recursivas.
Para evitar este efecto podemos diferenciar las invocaciones añadiendo un parámetro de consulta a la ruta del
atributo src:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (request.getParameter("filtro") == null) {
HttpServletRequest httpres = (HttpServletRequest) request;
response.setContentType("text/html; charset=UTF-8");
String rutaImagen = httpres.getContextPath() + httpres.getServletPath();
try (PrintWriter out = response.getWriter()) {
out.println("<html>");
out.println("<body style='background-color: lightblue'>");
out.println("<img src='" + rutaImagen + "?filtro'></img>");
out.println("<html>");
out.println("</body>");
out.println("</html>");
}
} else {
chain.doFilter(request, response);
}
}
El filtro evalúa ahora la existencia de un parámetro de la cadena de consulta. La invocación directa de la
imagen no tiene ese parámetro y se devuelve la página html. La solicitud del elemento <img /> incluye el
parámetro y se devuelve directamente la imagen.

PRÁCTICA
01. Crea un servlet denominado servlet.ConVidaTasada. Este servlet, cada vez que sea creado o destruido por
el contenedor Web, deberá escribir en el archivo de log del contenedor web el momento (fecha y hora) en
que fue creado y el momento en que fue destruido.

02. Crea un sitio web usando páginas html y servlets.


Este sitio web debe presentar primero una página HTML generada dinámicamente desde un servlet (llamado
ServletIndex) con un formulario para que el usuario escriba su nombre y un enlace a la págian
"preferencias.html". La página intentará recuperar de una cookie el nombre de último usuario guardado y lo
mostrará en el formulario.
El formulario debe postear el nuevo nombre al sevlet "ServletAutentifica", el cual lo guardará en la cookie.
Este servlet debe redirigir a la página "preferencias.html", donde el usuario podrá editar sus preferencias.
Estas preferencias incluyen el color de fondo de la página y un idioma preferido. Estas preferencias deben ser
almacenadas en el almacén local del navegador y mostradas en la página si se guardaron previamente.

122
123
UNIDAD 14. LA TECNOLOGÍA JSP
1. Fundamentos de JSP
Aunque los servlets son la tecnología base para construir aplicaciones web con Java, presentan varios
inconvenientes:
• Los servlets no se invocan directamente, responden a solicitudes que casan con sus patrones URL.
• Los servlets integran en un mismo método la invocación de código de la lógica del negocio del código que
genera la presentación de páginas. Esto hace difícil aplicar patrones de diseño que separan ambas
funcionalidades.
• Cuando un servlet debe responder con la generación de páginas dinámicas complejas se hace difícil
mantener y modificar su contenido.
Como solución a estos problemas surge la tecnología JSP (Java Server Pages). Una página JSP es un fichero
de texto que combina el lenguaje de marcas HTML/XML y el lenguaje de programación Java para generar
una respuesta dinámica ante la petición de un cliente. Las páginas JSP son usadas por el contenedor de
servlets para generar dinámicamente el código fuente de un servlet.
Básicamente, un archivo JSP es un archivo de texto con la extensión .jsp que contiene: texto, etiquetas
HTML, y construcciones JSP dinámicas (código jsp entre delimitadores <% y %>)
1.1. Sintaxis de las páginas JSP.
Si en NetBeans agregamos una página JSP a un proyecto web obtendremos el siguiente contenido:
Figura 1

Si comparamos este contenido con el de la página index.html podemos comprobar que en la práctica el
fichero JSP tiene el contenido de una página HTML, excepto por la instrucción:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
Esta instrucción no pertenece a la sintaxis HTML, sino que es una sintaxis propia de JSP. En este caso
corresponde a una directica de compilación que determina el tipo de contenido que genera este fichero JSP.
La sintaxis JSP se embebe por el medio de la sintaxis HTML definiendo diversos bloques de código con
funcionalidades diversas. Los bloques JSP son analizados y evaluado por un proceso del contenedor web
denominado motor JSP. El motor JSP genera a partir del código HTML y el código JSP instrucciones para
crear el código fuente de un servlet. La URI del fichero JSP queda asignada como patrón URL de este servlet
dinámico.
A continuación se indican las etiquetas JSP que podemos embeber con el código HTM y su significado:
Etiqueta JSP Significado Sintaxis
Directiva Especifican instrucciones que son ejecutadas por el motor <%@ Directivas %>
JSP.
Declaración Declara y define métodos y variables a nivel de instancia. <%! Declaraciones %>

124
Scriptlet Incluye variables e instrucciones a nivel local. <% código Java %>
Expresión Escribe algo en el código HTML evaluando una expresión <%= una expresión
de Java. %>
Acción Provee instrucciones para el motor JSP. <jsp:nombreAcción />
Comentario Se usa para documentación y comentarios. <%-- algún texto --%>
1.1.1. Directivas JSP.
Una directivaafecta a la estructura general de la clase servlet. Normalmente tienen la siguiente forma:
<%@ directiva atributo="valor" %>
Sin embargo, también podemos combinar múltiples selecciones de atributos para una sola directiva, de esta
forma:
<%@directiva atributo1="value1" atributo2="value2" ... atributoN="valueN" %>
Existen tres directivas JSP, y proporcionan al motor JSP información general acerca de las páginas. Estas
directivas son:
•<%@page %>, informa al motor JSP acerca de varias propiedades de la página. Se utiliza para importar
clases, personalizar la superclase del servlet, etc. A continuación se explican algunos de sus atributos:
<%@ page import="java.util, java.util.*" %>, para importar clases.
<%@ page contentType="text/plain" %>, para especificar el tipo MIME de la salida.
<%@ page isThreadSafe="true|false" %>, para indicar si el procesamiento del servlet es normal o que
debe implementar SingleThreadModel.
<%@ page session="true|false" %>, para indicar si deben usarse o no sesiones.
<%@ page buffer= "sizekb|none" %>, especifica el tamaño del buffer para el objeto out. El valor por
defecto es específico del servidor, debería ser de al menos 8kb.
<%@ page autoflush= "true|false" %>, un valor de true (por defecto) indica que el buffer debería
descargase cuando esté lleno. Un valor de false, raramente utilizado, indica que se debe lanzar una
excepción cuando el buffer se sobrecargue.
<%@ page extends= "package.class" %>, indica la superclase del servlet que se va a generar. Debemos
usarla con extrema precaución, ya que el servidor podría utilizar una superclase personalizada.
<%@ page info= "message" %>, define un string que puede ser recuperado mediante el método
getServletInfo().
<%@ page errorPage= "url" %>, especifica una página JSP que se debería mostrar si se produce
cualquier excepción o error no capturado en la página actual.
<%@ page isErrorPage="true|false" %>, indica si la página actual actúa o no como página de error de
otra página JSP. El valor por defecto es false.
<%@ page language="java" %>, especifica el lenguaje a utilizar.
•<%@include %>, permite insertar el contenido de un fichero en el momento que el fichero JSP es
traducido al servlet. Su sintaxis completa es:
<%@include file="url relativa" %>
La dirección URL es relativa a la página JSP de referencia. Los contenidos del fichero incluido son
analizados como texto normal JSP, y así pueden incluir código HTML, elementos de script, directivas y
acciones.
•<%@taglib %>,permite referencias librerías de etiquetas personalizadas.
1.1.2. Declaraciones.
Los bloques declarativos declaran variables y métodos que se convierten en miembros de la clase del servlet
dinámico generado. Por ejemplo,
<%! int count = 0; %>
crea una variable de instancia que es inicializada a cero sólo la primera vez que el motor JSP carga la página.
1.1.3. Scriptlets.
Son fragmentos de código Java que se copian tal cual en el método de invocación del servlet dinámico
generado. Por ejemplo,
<% int count = 0; %>
declara una variable local de método, que es inicializada a cero cada vez que la página es invocada.
1.1.4. Expresiones.
Se utiliza este bloque para renderizar en la respuesta el resultado de una expresión de Java. Por ejemplo,
<%= "valor = " + 5 %>
equivale a la instrucciónout.print("valor = 5"); que se encarga de escribir en la página resultante el texto:

125
valor = 5.
La expresión siempre será evaluada, según su tipo, antes de generar la salida. Y es importante dejar un espacio
entre el signo igual y la expresión posterior.
Nota. La expresión dentro de <%= %> debe evaluarse a un valor de tipo primitivo o a un objeto. Si se
evalúa a un objeto se escribirá en la salida lo que retorne su método toString().Nótese que la expresión
NO debe finalizarse con punto y coma.
1.1.5. Acciones o etiquetas JSP.
Son comandos que obligan al motor JSP a realizar ciertas tareas durante la ejecución de la página. La siguiente
tabla resume la sintaxis de las acciones disponibles:
Acción Sintaxis Interpretación
include <jsp:includepage="url" /> Incluye un fichero en el momento en que la página es
solicitada.
forward <jsp:forwardpage="url"/> Reenvía la petición a otra página.
useBean <jsp:useBean> Encuentra o crea un objeto JavaBean.
</jsp:useBean>
setProperty <jsp:setProperty /> Asigna propiedades a un JavaBean.
getProperty <jsp:getProperty/> Recupera el valor en una propiedad de un JavaBean.
plugin <jsp:plugin> Genera etiquetas OBJECT o EMBED, apropiadas al tipo de
</jsp:plugin> navegador, pidiendo que se ejecute un applet usando el
Java Plugin.
El uso de estas acciones se irá explicando en secciones posteriores.
1.1.6. Comentarios.
Los comentarios JSP se encapsulan entre etiquetas <%-- y --%>. Para evitar confusiones no deberían
mezclarse los comentarios JSP con los comentarios HTML y de código Java.
<%@ page contentType="text/html" pageEncoding="UTF-8" %>
<html>
<body>
<!-- comentario HTML (es enviado a la salida) -->
<%-- comentario JSP (no es enviado a la salida) --%>
</body>
</html>
1.1.7. Importación de paquetes y clases para un JSP.
Se utiliza la directiva @page para generar instrucciones import en tiempo de traslación del JSP al servlet
dinámico. Por ejemplo, si en un scriptlet queremos instanciar un objeto java.util.Date podemos importar la
clase de dos formas:
<%@ page import="java.util.Date" %>
<html>
<body>
<%
java.util.Date fecha1 = new java.util.Date();
Date fecha2 = new Date();
%>
</body>
</html>
En la primera instrucción de asignación de un Date se utiliza la importación explícita escribiendo la ruta de
paquetes a la que pertenece la clase. En la siguiente instrucción se utiliza directamente la clase Date, y en este
caso se produce una importación implícita determinada por el atributo import de la directiva <%@page %>.
Para importar un paquete entero se utiliza:
<%@ page import="java.util.*" %>
Si queremos realizar varias importaciones se pueden utilizar varias directivas @page o una única directiva
@page con uno o varios atributos import:
<%@ page import="java.text.*" %>
<%@ page import="java.sql.* , java.util.Date" %>
<%@ page import="java.uitl.Math" import="java.util.ArrayList" %>

126
Nota. Varias importaciones en una misma directiva @page se separan con comas dentro de cada atributo
import.
1.2. Páginas JSP como documentos XML.
Para dar más uniformidad al código de las páginas JSP, donde se mezclan etiquetas HTML con bloques
scriptlet, la especificación JSP define una sintaxis alternativa basada en el estándar XML.
Un ejemplo de sintaxis XML para las páginas JSP es el siguiente:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2">
<html>
<body>
<jsp:directive.page language="java" />
<jsp:declaration> int count = 0; </jsp:declaration>
<jsp:scriptlet> count++; </jsp:scriptlet>
<jsp:text> ¡Hola! Eres el visitante número </jsp:text>
<jsp:expression> count </jsp:expression>
</body>
</html>
</jsp:root>
A continuación se resume esta sintaxis XML para los diversos elementos JSP:
• Expresión JSP.
<jsp:expression> expresión de Java</jsp:expression>
Evalúa una expresión de código Java y la renderiza en la respuesta.
• Scriptlet.
<jsp:scriptlet> instrucciones de Java</jsp:scriptlet>
Las instrucciones de este bloque de insertan dentro del código del método _jspService() del servlet.
• Declaración JSP.
<jsp:declaration>declaración de variables y métodos de Java</jsp:declaration>
El código de este bloque se inserta cómo código en el cuerpo de la clase del servlet. .
• Directiva page.
<jsp:directive.page atributo="valor"/>
Define una directiva para la generación del código fuente del servlet dinámico. Los atributos que admite
son:
import="paquetes.Clase" Genera instrucciones import.
contentType="MIME-Type" Establece el tipo de contenido de la página.
isThreadSafe="true|false" Establece el modelo de hilos de la página.
session="true|false" Establece si se gestionarán sesiones.
buffer="sizekb|none" Establece el tamaño del búfer para el objeto out.
autoflush="true|false" Establece si el búfer de respuesta debe descargarse automáticamente.
extends="paquetes.Clases" Establece la superclase del servlet dinámico.
info="message" Establece información sobre la página.
errorPage="url" Establece la página de error de esta página.
isErrorPage="true|false" Establece que es una página de error.
language="java" Establece el lenguaje del código.
• Directiva include.
<jsp:directive.include file="url"/>
Incluye el código de un fichero del sistema local cuando la página se traslada al servlet dinámico. La url se
interpreta siempre como una ruta relativa.
Los comentarios y acciones JSP tienen la misma sintaxis en el formato XML.
1.3. Ciclo de vida de las páginas JSP.
La primera vez que un cliente solicita la página JSP, el motor JSP la analiza y genera el fichero fuente de una
clase de servlet. Este fichero fuente es compilado para crear la clase del servlet dinámico, de la cual se creará
una instancia que atenderá la solicitud. Esta instancia puede retornar como respuesta una página HTML.
En las siguientes peticiones, el motor JSP verifica el tiempo de expiración de la página y la correspondiente
instancia de servlet para determinar si la página es nueva y si tiene que ser trasladada otra vez. Si el servlet
dinámico es válido se invoca directamente para servir la respuesta.

127
La clase servlet generada desde el JSP implementa la interfaz javax.servlet.jsp.HttpJspPage, que extiende la
interfaz javax.servlet.jsp.JspPage. La interfaz JspPage declara los métodos jspInit() y jspDestroy(), mientras que
HttpJspPage declara el método _jspService(). Estos métodos son conocidos como los métodos del ciclo de
vida de las páginas JSP , y son equivalentes a los métodos init(), service() y destroy() de los servlets normales.
Figura 2

En total se diferencian siete fases en el ciclo de vida de un JSP:


1) Traslación de la página: la página JSP es leída, reconocida y validada, y se crea un archivo fuente de Java
conteniendo la correspondiente clase servlet.
2) Compilación de la página: el archivo Java es compilado.
3) Carga de la clase: la clase compilada es cargada en memoria.
4) Creación de la instancia: se crea una instancia HttpJspPage de la clase del servlet.
5) Ejecución de jspInit(): este método es llamado una sola vez para realizar las inicializaciones necesarias (las
incluidas en las declaraciones JSP).
6) Ejecución de _jspService(): este método es llamado para cada requerimiento.
7) Ejecución de jspDestroy(): este método es llamado cuando el contenedor de servlets decide dejar al
servlet fuera de servicio.
Podemos forzar la pre-compilación de una página JSP desde el navegador sin ejecutarla con el parámetro
jsp_precompile:
http://localhost:8080/pagina.jsp?jsp_precompile=true
Hay que tener en cuenta que el prefijo jsp está reservado y no debe usarse para nombrar parámetros definidos
por el usuario.
1.4. Proceso de traslación de JSP a Servlet.
Como se ha explicado, la primera vez que se invoca un JSP debe generar y compilar el código fuente de una
clase de tipo HttpJspBase. Podemos ver el contenido de esta clase usando los asistentes de NetBeans.
Por ejemplo, si añadimos una página index.jsp a nuestro proyecto web y la ejecutamos, se creará el fichero
index_jsp.java. Podemos ver el contenido de esta clase usando la opción «Ver servlet» del menú contextual
haciendo clic sobre el nodo «index.jsp».

128
Figura 3

Las reglas que aplica el motor JSP para crear el código fuente de este servlet son las siguientes:
•Las directivas JSP son usadas por el motor JSP para generar algunas instrucciones de compilación. Por
ejemplo, el atributo import de la directiva page provoca instrucciones import en el fichero fuente.
• Todas las instrucciones de los bloques declarativos JSP<!% %> son trasladas tal cual como instrucciones
a nivel de instancia de la clase generada.
•Las instrucciones de los bloques scriptlets <% %>se trasladan tal dentro del método _jspService() en el
mismo orden.
• Todas las expresiones JSP son enviadas al método _jspService() como instrucciones out.write().
• Las acciones JSP son reemplazadas por llamadas a las clases específicas.
• Los comentarios JSP son ignorados.
• Cualquier otro texto formará parte del método _jspService(), siendo encapsulado como argumento de
out.write().
Vamos a analizar algunas de estas reglas para entender qué podemos hacer y qué no en los bloques JSP de la
página.
• Las directivas JSP generan instrucciones de compilación en la clase del servlet. Esto quiere decir que el
valor de sus atributos no será evaluado como parte del código del servlet, y por tanto sólo podemos usar
valores literales.
Pongamos el caso de la directiva include. Si incluimos el siguiente código en un JSP se producirá un error:
<% String pageURL = "copyright.html"; %>
<%@ include file="<%= pageUrl%>" %>
En el atributo file se asigna una expresión JSP, la cual debe ser evaluada en el contexto de ejecución del
servlet. Este contexto todavía no está establecido cuando se generan las instrucciones de compilación.
Sin embargo, las acciones JSP generan código que será ejecutado por el servlet. Por tanto el siguiente
código será válido:
<% String pageURL = "copyright.html"; %>
<jsp:include page="<%= pageURL%>" />
• Puesto que los símbolos <%@, <%!, <%=, <%, %>, <%-- y --%> forman parte de la sintaxis JSP,
debemos usar los caracteres de escape HTLM (&lt; para < y &rt; para >) para poder generarlos como salida.
El siguiente ejemplo ilustra su uso:
<%@page info="Un ejemplo de uso de los caracteres ' \" \\ <\% y %\> " %>
<body>
<!-- Genera la salida: Un ejemplo de uso de los caracteres ' " \ &lt;% %&gt;% -->
<%= getServletInfo()%>
<br />
El elemento de apertura de un scriptlet es &lt;%
<br />

129
El elemento de cierre de un scriptlet es %&gt;
<br />
<%= "El elemento de apertura de un scriptlet es &lt;%"%>
<br />
<%= "El elemento de cierre de un scriptlet es %&gt;"%>
</body>
1.5. Variables y objetos implícitos.
Si analizamos el código generado para el método _jspService() de un servlet dinámico podemos ver que se
definen unas constantes y variables locales:
public void _jspService(final javax.servlet.http.HttpServletRequest request,
final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;
……………………….
}
Estas variables y constantes son inicializadas para poder acceder a los siguientes objetos:
Variable Clase o interfaz Objeto
application javax.servlet.ServletContext Referencia el contexto de la aplicación web.
session javax.servlet.http.HttpSession Referencia la sesión actual.
request javax.servlet.http.HttpServletRequest El parámetro del requerimiento actual.
response javax.servlet.http.HttpServletResponse El parámetro de respuesta actual.
out javax.servlet.jsp.JspWriter Referencia al canal de salida para la respuesta.
page java.lang.Object Referencia la instancia actual (this).
pageContext javax.servlet.jsp.PageContext Referencia el contexto de la página.
config javax.servlet.ServletConfig Referencia la configuración del servlet.
Al ser parámetros, constantes y variables locales del método _jspService(), todos estos objetos están
disponibles en los bloques scriptlet de las páginas JSP.
Por ejemplo, el siguiente código de una página JSP incluye un formulario para introducir el nombre de
usuario y guardarlo en un atributo de sesión. El formulario se postea sobre la misma página, de forma que al
inicio se intenta recuperar el parámetro con el nombre y guardarlo en la sesión. En el campo de texto se
asigna el nombre posteado.
<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<%
// Se intenta recuperar el parámetro tuNombre
String nombre = request.getParameter("tuNombre");
if (nombre==null)
nombre = "";
else
session.setAttribute("tuNombre", nombre);
%>
<form method="POST">
Tu nombre <input type="text" name="tuNombre" value="<%= nombre %>" />
<input type="submit" value="guardar" />

130
</form>
<a href="seguir.jsp">seguir</a>
</body>
</html>
Figura 4

Si además, a la directiva <%@page %> añadimos el atributo isErrorPage="true" se creará la variable local
exception:
Throwable exception = (Throwable) request.getAttribute("javax.servlet.jsp.jspException");
El uso de esta variable será analizado en el capítulo de redirección a páginas de error.
1.6. Inicialización de los JSP.
Ya que en el fondo, un JSP es un servlet, podemos modificar la inicialización de un JSP incluyendo
parámetros de inicialización y reescribiendo los métodos de su ciclo de vida.
1.6.1. Configuración de parámetros de inicialización.
Podemos configurar parámetros de inicio en nuestro JSP virtualmente de la misma forma que se hace con un
servlet. La única diferencia es que debemos añadir un elemento <jsp-file> dentro de la etiqueta <servlet> del
archivo descriptor.
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>MiJsp</servlet-name>
<jsp-file>/index.jsp</jsp-file>
<init-param>
<param-name>email</param-name>
<param-value>[email protected]</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MiJsp</servlet-name>
<url-pattern>/index.jsp</url-pattern>
</servlet-mapping>
</web-app>
Y ahora podemos recuperar el valor del parámetro email dentro de la página"index.jsp" de varias formas:
<%@page language="java" contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<% out.println("Mi email es "+ this.getServletConfig().getInitParameter("email")); %>
<br/>
<%= "Mi email es " + this.getInitParameter("email") %>
<br/>
Mi email es ${pageContext.servletConfig.getInitParameter("email")}
</body>
</html>

131
Esta página muestra tres líneas con el mismo contenido:
Mi email es [email protected]
Mi email es [email protected]
Mi email es [email protected]
La primera línea se genera mediante un scriptlet, la segunda mediante una expresión JSP, y la tercera mediante
una expresión EL, de las cuales se hablará posteriormente.
1.6.2. Rescritura del método «jspInit».
También podemos realizar inicializaciones en nuestro JSP rescribiendo el método jspInit(), de forma que el
contenedor de servlets invocará este método al comienzo del ciclo de vida de nuestra página. El método
rescrito será invocado desde método init() del servlet dinámico, de forma que en tiempo de ejecución estarán
disponibles los objetos ServletConfig y ServletContext del servlet. Esto significa que podremos invocar los
métodos getServletConfig() y getServletContext() dentro del código de jspInit().
El siguiente ejemplo muestra cómo rescribir el método jspInit() dentro de un scriptlet declarativo de nuestra
página.
<%@page language="java" contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<%!
private String correo;
public void jspInit() {
ServletConfig sConfig = this.getServletConfig();
correo = sConfig.getInitParameter("email");
}
%>
<% out.println("Mi email es " + correo);%>
</body>
</html>
1.7. Contexto de página.
Ya se ha explicado que los servlets permiten almacenar datos en tres ámbitos de gestión de estado: aplicación,
sesión y requerimiento. La tecnología JSP añade un nuevo ámbito de página. Por tanto, todos los objetos
implícitos existen en uno de estos cuatro niveles:
• Los objetos del nivel de aplicación (variable application) son compartidos por todos los componentes de
la aplicación web durante la vida de ésta. Estos objetos son mantenidos por una instancia de la clase
ServletContext.
• Los objetos del nivel de sesión (variable session) son compartidos por todas las peticiones de un mismo
usuario de sesión mientras el cliente esté activo. Estos objetos son mantenidos por una instancia de la clase
HttpSession.
• Los objetos del nivel de requerimiento(parámetro request) son compartidos por todos los componentes
que procesan el requerimiento actual, mientras éste está siendo servido. Estos objetos son mantenidos por
una instancia de la clase HttpServletRequest.
• Los objetos del nivel de página (variable pageContext) son accesibles sólo en la unidad de traslación en
los cuales son definidos. No existen fuera del proceso de un requerimiento en su traslación a servlet. Estos
objetos son mantenidos por una instancia de una subclase de la clase abstracta PageContext. A este nivel se
usan los JavaBeans.
La clase PageContext proporciona los siguientes métodos:
• void setAttribute(String name, Object object, int scope), para asignar un atributo.
• Object getAttribute(String name, int scope), para recuperar un atributo.
• void removeAttribute(String name, int scope), para destruir el objeto referenciado por un atributo
• Enumeration getAttributeNamesInScope(int scope), enumera los atributos de un nivel dado.
En el parámetro scope de estos métodos debemos usar la constante APPLICATION_SCOPE, SESSION_SCOPE,
REQUEST_SCOPE o PAGE_SCOPE.

132
• Object findAttribute(String name), busca por orden un atributo en página, requerimiento, sesión y
aplicación.
• int getAttributesScope(String name), obtiene el nivel en el cual se define un atributo dado.
Como se puede ver, los métodos de PageContext permiten acceder a atributos definidos en cualquier nivel o
ámbito. Crear y recuperar un atributo en el contexto de página es tan sencillo como:
<%
Float valor1 = new Float(42.5); %>
pageContext.setAttribute("unValor1", valor1);
out.println( pageContext.getAttribute("unValor1") );
%>
Para asignar y recuperar un atributo en el contexto de sesión:
<%
Float valor2 = new Float(22.4);
pageContext.setAttribute("unValor2", valor2, PageContext.SESSION_SCOPE);
out.println( pageContext.getAttribute("unValor2", PageContext.SESSION_SCOPE) );
// lo cual es idéntico a: <%= session.getAttribute("unValor2") %>
%>
Para asignar y recuperar un atributo en el contexto de aplicación:
<%
Float valor3 = new Float(22.4);
pageContext.setAttribute("unValor3", valor3, PageContext.APPLICATION_SCOPE);
out.println( pageContext.getAttribute("unValor3", PageContext.APPLICATION_SCOPE) );
// lo cual es idéntico a: <%= aplication.getAttribute("unValor2") %>
%>
Si no sabemos en qué contexto existe un atributo podemos usar el método findAttribute(). Este método
retorna null si no encuentra el atributo en ningún contexto, o bien retorna su valor en el primer contexto
donde exista.
<%= pageContext.findAttribute("unValor2") %>
El método busca el atributo primero en el contexto de página, si no lo encuentra lo busca en el contexto de
solicitud, después en el de sesión y por último en el de aplicación.
1.8. Manejar excepciones en páginas JSP.
Al hablar de servlets habíamos explicado el mecanismo de gestión automática de errores, el cual permite al
contenedor web redirigir la respuesta a una página personalizada de error si se producía una excepción en la
ejecución del código o si se enviaba un código de error con el método sendError().
Las páginas JSP amplían este mecanismo con las siguientes opciones:
• Podemos crear una página JSP de error personalizado asignando el atributo isErroPage="true" en la
directiva <%@page %>. Esto provoca que en el servlet dinámico se cree la variable local exception, la cual
contendrá una referencia a la excepción que invoque esta página de error. Esta variable se define así:
Throwable exception = (Throwable) request.getAttribute("javax.servlet.jsp.jspException");
• Podemos establecer en cada página JSP quién es su página de error personalizada asignándola en el
atributo errorPage de la directiva <%@page %>.
Para ilustrar este mecanismo crearemos primero una página JSP de error:
<%-- PÁGINA error.jsp --%>
<%@page isErrorPage="true" contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>Se ha producido un error</h1>
<div><%= exception==null? "Error desconocido." : exception.toString() %></div>
<a href="index.jsp">Retornar</a>
</body>
</html>

133
En esta página se establece el atributo isErrorPage="true", lo que permitirá el uso de la variable exception para
mostrar el mensaje de la excepción producida.
Ahora crearemos una clase de repositorio para almacenar las preferencias de un usuario:
package servicio;
import java.util.*;
public class Repositorio {
private Map<String, String> preferencias = new HashMap<>();
public void addPreferencia(String nombre, String valor) {
if (nombre.isEmpty() || valor.isEmpty())
throw new RuntimeException("Faltan los datos de la preferencia");
preferencias.put(nombre, valor);
}
public Set<String> nombresPreferencias() {
return preferencias.keySet();
}
public String getPreferencia(String nombre) {
return preferencias.get(nombre);
}
}
El método addPreferencia() lanzará una excepción si los datos están vacíos. A continuación, la página
"index.jsp" mostrará un formulario para añadir una preferencia de usuario a un repositorio que se creará en un
atributo de sesión:
<%-- PÁGINA index.jsp --%>
<%@page import="servicio.Repositorio"%>
<%@page errorPage="error.jsp" contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<%
String nombre = request.getParameter("nombre");
String valor = request.getParameter("valor");
if (nombre!=null && valor!=null) {
Repositorio repositorio = (Repositorio) session.getAttribute("repositorio");
if (repositorio==null) {
repositorio = new Repositorio();
session.setAttribute("repositorio", repositorio);
}
repositorio.addPreferencia(nombre, valor);
}
%>
<form method="POST">
Preferencia <input type="text" name="nombre" />
<br />
Valor <input type="text" name="valor" />
<br />
<input type="submit" value="guardar" />
</form>
</body>
</html>
El formulario se postea sobre la misma página, por ello al inicio se ejecuta un scriptlet que recupera los datos
de preferencia y los añade a un repositorio guardado en un atributo de sesión. El código garantiza que se
creará el repositorio la primera vez que se use.

134
Figura 5

2. Reutilización de componentes web


Respecto a los JSP, reutilizar un componente web significa incluir su contenido o la salida de otro
componente web en una página JSP. Eso se puedo realizar de dos modos: estáticamente o dinámicamente.
2.1. Inclusión estática.
Una inclusión estática implica que el contenido de otro archivo es incluido en un archivo JSP en el momento
de su traslación a servlet.
Por ejemplo, si en varias páginas queremos incluir una cabecera común, podemos crear el siguiente fichero:
<%-- FICHERO cabecera.jsp --%>
<div class="cabecera">
<h2>CABECERA DE LA EMPRESA</h2>
</div>
Como se hará una inclusión estática literal del contenido, en este fichero se prescinde de las etiquetas HTML
que definen cabeceras y cuerpo de la página.
Ahora en cualquier otra página de la aplicación aplicaremos la inclusión de la cabecera:
<%-- FICHERO index.jsp --%>
<%@page contentType="text/html" pageEncoding="UTF-8 %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<jsp:directive.include file="cabecera.jsp" />
<div class="cuerpo">
CUERPO DE LA PÁGINA
</div>
</body>
</html>
El archivo incluido puede tener instrucciones HTML, JSP y XML, o ser un simple fichero de texto. El
contenido formará parte del archivo JSP, junto al resto de su propio código, en la posición donde se hace la
inclusión.
La inclusión estática implica que:
• El valor del atributo file de la directiva include no puede ser una expresión que deba evaluarse, debe ser
una URL literal.

135
• Además, esta URL no puede recibir parámetros; es decir, "miURL?x=4" no es una ruta válida para la
directiva include.
2.2. Inclusión dinámica.
Una inclusión dinámica implica que cuando la página JSP es requerida, envía a su vez un requerimiento a otro
recurso, y la respuesta de ese recurso es incluida en la respuesta de la página JSP. Se utilizan las acciones
<jsp:include> y <jsp:forward> para implementar la inclusión dinámica. Su sintaxis completa es:
<jsp:include page="url relativa" flush="true" />
<jsp:forward page="url relativa" />
El recurso demandado en la url puede ser un componente estático o dinámico, incluido un servlet. La url del
recurso puede obtenerse en tiempo de ejecución evaluando una expresión. Por ejemplo, es válido:
<% String paginaURL = "otro.jsp" %>
<jsp:include page="<%=paginaURL%>" />
Funcionalmente, <jsp:include /> es equivalente al método RequestDispatcher.include(). Esta acción delega
temporalmente el proceso de respuesta al recurso incluido; una vez que el recurso finaliza, transfiere de nuevo
el control a la página que lo incluye. Por ejemplo, si creamos las siguientes páginas JSP:
<%-- FICHERO index2.jsp --%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>CONTENIDO DE INDEX2</h1>
</body>
</html>
Esta página será incluida en la siguiente página:
<%-- FICHERO index.jsp --%>
<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>CABECERA DE INDEX</h1>
<jsp:include page="index2.jsp" />
<h1>PIE DE INDEX</h1>
</body>
</html>
Si ejecutamos la página index.jsp obtendremos la siguiente salida en un navegador:
CABECERA DE INDEX
CONTENIDO DE INDEX2
PIE DE INDEX
Es decir, obtenemos la respuesta esperada de una inclusión.
Funcionalmente, la acción<jsp:forward> es equivalente al método RequestDispatcher.forward(). Esta acción
delega totalmente el proceso de respuesta al recurso redirigido. Si en el ejemplo previo sustituimos
<jsp:include page="index2.jsp" /> por <jsp:forward page="index2.jsp" />, en el navegador obtendremos:
CONTENIDO DE INDEX2
Es decir, la página redireccionada index2.jsp sustituye totalmente la salida generada desde index.jsp. Pero esto
no quiere decir que no se ejecute cualquier código de index.jsp previo a la acción <jsp:forward />,
simplemente se ignora el contenido del canal de salida.
En ambas acciones es posible pasar parámetros al recurso redireccionado o incluido. Por ejemplo, podemos
crear un fichero que declare una sección para incluir una cabecera con una imagen y el nombre de nuestra
empresa.
<%-- FICHERO cabecera.jsp --%>

136
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<body>
<table>
<tr>
<td><img src='<%= request.getParameter("imagen") %>'/></td>
<td><h1><%= request.getParameter("empresa") %></h1></td>
</tr>
</table>
</body>
</html>
Este fichero JSP recupera dos parámetros de solicitud: la ruta de una imagen y el texto de la empresa.
Desde una página podemos hacer la inclusión de esta cabecera pasando valores a los parámetros de solicitud:
<%-- FICHERO index.jsp --%>
<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<jsp:include page="cabecera.jsp">
<jsp:param name="imagen" value="logo.gif" />
<jsp:param name="empresa" value="EMPRESA S.A." />
</jsp:include>
<h1>CUERPO DE LA PÁGINA</h1>
</body>
</html>
Hay que tener en cuenta que, además, el recurso incluido o redireccionado compartirá los objetos request y
response de la página, y por tanto tendrá acceso también a los atributos del ámbito de solicitud.
Como conclusión: el recurso incluido o redireccionado y la página JSP que lo llama comparten el mismo
contexto de solicitud.
2.3. Cabeceras y pies de página automáticos.
En el elemento <jsp-config> del archivo descriptor podemos habilitar o deshabilitar diversas configuraciones
para grupos de páginas. Una de estas configuraciones permite establecer una cabecera y pie para grupos de
páginas que serán insertados automáticamente.
Podemos utilizar el editor de NetBeans para crear un nuevo grupo de páginas. Para ello hay que abrir el
fichero web.xml y pulsar la pestaña «Páginas». En la categoría «JSP Property Groups» hay que pulsar el botón
«Add JSP Property Group».

137
Figura 6

En la imagen previa se ha creado un grupo de páginas que incluye a todas las páginas del sitio web (asignando
/* en «URL Pattern(s)»). Se ha incluido una página llamada "cabecera.jsp" como página a incluir en la cabecera
de cada página del grupo, y una página llamada "pie.jsp" como página a incluir en el pie de cada página del
grupo. El código en el fichero web.xml será el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<jsp-config>
<jsp-property-group>
<display-name>Todas las páginas</display-name>
<url-pattern>/*</url-pattern>
<el-ignored>false</el-ignored>
<scripting-invalid>false</scripting-invalid>
<is-xml>false</is-xml>
<include-prelude>/cabecera.jsp</include-prelude>
<include-coda>/pie.jsp</include-coda>
</jsp-property-group>
</jsp-config>
</web-app>
Las páginas "cabecera.jsp" y "pie.jsp" serán incluidas en tiempo de traslación, y por tanto sólo deben incluir
aquel código que queramos embeber, sin etiquetas <html> o <body>. Para este ejemplo usaremos la página
"cabecera.jsp" para incluir estilos CSS:
<%-- FICHERO cabecera.jsp --%>
<style type="text/css">
.menu {
position: fixed;
top: 0%;
left: 0%;
height: 100%;
width: 25%;
background-color: lightblue;
}
.cuerpo {
position: fixed;

138
top: 0%;
left: 25%;
height: 100%;
width: 75%;
background-color: aliceblue;
}
</style>
La página de pie se reservará para un menú de enlaces:
<%-- FICHERO pie.jsp --%>
<div class='menu'>
Menú de enlaces.
</div>
Ahora cualquier página del sitio web tendrá este aspecto:
Figura 7

Otras propiedades que podemos configurar en un grupo de páginas son:


• <el-ignored />, especifica si deben ignorarse o no las expresiones EL de la página. Estudiaremos estas
expresiones EL posteriormente.
• <scripting-invalid>, especifica si la página no debe admitir bloques scriptlet. Un valor a true provocará que
la página genere un error si evalúa cualquier bloque JSP.
• <is-xml>, especifica si debe obligarse a utilizar la sintaxis XML para cualquier bloque JSP.
Si queremos aislar grupos de páginas para aplicarles propiedades distintas bastará con ubicarlas en carpetas
diferentes, y modificar apropiadamente el elemento <url-pattern>/carpeta/*</url-pattern>.
2.4. Inclusión de recursos externos.
El elemento HTML<object> permite también incluir recursos externos dentro de nuestras páginas JSP.
Dependiendo del tipo de recurso puede ser tratado como una imagen, como un navegador embebido, o
como un recurso externo que será procesado por un complemento (como por ejemplo un applet).
El elemento <object> posee los siguientes atributos propios:
data Especifica la dirección del recurso. Debe ser una URL válida.
type Especifica el tipo de recurso. Debe ser un tipo MIME válido.
name Especifica un nombre válido que puede ser usado en el contexto del navegador.
usemap Si el objeto representa una imagen, especifica un mapa de imagen asociado con la imagen.
Es ignorado si el objeto no representa una imagen.
form Se usa para asociar explícitamente un formulario con el objeto.
Los atributos data y type son excluyentes, se debe especificar uno u otro.
En el siguiente ejemplo se muestra cómo embeber un applet de Java dentro de una página usando este
elemento <object>.
<object type="application/x-java-applet">
<param name="code" value="MiAppletClass">
<p>Falta el complemento para ejecutar applets, o está desactivado.</p>
</object>
En el siguiente ejemplo, una página HTML llamada "reloj.html" es embebida dentro de otra:
<html>
<body>

139
<object data="reloj.html"></object>
</body>
</html>
En este último ejemplo, se muestra cómo usar un complemento en HTML (en este caso el complemento
Flash para mostrar un fichero de vídeo). La etiqueta proporciona un subelemento <video> para mostrar el
vídeo en aquellos navegadores que soportan Flash, y un enlace <a> al vídeo para aquellos navegadores que
no soportan Flash.
<object type="application/x-shockwave-flash">
<param name=movie value="http://video.example.com/library/watch.swf">
<param name="allowfullscreen" value="true">
<param name="flashvars" value="http://video.example.com/vids/315981">
<video controls src="http://video.example.com/vids/315981">
<a href="http://video.example.com/vids/315981">Ver vídeo</a>
</video>
</object>

3. Uso de JavaBeans en páginas JSP


Los JavaBeans son componentes de software independientes, portables y reusables que se usan para
ensamblar otros componentes y aplicaciones.
El motor JSP actúa como un contenedor de beans, de forma que una clase que siga las siguientes
convenciones puede ser usada como un JavaBean en páginas JSP:
• La clase debe tener un constructor público sin argumentos.
• Cada propiedad de la clase debe ser accedida por un método setter y/o getterpúblicos, siguiendo la
convención de nombrado de los Java Beans.
3.1. Uso de los JavaBeans.
En las páginas JSP se usan normalmente clases JavaBeans para instanciar objetos que recojan los datos
posteados por un formulario. Un uso habitual de este escenario incluirá los siguientes pasos:
1) En una página JSP crearemos un formulario HTML, dándole un nombre a cada elemento.
2) Creamos una clase bean en un fichero .java, definiéndole tantas propiedades (con métodos getter y
setter) como parámetros posteará el formulario.
3) En la página que recibe los datos del formulario, añadiremos una etiqueta <jsp:useBean /> para crear y
asignar una instancia del bean.
4) Añadiremos una o varias etiquetas <jsp:setProperty> para recoger los valores del formulario (el bean
necesita un método set correspondiente a cada parámetro de solicitud).
5) Añadiremos una etiqueta <jsp:getProperty> para recuperar y mostrar los datos desde el bean (el bean
necesita un método get correspondiente a cada parámetro de solicitud).
6) Si necesitamos realizar más procesos sobre los datos del usuario, usamos el objeto requesty la instancia
del bean dentro del código de un scriptlet.
El siguiente ejemplo usa un formulario para introducir un nombre, y como respuesta nos saludan después del
formulario. Los datos del formulario los recogeremos en el bean UsuarioBean. La acción del formulario será
llamar a la propia página, y por ello condicionaremos el uso del bean a recibir previamente datos a través del
parámetro de llamada nombreUsuario.
A continuación se muestra el código de la clase UsarioBean:
package bean;
public class UsuarioBean implements java.io.Serializable {
private String nombreUsuario;
public String getNombreUsuario() {
return nombreUsuario;
}
public void setNombreUsuario(String nombreUsuario) {
this.nombreUsuario = nombreUsuario;
}
}
Ahora el fichero con el formulario:
<%-- FICHERO index.jsp --%>
<%@page contentType="text/html" pageEncoding="UTF-8" %>

140
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<%-- EL FORMULARIO EMPIEZA AQUÍ --%>
<h1>¿Cómo te llamas?</h1>
<form>
<input type="text" name="nombreUsuario">
<input type="submit" value="Envíar">
</form>
<%-- EL FORMULARIO ACABA AQUÍ --%>
<%-- SI RECIBIMOS PARÁMETRO RELLENAMOS EL BEAN E INCLUIMOS EL SALUDO --%>
<% if (request.getParameter("nombreUsuario") != null) { %>
<jsp:useBean id="mibean" class="bean.UsuarioBean" />
<jsp:setProperty name="mibean" property="*" />
<h2>¡HOLA, <jsp:getProperty name="mibean" property="nombreUsuario" />! </h2>
<% }%>
</body>
</html>
En este ejemplo, la etiqueta <jsp:useBean> se encarga de instanciar un objeto del tipo bean.UsuarioBean. La
etiqueta <jsp:setProperty> se encarga de poblar las propiedades de este objeto con cualquier parámetro de
solicitud que coincida en nombre con una propiedad. Y la etiqueta <jsp:getProperty> se encarga de recuperar
el valor de la propiedad nombreUsuario y mostrarla en la página.
3.2. Acciones JSP para usar JavaBeans.
Veamos ahora con mayor detalle el funcionamiento de estas etiquetas.
3.2.1. La acción «useBean».
La acción <jsp:useBean /> nos permite cargar y utilizar un JavaBean en la página JSP. Su sintaxis habitual es
la siguiente:
<jsp:useBean id="nombre" class="paquetes.Clase" scope="page" />
Donde
•id, especifica el nombre de la variable a la que se referirá el objeto bean. Como <useBean /> genera una
variable de java con el nombre especificado, éste debe ser un nombre válido de variable.
•class, especifica la clase de la variable a la que se referirá el objeto bean. Como <useBean /> utiliza técnicas
de reflexión, debe ponerse el nombre completo de la clase (con su ruta de paquetes).
•scope, indica el contexto en el que el bean debería estar disponible. Tiene cuatro posibles valores: page (es
el valor por defecto, e indica que el bean está disponible sólo para la página actual), request (está disponible
sólo en la solicitud actual), session (está disponible en la sesión actual), y application (está disponible en todas
las páginas que compartan el mismo ServletContext).
Cuando el motor JSP evalúa esta etiqueta se ejecutan los siguientes pasos:
1) Si existe un atributo de ámbito con el nombre especificado en id, lo recupera y lo asigna a una variable
local de Java llamada igual que el nombre especificado en id.
2) Si no existe el atributo en el ámbito especificado, instancia la clase bean y crea un atributo en el ámbito y
una variable de Java con el nombres especificado en el id.
3) Si es la primera vez que se instancia el bean ejecuta el cuerpo del elemento <jsp:useBean />.
Por ejemplo, para el siguiente elemento:
<jsp:useBean id="mibean" class="bean.UsuarioBean">
</jsp:useBean>
El código Java generado en el servlet dinámico es el siguiente:
bean.UsuarioBean mibean = null;
mibean = (bean.UsuarioBean) _jspx_page_context.getAttribute("mibean", PageContext.PAGE_SCOPE);
if (mibean == null) {
mibean = new bean.UsuarioBean();
_jspx_page_context.setAttribute("mibean", mibean, PageContext.PAGE_SCOPE);
}

141
3.2.2.¿Cómo inicializar las propiedades de los beans?
Los beans son instanciados por el motor JSP usando un constructor sin argumentos, por ello no se pueden
inicializar sus propiedades durante la instanciación. Sin embargo, si tenemos este código:
<jsp:useBean id="mibean" scope="session" class="bean.UsuarioBean" >
<%
mibean.setNombreUsuario("Pedro");
%>
<%-- o bien --%>
<jsp:setProperty name="mibean" property="nombreUsuario" value="Pedro" />
</jsp:useBean>
Ocurre lo siguiente: si al nivel de sesión existe el bean mibean, el cuerpo de la acción <jsp:useBean /> es
ignorado; si no existe el bean, es instanciado a nivel de sesión y se ejecuta el código del cuerpo (en este caso
asignamos valor a la propiedad nombreUsuario de una de las manera indicadas).
Por tanto, dentro del cuerpo de la acción useBean podemos escribir cualquier código que será ejecutado la
primera vez que se instancie el bean.
3.2.3. Cambiar las propiedades usando «setProperty».
Se usa la acción <jsp:setProperty /> para asignar valores a las propiedades de los beans. Usa estos atributos:
name es el nombre del bean tal como es identificado en el atributo id dejsp:useBean.
property es el nombre de la propiedad a la cual se quiere asignar valor.
value es el valor que se quiere asignar.
param es el nombre del parámetro disponible en el HttpServletRequest del cual se tomará el valor.
Por ejemplo, en el siguiente elemento:
<jsp:setProperty name="mibean" property="nombreUsuario" value="Pedro" />
Se inicializa la propiedad nombreUsuario del bean mibean al valor "Pedro". El valor puede ser un literal o una
expresión que pueda evaluarse. Si queremos asignar la propiedad desde un parámetro de solicitud se utiliza el
atributo param:
<jsp:setProperty name="mibean" property="nombreUsuario" param="usuario" />
En este caso se asigna la propiedad nombreUsuario del bean mibean con el valor del parámetro de solicitud de
nombre usuario, si es que existe.
Si no se indica un atributo value ni param, el motor JSP busca un parámetro de solicitud con el mismo
nombre que la propiedad del bean y usa su valor.
Nota.Los parámetros con más de un valor son convertidos en un array, y por tanto debemos recuperarlo
en una propiedad de tipo array.
Si queremos rellenar automáticamente todas las propiedades del bean a través de parámetros de llamada
podemos usar:
<jsp:setProperty name="mibean" property="*" />
El modo de funcionar de esta acción es la siguiente:
• Se recuperan todos los nombres de parámetros de invocación.
• Por cada nombre de parámetro de invocación:
- En la clase del objeto especificado en el atributo name se busca un método accesor que se corresponda
con el nombre. Por ejemplo, si el parámetro se llama "propiedad1", se busca el método accesor
"setPropiedad1".
- Si se encuentra el método accesor se invoca sobre el objeto, y se le pasa como argumento el valor del
parámetro.
Si es necesario este valor es convertido al tipo del argumento del método accesor. Para realizar la
conversión de tipos se utiliza un editor de propiedades (java.beans.PropertyEditor) registrado en la clase
java.beans.PropertyEditorManager. Existen editores por defecto para los tipos primitivos de Java (boolean,
byte, short, int, long, float y double), y para las clases java.lang.String, java.awt.Color, y java.awt.Font.
Si no se encuentra el editor de propiedades correspondiente se lanza una excepción.
El siguiente ejemplo de código muestra cómo crear un editor de propiedades personalizado para procesar
datos del tipo java.util.Date pasados desde un formulario en el formato año-mes-día.
package bean;
import java.beans.PropertyEditorManager;
import java.beans.PropertyEditorSupport;
import java.text.SimpleDateFormat;
import java.text.ParseException;

142
public class EditorDateUTC extends PropertyEditorSupport {
public void setAsText(String fecha){
Date d = null;
try {
d= new SimpleDateFormat("yyyy-MM-dd").parse(fecha);
} catch (ParseException ex) {
}
super.setValue(d);
}
public String getAsText() {
Date d = (Date) super.getValue();
if (d == null) {
return "";
} else {
return new SimpleDateFormat("yyyy-MM-dd").format(d);
}
}
}
Podemos registrar esta clase en una clase oyente del inicio de la aplicación:
package oyentes;
import bean.EditorDateUTC;
import java.beans.PropertyEditorManager;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ObservadorContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
PropertyEditorManager.registerEditor(java.util.Date.class, EditorDateUTC.class);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
3.2.4. Acceder a las propiedades usando «getProperty».
Se usa la acción <sp:getProperty />para recuperar los valores de las propiedades de los beans. Por ejemplo:
<jsp:getProperty name="mibean" property="nombreUsuario" />
Esta acción genera el valor de la propiedad como salida de la página JSP, pero no utiliza un editor de
propiedades. De hecho esta acción es equivalente a:
<% out.print(mibean.getNombreUsuario()); %>
Esto también implica que no puede usarse <jsp:getPropety /> como expresión de evaluación. Por ejemplo, el
siguiente código da error al ejecutarlo dentro de un fichero JSP:
<jsp:include page='<jsp:getProperty name="mibean" property="nombreUsuaro" />.txt' />
Para este caso deberemos utilizar:
<jsp:include page='<%= mibean.getNombreUSuario() %>.txt' />
3.3. Serialización de JavaBeans.
Podemos mantener la existencia de un JavaBean utilizando el mecanismo de serialización de objetos de Java.
Para ello, la clase del bean debe implementar la interfaz java.io.Serializable. Entonces podemos guardarlo en
un archivo usando la clase java.io.ObjectOutputStream.
Los archivos donde se copia un bean serializado deben tener extensión .ser. Y el archivo debe ubicarse dentro
del directorio raíz de la aplicación (normalmente se hará en /WEB_INF/classes o un subdirectorio del mismo).
El siguiente ejemplo de código guarda un objeto UsuarioBean (que es una clase serializable) en el archivo
"bean1.ser" dentro de la carpeta "WEB-INF/classes/misBeans":
<%@ page import="bean.UsuarioBean, java.io.* " %>
<%
String mensaje=null;
//Creamos una instancia.
UsuarioBean mibean = new UsuarioBean();
String rutaRelativa = "/WEB-INF/classes/misBeans/bean1.ser";
String rutaReal = application.getRealPath(rutaRelativa);

143
//Serializamos el objeto en el archivo
try (ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(rutaReal)) {
oout.writeObject(mibean);
oout.close();
mensaje = "Se ha guardado el bean en " + rutaReal;
} catch(Exception e) {
mensaje = "Error: no se pudo guardar el bean";
}
%>
<html>
<body>
<h3><%= message %></h3>
</body>
</html>
Para recuperar el objeto serializado, podemos usar ahora el siguiente código:
<%@ page import="SerializableBean" %>
<html>
<body>
<jsp:useBean id="elBean" type="UsuarioBean" beanName="misBeans.bean1" />
</body>
</html>
En el atributo beanName de la etiqueta <jsp:useBean> se asigna la ruta del archivo serializado como si fuese
una clase más de Java ubicada dentro de la carpeta "/WEB-INF/classes" y el paquete "misBeans" (nótese que no
se añade la extensión .ser). Es necesario especificar el atributo type con la clase del bean o bien una superclase
o bien una interfaz que implemente la clase del bean.
3.4. Uso de beans instanciados desde páginas JSP en Servlets.
Las acciones JSP no se pueden utilizar en el código Java de un servlet, pero si podemos trabajar con las
instancias creadas desde una acción useBean. A fin de cuentas, la acción useBean recupera o instancia objetos
en los ámbitos del contenedor web, y estos ámbitos están disponibles también en los servlets.
Como ejemplo de esto, veremos cómo implementar una sencilla aplicación de carro de compra usando
páginas JSP y servlets. Esta aplicación incluirá dos beans, primero una clase Compra para almacenar la fecha y
descripción de una compra:
package bean;
import java.text.*;
import java.util.Date;
public class Compra implements java.util.Serializable {
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
private Date fecha;
private String descripcion;
public Date getFecha() { return fecha; }
public void setFecha(Date fecha) { this.fecha = fecha; }
public String getFechaStr() { return fecha == null ? "" : dateFormat.format(fecha); }
public String getDescripcion() { return descripcion; }
public void setDescripcion(String descripcion) { this.descripcion = descripcion; }
}
Por comodidad se ha incluido una propiedad de solo lectura fechaStr que retorna la fecha en el formato "año-
mes-día".
Y una clase ServcioCompra para persistir las compras en cada sesión.
package bean;
import java.util.*;
public class ServicioCompra {
private List<Compra> compras = new ArrayList<>();
public void addCompra(Compra compra) {
this.compras.add(compra);
}
public List<Compra> getCompras() {
return compras;
}
public void persistirCompras() {

144
// código para almacenar las compras.
}
}
Figura 8

La página "altaCompras.jsp" presentará el formulario para realizar una compra, para mostrar las compras
acumuladas y para confirmar las compras.
<%@page import="bean.Compra"%>
<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<jsp:useBean id="servicio" class="bean.ServicioCompra" scope="session" />
<form action="procesaCompra.jsp" method="POST">
<table>
<tr>
<td>Fecha</td><td><input type="date" name="fecha" value="" /></td>
</tr>
<tr>
<td>Descripción</td><td><textarea name="descripcion"></textarea></td>
</tr>
<tr>
<td><input type="submit" value="Agregar" /></td><td></td>
</tr>
</table>
</form>
<h2>Compras realizadas</h2>
<table border="1">
<thead>
<tr><th>Fecha</th><th>Descripción</th></tr>
</thead>
<tbody>
<jsp:scriptlet>for (Compra c : servicio.getCompras()) {</jsp:scriptlet>
<tr>
<td><jsp:expression>c.getFechaStr()</jsp:expression></td>

145
<td><jsp:expression>c.getDescripcion()</jsp:expression></td>
</tr>
<jsp:scriptlet>}</jsp:scriptlet>
</tbody>
</table>
<br />
<form action="confirmar"><input type="submit" value="Confirmar compras" /></form>
</body>
</html>
La primera vez que se ejecute esta página se creará una instancia de la clase bean.ServicioCompra en el ámbito
de sesión. En este formulario los datos serán posteados a la página "procesaCompra.jsp", la cual recuperará los
datos mediante un bean y los insertará en el servicio de compras.
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<jsp:useBean id="servicio" class="bean.ServicioCompra" scope="session" />
<jsp:useBean id="compra" class="bean.Compra" />
<jsp:setProperty name="compra" property="*" />
<jsp:scriptlet>servicio.addCompra(compra);</jsp:scriptlet>
<jsp:forward page="altaCompras.jsp" />
</body>
</html>
Esta página recupera la instancia del servicio del ámbito de sesión, e instancia un objeto bean.Compra que
puebla con los parámetros de solicitud. Inserta la nueva compra en el servicio y vuelve a redirigir a la página
"altaCompras.jsp".
Cuando se pulsa el botón de confirmar un servlet persistirá las compras y las eliminará de la sesión.
package servlets;
import bean.ServicioCompra;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletConfirmar", urlPatterns = {"/confirmar"})
public class ServletConfirmar extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession sesion = request.getSession();
ServicioCompra servicio = (ServicioCompra) sesion.getAttribute("servicio");
servicio.persistirCompras();
sesion.removeAttribute("servicio");
request.getRequestDispatcher("/altaCompras.jsp").forward(request, response);
}
}

4. JSTL y el lenguaje EL
Como se ha visto, las acciones JSP permiten manipular beans y sus propiedades, además de realizar
inclusiones y redirecciones de páginas. Pero se echa en falta acciones que implementen estructuras de
programación, como bucles, selecciones, etc.
La API JSTL responde a la demanda de los desarrolladores proporcionando un nuevo conjunto de acciones
JSP personalizadas para realizar tareas comunes en casi todas las páginas JSP, incluyendo procesamiento
condicional, internacionalización, acceso a bases de datos y procesamiento XML. Esto acelera el desarrollo de
los JSP eliminando la necesidad de elementos de scripting y los inevitables y difíciles de encontrar errores de

146
sintaxis, y liberando el tiempo anteriormente gastado en desarrollar y aprender trillones de acciones
personalizadas específicas del proyecto para estas tareas comunes.
4.1. Las librerías JSTL.
JSTL especifica un conjunto de librerías de etiquetas basadas en el API JSP 1.2. Hay cuatro librerías de
etiquetas independientes, y cada una contiene acciones personalizadas dirigidas a un área funcional específica.
Esta tabla lista cada librería con su prefijo de etiqueta recomendado y la URI por defecto:
Librería Prefijo URI por defecto Descripción
Core c Contiene acciones para las tareas rutinarias.
http://java.sun.com/jsp/jstl/core
XML Processing x http://java.sun.com/jsp/jstl/xmlContiene acciones para procesamiento
XML.
I18N &Formatting fmt http://java.sun.com/jsp/jstl/fmt Contiene acciones para globalizar y
localizar las aplicaciones Web.
Database Access sql http://java.sun.com/jsp/jstl/sql Contiene acciones para leer y modificar
información almacenada en una base de
datos.
El entorno de desarrollo dispone de estas librerías y nos permite añadirlas a nuestra aplicación accediendo a
su biblioteca.
Figura 9

Para usar las acciones de una librería JSTL, debemos declarar la librería en cada página usando una directiva
taglib. Por ejemplo, para usar las etiquetas Core es necesaria la siguiente etiqueta:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
4.2. El Lenguaje de Expresiones JSTL.
Además de las librerías de etiquetas, JSTL define un llamado Lenguaje de Expresiones (EL). EL es un
lenguaje para acceder a datos de varias fuentes en tiempo de ejecución. Su sintaxis es considerablemente más
amigable que la de Java, que es el único lenguaje soportado directamente por la especificación JSP. Todas las
acciones JSTL reconocen expresiones EL en sus valores de atributos, y se podrían desarrollar acciones
personalizadas para que hicieran lo mismo. EL ha sido incorporado dentro de la última versión de la
especificación JSP para mejorar su uso en el acceso a datos sobre el lenguaje Java. Así, podremos usar
expresiones EL en un valor de atributo de una acción, o directamente embebido en el código HTML de una
página JSP.

147
Importante. Las expresiones EL no forman parte del lenguaje Java, y por tanto no pueden usarse dentro
del código de un scriptlet.
4.2.1. Sintaxis de expresiones EL.
Las expresiones EL fueron pensadas inicialmente para acceder a los datos disponibles en los diversos
contextos de una aplicación web, incluyendo los parámetros de solicitud, cookies, cabeceras y atributos de
ámbito. Cuando recupera un objeto proporciona una sintaxis abreviada para acceder a sus propiedades.
En general, EL presenta tres sintaxis para recuperar la propiedad de un objeto accesible:
${unObjeto.unaPropiedad}
${unObjeto["unaPropiedad"]}
${unObjeto[variableConElNombreDeLaPropiedad]}
Como se ve aquí, una expresión EL siempre debe estar encerrada entre los caracteres ${ y }. Las dos primeras
expresiones acceden a una propiedad llamada unaPropiedad en un objeto representado por una variable
llamada unObjeto.
Nota. Acceder a la propiedad unaPropiedad de un objeto unObjeto, implica que la clase del objeto
unObjeto debe poseer un método público denominado getUnaPropiedad().
La tercera expresión accede a una propiedad con un nombre contenido en un variable, esta sintaxis se puede
utilizar con cualquier expresión que evalúe el nombre de la propiedad.
El operador de acceso a array también se usa para datos representados como una colección de elementos
indexados, como un array de Java o una colección de tipo java.util.List:
${unaLista[2]}
${unaLista[unaVariable + 1]}
EL está pensado para acceder a variables definidas como atributos en algún contexto, pero no está penado
para recuperar variables de Java. Por ejemplo, si definimos variables y atributos en un scriptlet:
<jsp:scriptlet>
Integer var1 = 34;
application.setAttribute("var2", "Aplicación");
session.setAttribute("var2", "Sesión");
</jsp:scriptlet>
Podemos recuperar el atributo "var2" con una expresión EL:
${var2} <%-- lo encuentra en el contexto de sesión y escribe el valor "Sesión" –%>
Pero no podemos recuperar la variable "var1":
${var1} <%-- retorna el valor null y no escribe nada --%>
En el primer caso, la expresión EL busca un atributo de nombre "var2" en los contextos en el siguiente orden:
solicitud (request), página (pageContext), sesión (session) y aplicación (application).
En el segundo caso, la expresión EL busca un atributos de nombre "var1" en los cuatro contextos, y como no
la encuentra retorna el valor nulo y no escribe nada.
4.2.2. Operadores y literales en expresiones EL.
Además de los operadores de propiedad y elemento array y los operadores aritméticos, relacionales, y lógicos,
hay un operador especial para comprobar si un objeto está "vacío" o no puede ser usado en una expresión EL
(empty). La siguiente tabla lista todos los operadores que podemos usar en expresiones EL:
Operador Descripción
. Accede a una propiedad
[] Accede a un elemento de un array/lista
() Agrupa una subexpresión
+ Suma
- Resta o negación de un número
/ ó div División
% ó mod Módulo (resto)
== ó eq Comprueba Igualdad
!= ó ne Comprueba desigualdad
< ó lt Comprueba menor que
> ó gt Comprueba mayor que
<= ó le Comprueba menor o igual que
>= ó gt Comprueba mayor o igual que
&& ó and Comprueba AND lógico

148
|| ó or Comprueba OR lógico
! ó not Complemento binario booleano
empty Comprueba un valor vacío (null, string vacío, o una colección vacía)
Lo que no encontraremos en EL son sentencias como asignaciones, if/else, o while. Para este tipo de
funcionalidades en JSP se usan las acciones de la librería Core de JSTL; y EL no está pensado para utilizarse
como un lenguaje de programación de propósito general, sino como un lenguaje de acceso a datos.
Por supuesto, los literales y las variables también son parte del lenguaje. EL proporciona los siguientes
literales, similares a los que proporcionan JavaScript, Java, y otros lenguajes similares:
Tipo de literal Descripción
String Encerrado con comillas simples o dobles. Una comilla del mismo tipo dentro del
string puede ser escapada con una barra invertida: ( \' en un string encerrado con
comillas simples; \" en un string encerrado con comillas dobles). El caracter de barra
invertida debe ser escapado como \\ en ambos casos.
Entero Un signo opcional (+ o -) seguido por dígitos entre 0 y 9.
Coma flotante Lo mismo que un literal entero, excepto que usa un punto como separador de la parte
fraccional y que se puede especificar un exponente con e o E, seguido por un literal
entero.
Booleano true o false.
Nulo null.
Cualquier objeto en uno de los ámbitos de JSP (página, solicitud, sesión o aplicación) se puede utilizar como
una variable en una expresión EL. Por ejemplo, si tenemos un bean con una propiedad primerNombre en el
ámbito de la solicitud bajo el nombre cliente, la siguiente expresión EL representa el valor de la propiedad
primerNombre del bean.
${cliente.primerNombre}
Pero no se para aquí. EL también hace que la información de la solicitud y la información general del
contenedor esté disponible como un conjunto de variables implícitas:
Variable Descripción
param Un mapa con todos los parámetros de la solicitud como un sólo valor string para cada
parámetro.
paramValues Un mapa con todos los valores de los parámetros de la solicitud como un array de
valores string por cada parámetro.
header Un mapa con todas las cabeceras de solicitud como un sólo valor string por cada
cabecera.
headerValues Un mapa con todos los valores de cabecera de la solicitud como un array de valores
string por cada cabecera.
cookie Un mapa con todas las cookies de la solicitud en un sólo ejemplar de
javax.servlet.http.Cookie por cada cookie.
initParam Un mapa con todos los parámetros de inicialización de la aplicación en un sólo valor
string por cada parámetro.
pageContext Un ejemplar de la clase javax.servlet.jspPageContext.
pageScope Un mapa con todos los atributos en el ámbito de la página.
requestScope Un mapa con todos los atributos en el ámbito de la solicitud.
sessionScope Un mapa con todos los atributos en el ámbito de la sesión.
applicationScope Un mapa con todos los atributos en el ámbito de la aplicación.
Las cinco primeras variables implícitas de la tabla nos ofrecen acceso a los valores de parámetros, cabeceras y
cookies de la solicitud actual. Aquí hay un ejemplo de cómo acceder a un parámetro de solicitud llamado
listType y a la cabecera User-Agent:
${param.listType}
${header["User-Agent"]}
En este caso debemos usar la sintaxis de array para la cabecera, porque el nombre incluye un guion; con la
sintaxis de propiedad, sería interpretado como la expresión variable header.User menos el valor de una
variable llamada Agent.

149
La variable initParam proporciona acceso a los parámetros de inicialización que se definen en el fichero
web.xml de la aplicación. La variable pageContext tiene varias propiedades que proporcionan acceso al objeto
servlet que representa la solicitud, la respuesta, la sesión y la aplicación, etc.
Las cuatro últimas variables son colecciones que contienen todos los objetos de cada ámbito específico.
Podemos usarlas para limitar la búsqueda de un objeto en sólo un ámbito en lugar de buscar en todos ellos, lo
que está por defecto si no se especifica ningún ámbito. En otras palabras, si hay un objeto llamado cliente en
el ámbito de sesión, estas dos primeras expresiones encuentran el mismo objeto, pero la tercera vuelve vacía:
${customer}
${sessionScope.customer}
${requestScope.customer}
Todas las acciones JSTL aceptan expresiones EL como valores de atributo, excepto para los atributos var y
scope, porque estos valores de atributo podrían usarse para verificar el tipo en el momento de la traducción
en una futura versión. Se pueden usar una o más expresiones EL en el mismo valor de atributo, y el texto fijo
y las expresiones EL se pueden mezclar en el mismo valor de atributo:
Nombre: <c:out value="${cliente.Nombre}" />
<c:out value="Nombre: ${cliente.Nombre}" />
Nota. Debe tenerse en cuenta que si una expresión EL retorna un string que contiene etiquetas HTML,
estas etiquetas serán procesadas por el navegador y no se escribirán en la salida de la página. Por ejemplo,
si tenemos la siguiente expresión:
${"las etiquetas <b></b> se usan para negritas"}
En la página se escribirá:
lasetiquetas se usan para negritas
4.2.3. Acceso a arrays y colecciones mediante EL.
Las expresiones EL consideran a cualquier colección de elementos como si fuese un mapa. Esto es evidente
para colecciones que implementen la interfaz Map. Pero es que este principio también vale para los arrays y
listas indexadas.
Por ejemplo, supongamos los siguientes objetos:
<%
String[] color = {"azul", "rojo", "verde"}; // un array de strings
List<Integer> lista = Arrays.asList(1, 2, 3, 4); // una lista de números
// Los guardamos en atributos de página
pageContext.setAttribute("color", color);
pageContext.setAttribute("lista", lista);
%>
Podemos usar expresiones EL para recuperar los elementos del array con dos sintaxis.
${color[0]}
${color["1"]}
La primera expresión utiliza la sintaxis habitual de uso de arrays: se recupera el primer color (azul) indexando
el array con un índice entre corchetes. La segunda expresión recupera el segundo color (rojo) usando el índice
"1" como si los elementos del array tuviesen asociada una clave de tipo string, cuyo valor es su posición
dentro del array.
Podemos utilizar estas dos sintaxis también para recuperar los elementos de la lista:
${lista[0]}
${lista["1"]}
Si ahora declaramos una colección de tipo mapa:
<%
HashMap<String, Integer> secuencia = new HashMap<String, Integer>();
secuencia.put("uno", 1);
secuencia.put("dos", 2);
pageContext.setAttribute("secuencia", secuencia);
%>
Podemos acceder al contenido del mapa con la siguiente expresión:
${secuencia}
Obteniendo como resultado:
{uno=1, dos=2}

150
Se muestra el contenido del mapa en formato de pares clave=valor. Para facilitar el acceso a los valores, las
expresiones EL pueden interpretar las claves como si fuesen propiedades del mapa. De esta manera podemos
recuperar el primer valor del mapa con las expresiones:
${secuencia.uno}
${secuencia["dos"]}
Pero cuidado con la siguiente expresión:
${secuencia[uno]}
El resultado sería nulo en este caso, puesto que la expresión EL buscaría un atributo llamado uno en algún
contexto y recuperaría su valor como clave para el mapa secuencia.
Aquí conviene hacer notar que con los arrays no podemos usar la sintaxis de objeto puesto que los índices
numéricos no son nombres válidos de propiedades. De la misma forma, si el mapa tuviese claves numéricas
sólo podríamos usar la sintaxis de array asociativo para acceder a los valores:
${secuencia.1} <%-- ERROR DE SINTAXIS --%>
4.2.4. Acceso a objetos personalizados mediante EL.
Como se ha visto, las expresiones EL permiten recuperar propiedades de objetos. Pero veamos ahora todo lo
que esto implica. Supongamos creada la siguiente clase:
package bean;
import java.util.Date;
public class Persona {
private String nombre;
private Date fecha;
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public Date getFecha() {
return fecha;
}
public void setFecha(Date fechaNacimiento) {
this.fecha = fecha;
}
}
Tal como está definida la clase bean.Persona posee dos propiedades: nombre y fecha.
Y ahora creamos un objeto Persona en un atributo de sesión con el identificador p y asignamos sus
propiedades:
<jsp:useBean id="p" class="bean.Persona" scope="session" />
<jsp:setProperty name="p" property="nombre" value="Pedro" />
<jsp:setProperty name="p" property="fecha" value="<%= new java.util.Date() %>" />
Podemos recuperar el objeto p mediante una expresión EL o mediante una expresión JSP:
${p}
<%= session.getAttribute("p") %>
Obteniendo como resultado en ambos casos que se escriba en la página:
bean.Persona@42211bec
Este resultado es así porque cuando una expresión EL evalúa un objeto escribe lo que devuelve el método
toString() del objeto. Si queremos recuperar el nombre de la persona p podemos rescribir el método toString()
de la clase Persona, o podemos acceder a la propiedad nombre mediante la expresión EL:
${p.nombre}
Pero aún más. Las expresiones EL permiten encadenar invocaciones a propiedades. Por ejemplo, si
recordamos que la clase java.util.Date tiene un método de instancia llamado getTime() podemos utilizar la
siguiente expresión EL para escribir los milisegundos de la fecha.
${p.fecha.time}
En su última especificación, las expresiones EL también permiten invocar los métodos públicos del objeto.
Por ejemplo, podemos asignar las propiedades mediante expresiones EL:
${p.setNombre("Juan")}

151
4.2.5. Expresiones EL que utilizan atributos no existentes.
Las expresiones EL son lo suficientemente flexibles y poderosas para intentar no lanzar excepciones cuando
se utilizan atributos y valores no existentes. Para ilustrar esto supongamos que no existen dos atributos
llamados foo y bar en ningún contexto.
La siguiente tabla muestra el resultado de usar estos dos atributos no existentes:
Expresión EL Resultado Explicación
${null}
${foo}
${foo[bar]} Las expresiones nulas no escriben nada en la salida.
${bar[foo]}
${foo.bar}
${7 + foo} 7
${7 / foo} Infinite En expresiones aritméticas los atributos desconocidos o nulos
${7 - foo} 7 son tratados como cero.
${7 % foo} Lanza una excepción
${7 < foo} false
${7 == foo} false
${foo == foo} true
${7 != foo} true En expresiones lógicas, los atributos desconocidos son
${true and foo} false tratados como false.
${true or foo} true
${not foo} true

4.3. Configuración del archivo descriptor.


Por defecto, desde Tomcat 5, la interpretación del lenguaje EL está habilitada en las páginas JSP. Si queremos
deshabilitar el lenguaje EL o incluso la interpretación de scriptlets, debemos editar el archivo descriptor
web.xml de la aplicación.
En el elemento <jsp-config> del archivo descriptor podemos habilitar o deshabilitar diversas configuraciones
para grupos de páginas. La sintaxis general es la siguiente:
<web-app>
<jsp-config>
<jsp-property-group>
<description>Todas las páginas</description>
<url-pattern>*.jsp</url-pattern>
<page-encoding>ISO-8859-1</page-encoding>
<el-ignored>false</el-ignored>
<scripting-invalid>false</scripting-invalid>
</jsp-property-group>
</jsp-config>
</web-app>
La etiqueta <jsp-property-group> declara un grupo de páginas. La etiqueta <el-ignored> activa (false) o
desactiva (true) la interpretación de las expresiones EL. La etiqueta <scripting-invalid>activa (false) o desactiva
(true) la utilización de bloques scriptlets.
Si se desactiva la interpretación del lenguaje EL con la etiqueta <el-ignored>true</el-ignored>, entonces
cualquier expresión EL insertada directamente en la página será mostrada como un texto literal.
Si se desactiva la interpretación de código scriptlet con la etiqueta <scripting-invalid>false</scripting-invalid>,
cualquier bloque de código java provocará un error en la página.
4.4. Librería Core JSTL.
La librería Core JSTL incluye el grupo de etiquetas que suplen las instrucciones de programación de Java. La
tabla siguiente describe someramente estas etiquetas:
Etiqueta Descripción
c:if Implementa una estructura selectiva. Si se cumple la condición asignada en el atributo test se
evalúa el cuerpo, sino no se avalúa.
choose Implementa una estructura selectiva similar a la estructura switch de Java.

152
c:catch Captura las excepciones producidas en el cuerpo. El valor de la excepción es asignado en el
atributo opcional var, el cual define un atributo del ámbito de página.
forEach Implementa un bucle para iterar sobre un rango o una colección de elementos.
forTokens Implementa un bucle para iterar sobre un texto con caracteres delimitadores.
set Crea o modifica una variable en el ámbito especificado (por defecto a nivel de página) con un
valor especificado.
remove Elimina una variable especificada en el nivel indicado.
import Recuperar el contenido de un recurso.
out Expone el resultado de evaluar una expresión sobre la página.
redirect Redirige la respuesta de la página a una Url especificada.
url Normaliza una Url.
4.4.1. Acciones de selección.
La acción <c:if> nos permite incluir, o procesar, condicionalmente una parte de una página, dependiendo de
la información durante la ejecución. El siguiente ejemplo incluye un saludo personal si el usuario es un
visitante anterior, según lo indica la presencia de una cookie con el nombre del usuario:
<c:if test="${!empty cookie.nombreUsuario}">
Bienvenido otra vez ${cookie.nombreUsuario.value}"
</c:if>
El valor del atributo test es una expresión EL que verifica si la cookie existe. El operador empty combinado
con el operador "not" (!) significa que evalúa a true si la cookie no existe, haciendo que el cuerpo del
elemento sea procesado.
Aunque la acción c:if es similar a la estructura if de Java, no posee una parte opcional else. La acción
<c:choose>permite agrupar una o más acciones <c:when>, cada una de las cuales permite especificar una
condición booleana diferente. Las acción<c:choose> verifica cada condición en orden y sólo se ejecutará el
cuerpo de la primera acción <c:when>cuya condición se evalúe a true. El cuerpo de<c:choose> también
puede contener una acción<c:otherwise>, cuyo cuerpo sólo se procesará si ninguna de las condiciones de los
<c:when> es true.
Por ejemplo, el siguiente código evalúa el valor de un parámetro de solicitud para determinar a qué página
redirigir.
<c:choose>
<c:when test='${param.dir eq "a"}'>
<jsp:forward page="paginaA.jsp" />
</c:when>
<c:when test='${param.dir eq "b"}'>
<jsp:forward page="paginaB.jsp" />
</c:when>
<c:otherwise>
<jsp:forward page="otraPagina.jsp" />
</c:otherwise>
</c:choose>
Como se puede ver, la estructura de c:when es similar a una estructura switch.
4.4.2. Captura de excepciones.
Para capturar excepciones se utiliza la acción <c:catch />. Posee la siguiente sintaxis:
<c:catch var="error">
<%-- código que pueda lanzar una excepción --%>
</c:catch>
Donde el atributo var indica el nombre de una variable de tipo Throwable, que será creada en el ámbito de
página y que contendrá la excepción generada en el cuerpo de la etiqueta.
Después de la etiqueta <c:catch /> podemos evaluar el valor de la variable error mediante una expresión EL.
Como ejemplo supongamos una página que recupera un parámetro de solicitud llamado contador, el cual
debe convertir a un entero. Si no existe el parámetro o no es convertible se mostrará un mensaje:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

153
<title>JSP Page</title>
</head>
<body>
<c:catch var="ex">
<% int contador = Integer.parseInt(request.getParameter("contador"));%>
El valor de contador es <%= contador %>
</c:catch>
<c:if test="${!empty ex}">
Falta el contador o no es convertible
</c:if>
</body>
</html>
4.4.3. Visualizar valores de expresiones.
Para visualizar los valores de una expresión se utiliza la acción <c:out />, como en el siguiente ejemplo:
Hay <c:out value="${producto.stockActual}" escapeXml="true" default="0" /> productos en el stock.
Donde el atributo value especifica la expresión a visualizar, el atributo escapeXml indica si hay que aplicar
códigos de escape a los caracteres <, >, & y . (punto), y el atributo default indica un valor por defecto si la
expresión no puede evaluarse.
4.4.4. Crear, modificar y eliminar variables de ámbito.
Para crear una variable en uno de los contextos (request, page, session o application) se usa la acción <c:set />:
<c:set var="idCliente" value="${param.numeroCliente}" scope="session" />
Donde el atributo var indica el nombre de la variable a crear o modificar, el atributo value especifica una
expresión que evalúa al valor que se quiere asignar, y el atributo scope indica el contexto en el que se define la
variable.
También se puede usar <c:set /> para asignar el valor de la propiedad de un objeto, de forma parecida a
como lo hace la acción <jsp:setProperty />:
<jsp:useBean id="persona1" class="bean.Persona" />
<%-- Asignamos la propiedad "nombre"--%>
<c:set target="${persona1}" property="nombre" value="Juan Pérez" />
En este caso, el atributo target establece el objeto que queremos modificar, y el atributo property establece
una propiedad del objeto especificado en target.
Si el objeto especificado en target es de tipo java.util.Map, el atributo property especificará una clave para el
mapa, y el atributo value el valor asociado a dicha clave.
<%-- Creo un objeto de tipo HashMap y le añado un elemento con la clave "uno" y el valor 1 --%>
<jsp:useBean id="mapa1" class="java.util.HashMap" />
<c:set target="${mapa1}" property="uno" value="1" />
También podemos asignar el cuerpo de la etiqueta<c:set> a una variable. El siguiente código crea una
variable en el ámbito de página llamado contenidoCelda con el contenido del cuerpo de la etiqueta <c:set />.
<c:set var="contenidoCelda">
<td>
<c:out value="${miCelda}"/>
</td>
</c:set>
Para eliminar una variable de un ámbito se utiliza la acción <c:remove />:
<c:remove var="unaVariable">
4.4.5. Bucle para colecciones.
Se utiliza la acción c:forEach para recorrer cualquier tipo de colección de datos o un rango de valores.
El siguiente código permite iterar sobre un rango de valores entre 1 y 10:
<c:forEach begin="1" end="10" var="i">
<p>Iteración ${i}</p>
</c:forEach>
En el atributo var se declara un nombre de variable que estará disponible dentro del cuerpo de la etiqueta, y
que en cada iteración tomará valores desde el valor inicial establecido en el atributo begin hasta el valor final
establecido en el atributo end.
Pero la gran potencia de esta etiqueta es su capacidad para iterar sobre cualquier tipo de colección. El
siguiente fragmento itera sobre la colección de cabeceras de la solicitud:
<c:forEach var="cabecera" items="${header}">
<p>${cabecera.key} = ${cabecera.value}</p>

154
</c:forEach>
La expresión EL para el valor header obtiene el mapa de cabeceras. Cada elemento de un mapa es un objeto
de tipo java.util.Map.Entry, el cual posee las propiedades key y value.
Para ilustrar todas las posibilidades que ofrece esta acción, extenderemos el ejemplo de iteración para
procesar sólo un conjunto de cabeceras por cada página solicitada, añadiendo enlaces "Anterior" y "Siguiente"
en la misma página.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<c:set var="lineas" value="4" />
<ul>
<c:forEach var="cabecera" items="${header}"
begin="${param.inicial}" end="${param.inicial + lineas - 1}" step="1">
<li>${cabecera.key} = ${cabecera.value}</li>
</c:forEach>
</ul>
<a href="?inicial=${param.inicial-lineas}">Anterior</a>
<a href="?inicial=${param.inicial+lineas}">Siguiente</a>
</body>
</html>
La acción <c:set>define la variable lineas para indicar cuántas cabeceas se visualizarán por página. El
<c:forEach> en este ejemplo toma los mismos valores para los atributos items y var como antes, pero hemos
añadido dos nuevos atributos:
• E atributo begin toma el índice (base 0) del primer elemento de la colección a procesar. Aquí se selecciona
al valor de un parámetro de solicitud llamadoinicial. Para la primera solicitud, este parámetro no está
disponible, por eso la expresión se evaluará a 0; en otras palabras, la primera fila.
• El atributo end especifica el índice del último elemento de la colección a procesar. Aquí lo hemos
seleccionado al valor del parámetro inicial más lineas menos uno. Para la primera solicitud, cuando no existe
el parámetro de la solicitud, este resultado es 3, por eso la acción iterará la primera vez sobre los índices del
0 al 3.
• El atributo step especifica el paso de iteración. Un valor mayor que 1 hará que el bucle se salte elementos
de la colección.
Luego añadimos los enlaces "Anterior" y "Siguiente", donde realmente se define el parámetro de solicitud
inicial, incrementándolo y decrementándolo apropiadamente.
Si probamos esta página se verá que realmente página las cabeceras. Pero si en la primera página pulsamos el
enlace "Anterior" se producirá un error. Esto es porque el atributo begin no admite valores negativos. Para
evitar esto podemos condicionar la aparición de este enlace:
<c:if test="${param.inicial > 0}">
<a href="?inicial=${param.inicial-lineas}">Anterior</a>
</c:if>
Si avanzamos más allá de la última página de cabeceras se mostrará una página vacía pero no se producirá
error. Aún así condicionaremos también la aparición de este enlace teniendo en cuenta el tamaño de la
colección:
<c:if test="${param.inicial + lineas < header.size()}">
<a href="?inicial=${param.inicial+lineas}">Siguiente</a>
</c:if>
4.4.6. Iteración sobre textos.
Al igual que al clase StringTokenizer, la acción <c:forTokens /> permite iterar sobre trozos de un string
delimitados por algún carácter especificado. En siguiente ejemplo muestra el uso de esta etiqueta:
<c:forTokens items="uno-dos-tres-cuatro" delims="-" var="token">
${token}
</c:forTokens>

155
Al igual que la etiqueta c:forEach, c:forTokens también posee atributos begin, end y step.
4.4.7. Procesar URLs (Reescritura URL).
La acción <c:url /> permite normalizar una URL relativa del sitio web convirtiéndola en una URL absoluta
respecto a la carpeta raíz, si empiezan con una barra inclinada. Esto es interesante para no tener problemas al
realizar redirecciones externas.
Además esta acción ofrece una funcionalidad más interesante aún. La url normalizada aplica reescritura URL
para asegurar el mantenimiento de las sesiones aunque están desactivadas las cookies en el navegador cliente.
Como ejemplo de aplicación modificaremos los enlaces "Anterior" y "Siguiente" del ejemplo previo:
<c:if test="${param.inicial > 0}">
<c:url var="urlAnterior" value="">
<c:param name="inicial" value="${param.inicial-lineas}" />
</c:url>
<a href="${urlAnterior}">Anterior</a>
</c:if>
<c:if test="${param.inicial + lineas < header.size()}">
<c:url var="urlSiguiente" value="">
<c:param name="inicial" value="${param.inicial+lineas}" />
</c:url>
<a href="${urlSiguiente}">Siguiente</a>
</c:if>
La acción <c:url> soporta un atributo var, usado para especificar la variable que contendrá la URL codificada,
y un atributo value para contener la URL a codificar. Si no se especifica el atributo var, la URL codificada es
renderizada en la salida.
Se pueden especificar parámetros string para solicitar la URL usando acciones <c:param>. Los caracteres
especiales en los parámetros especificados por elementos anidados son codificados (si es necesario) y luego
añadidos a la URL como parámetros string de la consulta. El resultado final se pasa a través del proceso de
reescritura de URL, añadiendo un ID de sesión si está desactivado el seguimiento de sesión usando cookies.
4.4.8. Redirecciones externas.
La acción <c:redirect> permite redirigir externamente a otro recurso en la misma aplicación Web, en otra
aplicación Web o en un servidor diferente, aplicando reescritura URL sobre la url. Por ejemplo, podemos
evaluar una variable de contexto para redirigir a una u otra página:
<if test="${condicion}">
<c:redirect url="/pagina1.jsp" />
</c:if>
<c:redirect url="/pagina2.jsp" />
La acción c:redirect es equivalente a response.sendRedirect(encodeRedirectURL(url)).
4.4.9. Importar y ejecutar recursos.
La acción <c:import> es una acción más flexible que la acción estándar <jsp:include>. Podemos usarla para
incluir contenido desde recursos dentro de la misma aplicación Web, desde otras aplicaciones Web en el
mismo contenedor, o desde otros servidores, usando protocolos como HTTP y FTP.
Por ejemplo, supongamos definido el siguiente servlet:
package servlets;
import java.io.*;
import java.text.DateFormat;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.*;
@WebServlet(name = "FechaActual", urlPatterns = {"/fechaactual"})
public class FechaActual extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(DateFormat.getDateInstance().format(new Date()));
}
}
}

156
El servlet FechaActual simplemente responde escribiendo la fecha actual en el canal de salida. Este servlet
tiene asociado como patrón URL el recurso "fechaactual".
Ahora, en un JSP, podemos utilizar la etiqueta <c:import /> sobre este servlet de dos maneras:
<c:import url="fechaactual" /> <%-- se escribe directamente la fecha --%>
<br />
<c:import url=" fechaactual " var="fecha" /> <%-- se guarda la fecha en una variable --%>
${fecha} <%-- y se muestra --%>
Como resultado de la ejecución de este JSP se genera una página HTML que muestra en dos líneas distintas la
fecha actual.
Si queremos mostrar el contenido de un archivo de texto dentro de una página JSP podemos utilizar también
<c:import />.
Contenido del archivo de texto: <br />
<c:import url="un_archivo.txt" />
La etiqueta <c:import /> también admite el paso de parámetro con la etiqueta anidada <c:param />. Por
ejemplo:
<c:import url="fechaactual" />
<c:param name="nombre del parámetro" value="valor del parámetro" />
</c:import>
Los parámetros son concatenados a la URL del recurso importado como parámetros de solicitud GET, y
pueden ser recuperados desde un servlet con el método HttpRequest.getParameter().
4.5. Librería Formatting.
La librería fmt incluye un conjunto de acciones para simplificar la internacionalización, principalmente cuando
páginas compartidas se usan para varios idiomas. La tabla siguiente describe someramente sus etiquetas:
Etiqueta Descripción
bundle Carga un grupo de recursos de idiomas para ser utilizado por las demás etiquetas que
contenga en su cuerpo. El atributo basename debe contener el nombre común del grupo
de recursos de idioma.
formatDate Da formato a un valor de fecha de acuerdo a la cultura establecida. El formato de fecha
puede ser establecido mediante los atributos opcionales dateStyle, pattern, y timeStyle.
formatNumber Da formato a un número de acuerdo a la cultura establecida. El formato de número
puede ser establecido mediante los atributos opcionales currencyCode, groupingUsed,
currencySymbol, maxFractionDigits, maxIntegerDigits, minFractionDigits, minIntegerDigits, y
pattern.
message Recupera el valor asociado a una clave del archivo de recursos de idioma para el idioma
establecido.
parseDate Similar a formatDate para extraer el valor de fecha de un string.
parseNumber Similar a formatNumber para extraer el valor numérico de un string.
requestEncoding Establece la codificación de caracteres por defecto.
setBundle Estable un grupo de archivos de recursos de idioma para ser utilizado por etiquetas
posteriores.
setLocale Estable el idioma y/o región por defecto.
setTimeZone Establece la zona horaria.
timeZone Determina la zona horaria dentro del cuerpo de la etiqueta.
4.5.1. Internacionalización y formateo.
Los grandes sitios web normalmente necesitan complacer a los visitantes de todo el mundo, y servir el
contenido sólo en un idioma no es suficiente. Para desarrollar un sitio que proporcione una elección de
idiomas, tenemos dos opciones:
• Escribir un conjunto de páginas para cada idioma.
• Escribir un conjunto compartido de páginas que ponga el contenido en diferentes idiomas desde fuentes
externas.
Frecuentemente terminaremos con una mezcla de estas técnicas, usar páginas separadas para la mayoría de las
grandes cantidades de contenido estático y páginas compartidas cuando la cantidad de contenido sea pequeña
pero dinámica (por ejemplo, una página con unas pocas etiquetas fijas mostradas en diferentes idiomas y
todos los demás datos que vengan desde una base de datos).

157
Preparar una aplicación para varios idiomas se llama internacionalización (comúnmente abreviado i18n ) y
hacer que el contenido esté disponible para un idioma específico se llama localización (o l10n ). Para hacer
esto necesitamos considerar otras cosas además del idioma: cómo se formatean las fechas y números entre los
diferentes países, e incluso dentro de los países. También podríamos necesitar adaptar los colores, las
imágenes y otro contenido no textual. El término localidad se refiere a un conjunto de reglas y contenido
aceptable para una región o cultura.
4.5.2. Formateo de fechas y números sensible a la localidad.
Primero veamos cómo formatear apropiadamente fechas y números. Este ejemplo formatea la fecha actual y
un número basándose en las reglas de la localidad por defecto:
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>Formateando con la localidad por defecto</h1>
<jsp:useBean id="now" class="java.util.Date" />
Fecha: <fmt:formatDate value="${now}" dateStyle="full" />
Numero: <fmt:formatNumber value="${now.time}" />
</body>
</html>
La primera línea es la directiva taglib para la librería JSTL que contiene las acciones de formateo e
internacionalización. El prefijo por defecto, usado aquí, es fmt. Para obtener un valor a formatear, se crea un
objeto java.util.Date que representa la fecha y hora actuales, y lo graba como una variable llamada now.
La acción JSTL <fmt:formatDate> formatea el valor de la variable now asignado al valor del atributo value
usando el tipo de expresión EL. El atributo dateStyle le dice a la acción cómo debe formatear la fecha.
Podemos usar cualquiera de los valores default, short, medium, long, o full para el atributo dateStyle. El tipo de
formateo que representa cada uno de estos tipos depende exactamente de la localidad. Para la localidad
English, full resulta en un string como "Thursday, August 29, 2002". En este ejemplo sólo hemos formateado la
parte de la fecha, pero también podemos incluir la parte de la hora (o formatear sólo la hora), y definir reglas
de formateo personalizadas tanto para la fecha como para la hora en lugar de los estilos dependientes de la
localidad:
<fmt:formatDate value="${now}" type="both" pattern="EEEE, dd MMMM yyyy, HH:mm" />
El atributo pattern toma un patrón de formateo personalizado del mismo tipo que la clase
java.text.SimpleDateFormat. El patrón usado aquí resulta en "Thursday, 29 August 2002, 17:29" con la localidad
English.
Para escanear un string y obtener la fecha que contiene se usa la acción parseDate:
<fmt:parseDate value="${stringConFecha}" pattern="MM dd, YYYY" var="parsedDate" />
Esta acción extrae la fecha contenida en una variable stringConFecha según el patrón especificado y la asigna
en una variable denominada parsedDate.
La acción <fmt:formatNumber> soporta atributos similares para especificar cómo formatear un número
usando estilos dependientes de la localidad para números normales, valores de moneda, o un porcentaje, así
como usar patrones personalizados de diferentes tipos. Por ejemplo:
<fmt:formatNumber value="1000.001" pattern="#,#00.0#"/>
Provoca que el número se muestre como 1,000.00 para la localidad English.
De forma análoga a la acción parseDate, si queremos escanear un número a partir de un string usaremos la
acción parseNumber del siguiente modo:
<fmt:parseNumber value="${stringConNumero}" type="currency" var="parsedNumber"/>
4.5.3. Usar JSTL para seleccionar la localidad.
Volviendo a la cuestión principal: ¿cómo se determina la localidad para formatear las fechas y los números? Si
usamos las acciones JSTL exactamente como en este ejemplo, sin hacer nada más, el formateo de localidad se
determina comparando las localidades especificadas en un cabecera de solicitud llamada Accept-Language con
las localidades soportadas por el entorno de ejecución Java.

158
La cabecera, si está presente, contiene una o más especificaciones de localidades (en la forma de código de
idioma y posiblemente un código de país), con información sobre su prioridad relativa. El usuario utiliza las
configuraciones del navegador para definir qué especificaciones de localidades enviar. Las localidades de la
solicitud se comparan en orden de prioridad con las ofrecidas por la máquina virtual Java, y se selecciona la
que mejor coincida.
Si no se encuentra una correspondencia, la acción de formateo busca la llamada selección de configuración de
la localidad predefinida. Una selección de configuración es un valor seleccionado por un parámetro de
contexto en el fichero web.xml de la aplicación o por una acción JSTL o una sentencia Java en uno de los
ámbitos JSP. Para seleccionar esta localidad en el fichero web.xml, incluiremos estos elementos:
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.fallbackLocale</param-name>
<param-value>de</param-value>
</context-param>
Con esta selección, se usará la localidad German (especificada por el código de idioma "de" como valor de
parámetro) si ninguna de las localidades especificadas por la solicitud está soportada por el entorno Java.
JSTL también nos permite seleccionar una localidad por defecto para la aplicación que podremos sobrescribir
cuando lo necesitemos. Esto nos da control total sobre la localidad a utilizar, en lugar de dejarlo a la
configuración del navegador del visitante. La localidad por defecto también es una selección de configuración.
Se puede especificar con un parámetro de contexto en el fichero web.xml de esta forma:
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.locale</param-name>
<param-value>en</param-value>
</context-param>
Esta selección establece English como la localidad por defecto, resultando que en la anterior página JSP
siempre formateará la fecha y los números de acuerdo a las reglas inglesas.
Para sobrescribir la configuración de la localidad por defecto, podemos usar la acción <fmt:setLocale>, que
selecciona la localidad por defeco dentro de un ámbito JSP específico. Aquí tenemos un ejemplo que
selecciona la localidad por defecto del ámbito de la página, basándose en una cookie que sigue la pista de la
localidad seleccionada por el usuario en una visita anterior:
<h1>Formateando con una localidad puesta por setLocale</h1>
<c:if test="${!empty cookie.preferredLocale}">
<fmt:setLocale value="${cookie.preferredLocale.value}" />
</c:if>
<jsp:useBean id="now" class="java.util.Date" />
Fecha: <fmt:formatDate value="${now}" dateStyle="full" />
Número: <fmt:formatNumber value="${now.time}" />
Aquí, primero hemos usado la acción JSTL <c:if> para comprobar si con la solicitud se ha recibido una
cookie llamada preferredLocale. Si es así, la acción <fmt:setLocale> sobrescribe la localidad para la página
actual seleccionando la variable de configuración de la localidad en el ámbito de la página. Si lo queremos,
podemos seleccionar la localidad para otro ámbito usando el atributo scope. Finalmente, se formatean la fecha
y el número de acuerdo a las reglas de la localidad especificada por la cookie, o la localidad por defecto, si la
cookie no está presente.
4.5.4. Generar texto localizado.
Aunque formatear fechas y número es importante cuando se localiza una aplicación, el contenido de texto es,
por supuesto, incluso más importante. JSTL está basado en Java, por eso trata con el soporte genérico de i18n
de la plataforma Java. Cuando viene con texto, este soporte está basado en lo que se llama un paquete de
recursos. En su forma más simple, un paquete de recursos está representado por un fichero de texto que
contiene claves y un valor de texto para cada clave. Este ejemplo muestra un fichero con dos claves (hello y
goodbye) y sus valores:
Archivo «labels.properties»
hello=Hola
goodbye=Adiós
Las distintas localidades se soportan creando ficheros separados para cada una de ellas, con un nombre de
fichero que incluye el nombre de la localidad. Por ejemplo, si el fichero del paquete de recursos anterior
representa la localidad Español, debería almacenarse en un fichero llamado labels_es.properties. El paquete de

159
recursos para la localidad Swedish sería almacenado en un fichero llamado labels_sv.properties, donde sv es el
código de idioma. La parte fija del nombre de fichero, labels de este ejemplo, se llama el nombre base del
paquete de recursos, y se combina con una especificación de localidad (como un código de idioma) para
encontrar el paquete de recursos para una localidad específica. Todos los paquetes de recursos incluyen las
mismas claves; sólo difiere el texto dependiendo de la localidad. Los ficheros deben situarse en un directorio
que sea parte del classpath del contenedor, normalmente el directorio WEB-INF/classes de la aplicación.
La acción JSTL que añade texto desde un paquete de recursos a una página es <fmt:message>. El paquete a
utilizar puede especificarse de varias formas. Una es anidar las acciones <fmt:message> dentro del cuerpo de
una acción <fmt:bundle>:
<fmt:bundle basename="labels">
<fmt:message key="hello" />y<fmt:message key="goodbye" />
</fmt:bundle>
En este caso, la acción <fmt:bundle> localiza el paquete de la localidad que es la correspondencia más cercana
entre la selección de configuración de la localidad (o las localidades en el cabecera Accept-Language, si no hay
localidad por defecto) y los paquetes de recursos disponibles para el nombre base especificado. Las acciones
anidadas obtienen el texto desde el paquete por la clave asociada.
Al igual que con las acciones de formateo, podemos establecer un paquete por defecto, para toda la
aplicación, con un parámetro de contexto o con la acción <fmt:setBundle> o la clase Config para un ámbito
JSP específico:
<fmt:setBundle basename="labels"/>
……………….
<fmt:message key="hello" />y<fmt:message key="goodbye" />
Después de que se haya definido un paquete por defecto, podemos usar acciones <fmt:message>
independientes dentro del ámbito donde se estableció el valor por defecto.

PRÁCTICA
01. Crea una página JSP que tenga un contenido parecido al siguiente:

La lista desplegable permitirá elegir un tipo de usuario. Cuando se seleccione un usuario y se pulse el botón
[Actualizar] se debe almacenar en una cookie el tipo se usuario seleccionado y se debe recargar la página. El
usuario Todos debe poder visualizar las tres líneas, el usuario Directivo debe ver sólo la primera línea, el
usuario Coordinador sólo la segunda línea, y el usuario Subordinado sólo la tercera línea.
La primera vez que se cargue la página debe recuperarse el tipo de usuario de la cookie, si existe, sino se
establecerá por defecto el tipo Todos.
La cookie no debe existir durante más de 1 día.

02. Crea una página JSP llamada "inicio.jsp" que tenga un contenido parecido al siguiente:

La página permitirá un tipo de dato, introducir un valor del tipo seleccionado y seleccionar una operación.
Cuando se pulse el botón [Realizar operación] deberá invocarse otra página "procesa.jsp" que calculará la
solución. Utiliza un JavaBean para postear los datos y capturarlos en la página "procesa.jsp".

160
La página de proceso deberá gestionar errores si el valor o la operación no se corresponden con el tipo de
dato. Para ello hay que crear otra página llamada "error.jsp" que debe mostrar un mensaje correspondiente
al tipo de error producido y un enlace para retornar a la página inicial.
Aplica un mecanismo que funcione de la siguiente manera: cuando la página "procesa.jsp"detecte un error
en los datos deberá lanzar una excepción. La excepción generada debe provocar la carga inmediata de la
página "error.jsp". Si no hay error debe redirigirse a la página inicial pasándole el resultado como un atributo
de solicitud.

03. Crea un sitio web que simule un asistente de dos pasos para seleccionar un producto. El sitio mostrará
una página para cada paso con la opción de visualizar una ayuda.

El sitio Web constará de las siguientes páginas JSP:


Ayuda.jsp: Contendrá las instrucciones de ayuda.

161
Paso1.jsp: Contera el asistente del paso 1.
Paso2.jsp: Contera el asistente del paso 2.
Las páginas Paso1.jsp y Paso2.jsp contienen un cuadro de verificación (checkbox) que cuando esté marcado
deberá embeber el contenido de la página Ayuda.jsp. Para conseguir esto, al pulsar sobre el checkbox deberá
refrescarse la página para mostrar u ocultar la ayuda. Embebe el contenido de la página de ayuda usando
acciones JSP.
Como ves en las imágenes no hay un botón de tipo "submit" para provocar el refresco de la página. Se puede
gestionar el cambio de estado del checkbox para provocar un submit desde código script.
Los enlaces siguiente» y «anterior en las páginas permitirán navegar a la página siguiente o anterior. El estado
del checkbox deberá mantenerse entre páginas. Esto quiere decir que si en la primera página el checkbox está
marcado (y por tanto aparece la ayuda), al navegar a la segunda página su checkbox también deberá estar
marcado (y se mantendrá por tanto la ayuda). Esto implica usar otro tipo de variable para controlar el estado
del checkbox, puesto que la variable local usada en el ejemplo previo no pervive entre llamadas a páginas.
El enlace finalizar no es necesario que navegue hasta ninguna otra página.

162
UNIDAD 15. ETIQUETAS PERSONALIZADAS
1. Fundamentos de librerías de etiquetas
En esta unidad veremos cómo crear nuestras propias librerías de etiquetas o acciones personalizadas, al estilo
de las librerías JSTL.
Se recomienda crear librerías de etiquetas para encapsular la lógica del negocio de nuestra aplicación, y poder
invocarla desde las páginas JSP mediante la sintaxis de acciones.
1.1. Conceptos básicos sobre etiquetas.
La sintaxis para crear etiquetas personalizadas es similar a la utilizada en las acciones JSP y sigue el formato
XML. El código que tendrá que ser ejecutado cuando se utilice la etiqueta podrá ser escrito como código Java
o como código JSP.
Existen dos formas de crear etiquetas personalizadas:
1) Creando una clase manejadora de la etiqueta (Tag Handler). Esta clase debe implementar la interfaz
SimpleTag, Tag, IterationTag, o BodyTag del paquete javax.servlet.jsp.tagext. Estas interfaces proporcionan
unos métodos que determinan el comportamiento de la etiqueta.
2) Creando un fichero de tag (Tag File). El fichero de tag es un archivo que permite escribir el código de la
etiqueta mediante acciones JSP.
Podemos agrupar las etiquetas en librerías de etiquetas. Esto es obligado en el caso de los Tag Handler y
opcional en el caso de los Tag File. De esta forma podemos usar la sintaxis <lib:act />, donde lib identifica la
librería y act la etiqueta.
Para definir la librería se utiliza un archivo descriptor de librería (TLD), donde se asocia cada etiqueta de
tipo Tag Handler con la clase Java que la implementa, y cada etiqueta de tipo Tag File con su ubicación.
En las páginas JSP, antes de usar las etiquetas, debemos referenciar las librerías mediante una URI en una
directiva <%@ taglib %>.
1.2. Declaración de las librerías de etiquetas en las páginas JSP.
Para usar una librería de etiquetas TLD en una página JSP debemos declararla con la siguiente directiva:
<%@ taglib prefix="test" uri="simpleLib" %>
Donde el atributo uri (con valor "simpleLib"), identifica al archivo descriptor (siempre deben tener extensión
tld) y test es el prefijo con el cual serán llamadas las etiquetas de esa librería.
Si queremos usar sintaxis XML, esta declaración debemos incluirla en el elemento <jsp:root>:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:test="simpleLib" version="1.2" >
... CONTENIDO DE LA PÁGINA ...
</jsp:root>
Si además queremos forzar que se genere contenido XHTML deberemos usar la versión 2.0 de la siguiente
forma:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:test="simpleLib" version="2.0" >
... CONTENIDO DE LA PÁGINA ...
</jsp:root>
Para usar una librería de etiquetas de tipo Tag File en una página JSP debemos declararla con la siguiente
directiva.
<%@ taglib prefix="test" tagdir="/WEB-INF/tags" %>
Donde el atributo tagdir indica la carpeta donde están ubicados los tag files que forman parte de la librería.
1.3. El archivo descriptor TLD.
Este archivo de tipo XML contiene toda la información referente a una librería de etiquetas. Tiene el
siguiente formato:
<?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> <!-- versión de la librería -->
<jsp-version>1.2</jsp-version> <!-- versión JSP -->
<short-name>test</short-name> <!-- el prefijo que usa esta librería -->
<uri> simpleLib</uri> <!-- uri para identificar la librería -->

163
<!-- Parte de declaración de etiquetas -->
<tag>
<name>etiqueta1</name> <!-- el nombre de la etiqueta -->
<tag-class>sampleLib.Etiqueta1Tag</tag-class> <!-- la clase manejadora de la etiqueta -->
<body-content>empty</body-content> <!-- tipo de contenido del cuerpo -->
<description></description> <!-- la descripción de la etiqueta -->
<attribute> <!-- atributos de la la etiqueta -->
<name>user</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
<tag>
....................
</tag>
</taglib>
Este fichero se divide en dos partes. La primera parte establece las propiedades generales de la librería, donde
hay que destacar la uri de la librería y su nombre corto.
Las etiquetas que se utilizan en esta parte son:
Etiqueta Significado
tlib-version Especifica la versión de la librería.
jsp-version Especifica la versión mínima del motor JSP que es compatible con la librería.
short-name Especifica el prefijo que usará esta librería en las páginas JSP.
uri Especifica la URI que identificará a esta librería de forma unívoca.
display-name Especifica un nombre que puede ser obtenido por las utilidades de autoría.
small-icon Especifica la ruta de un icono pequeño que puede ser usado por utilidades.
large-icon Especifica la ruta de un icono grande que puede ser usado por utilidades.
description Contendrá información adicional sobre la utilidad de la librería.
validator Especifica una clase validadora de tipo TagLibraryValidator, la cual podemos usar para
validar que las etiquetas son usadas correctamente.
listener Especifica una clase oyente de tipo ServletContextListenerque será registrada en el
contenedor Web y que podemos usar para realizar inicializaciones.
tag Describe una etiqueta de la librería.
La segunda parte de la librería describe cada etiqueta mediante el atributo tag, usando las siguientes sub-
etiquetas:
Etiqueta Significado
name Especifica el nombre de la etiqueta que será usado en las páginas JSP.
tag-class Especifica la clase Java manejadora en la que se encuentra la funcionalidad de la etiqueta.
tei-class Especifica una subclase opcional de javax.servlet.jsp.tagext.TagExtraInfo. Se usa si la
etiqueta necesita definir alguna variable o una clase de ayuda para realizar validaciones en
tiempo de ejecución.
body-content Indica el tipo de contenido del cuerpo de la etiqueta (si lo tiene). Y puede tomar tres
valores: empty (cuando no hay nada), JSP (si contiene código JSP o incluso otra etiqueta)
o tagdependent (indica que el cuerpo debe ser interpretado por la propia etiqueta). El
valor por defecto es JSP.
display-name Establece un nombre que puede ser obtenido por las utilidades de autoría.
description Establece la descripción de la etiqueta.
small-icon Establece la ruta de un icono pequeño que puede ser usado por utilidades.
large-icon Establece la ruta de un icono grande que puede ser usado por utilidades.
variable Especifica la información de una variable de script.
attribute Describe un atributo que la etiqueta puede aceptar.
example Especifica información opcional sobre un ejemplo de uso de la etiqueta.
La descripción de cada atributo contiene la siguiente información:
Etiqueta Significado

164
name Establece el nombre del atributo.
required Puede ser true, false, yes o no para indicar si el atributo es obligatorio u opcional.
rtexprvalue Establece un valor para especificar cuándo el atributo puede aceptar expresiones JSP y
EL que serán evaluadas en tiempo de respuesta. Puede ser true, false, yes o no.
type Establece el tipo de dato del atributo (cuando rtexprvalue es true). El valor por defecto
java.lang.String.
description Establece una descripción del atributo.
Especial atención merece el atributo rtexprvalue. Si este atributo está asignado a true, cualquier expresión JSP
o EL que contenga el atributo será evaluada previamente antes de que el atributo quede asignado a un valor
durante la ejecución de la etiqueta. Si este atributo está asignado a false, cualquier expresión JSP o El será
interpretada como un literal de texto.
1.4. Reconocimiento de librerías de etiquetas por parte del contenedor.
Para poder usar las etiquetas de una librería de etiquetas en una página JSP, el contenedor JSP del servidor
Web debe ser capaz de encontrar la librería a partir de su URI. El motor JSP busca librerías y analiza su
contenido en ubicaciones concretas del sitio web. Estas ubicaciones vienen dadas de la siguiente manera:
1) El archivo TLD está ubicado dentro de una subcarpeta de la carpeta WEB-INF. Por ejemplo, si tenemos la
siguiente estructura en nuestro sitio Web:
Figura 1

Tanto Libreria2.tld como Libreria3.tld son reconocidos como librerías de etiquetas; sin embargo, Libreria1.tld
no es reconocido como librería de etiquetas.
2) El archivo TLD está dentro de un archivo de tipo JAR dentro de la carpeta META-INF. (Ésta es la técnica
que utilizan las librerías de etiquetas de JSTL.) Por ejemplo, si tenemos la siguiente estructura dentro de un
archivo comprimido MisEtiquetas.jar:
Figura 2

Libreria1.tldes reconocido como una librería de etiquetas, pero Libreria2.tld no lo es.


3) Si el archivo TLD no está ubicado en los lugares indicados en las maneras previas, debe estar registrada
en el archivo descriptor. Cada URI de una librería de etiquetas debe ser declarada en el archivo web.xml de
la siguiente forma:
<web-app>
<jsp-config>
<taglib>
<taglib-uri>simpleLib</taglib-uri>
<taglib-location>/META-INF/simpleLib.tld</taglib-location>
</taglib>
</jsp-config>
</web-app>
En el archivo web.xml se identifica una librería por su URI y se indica su ubicación física respecto a la
carpeta raíz de la aplicación web.

165
4) Si trabajamos directamente con tag files sin usar un TLD, la carpeta donde están contenidos los tag files
es reconocida como una librería de etiquetas si está ubicada dentro de la carpeta WEB-INF del sitio Web. Por
ejemplo, si tenemos la siguiente estructura en nuestro sitio Web:
Figura 3

La carpeta tags es reconocida como una librería que contiene una etiqueta identificada por etiqueta1, y la
carpeta test es reconocida como una librería que contiene una etiqueta identificada por etiqueta2.
1.5. Primer ejemplo de una librería de etiquetas.
Usaremos el entorno de desarrollo NetBeans para crear un ejemplo de librería de etiquetas. Empezaremos
creando un proyecto web vacío y abriendo el asistente para añadir un nuevo fichero:
Figura 4

En la categoría «Web» debemos seleccionar la plantilla «Tag Library Descriptor». Tras pulsar el botón
«Siguiente» debemos configurar los datos de la nueva librería.

166
Figura 5

Como nombre de la librería pondremos LibreriaTest. Por defecto las nuevas librerías se ubican en la carpeta
/WEB-INF/tlds; esta carpeta es una de las rutas donde el motor JSP busca librerías, así que la mantendremos.
Cada librería debe tener asociada una URI única, para este ejemplo hemos puesto /com.personal.test, y un
prefijo, que hemos dejado con test.
Tras pulsar el botón «Finalizar» se crea la nueva librería.
Figura 6

En el fichero LibreriaTest.tld podemos rectificar cualquier dato que nos interese. Y ahora la librería está
preparada para que podamos agregarle etiquetas personalizadas.

167
2. Etiquetas basadas en clases manejadoras
El primer mecanismo para crear una etiqueta personalizada que esté incluida dentro de una librería es
asociarla a una clase de Java que proporcione la funcionalidad de la misma. Estas clases deben implementar
las interfaces SimpleTag, Tag y BodyTag o bien extender las clases adaptadoras SimpleTagSupport, TagSupport y
BodyTagSupport, que a su vez implementan respectivamente estas interfaces. Todas estas clases e interfaces se
encuentran en el paquete javax.servletJsp.tagtext.
Figura 7

Cuando una página JSP procesa una etiqueta, el motor JSP realiza las siguientes acciones en la clase
manejadora asociada, si implementa Tag o BodyTag:
1. Llama al método setPageContext() para asignar el contexto de la página.
2. Llama al método setParent() para asignar una etiqueta anidada de nivel superior, si la hubiera.
3. Establece los valores de los atributos, si los hay, mediante métodos setAtributo().
4. Llama al método doStartTag(), y procesa la funcionalidad de la etiqueta. El valor de retorno indica si debe
evaluarse el posible cuerpo de la etiqueta. (Si debe evaluarse el cuerpo llama a doInitBody() y doAfterBody() a
continuación.)
5. Llama al método doEndTag().
6. Llama al método release().
En estas interfaces, los métodos doTag(), doStartTag() y doEndTag() pueden lanzar una JspException si ocurre
un error.
2.1. Etiquetas que implementan «SimpleTag».
Si queremos definir etiquetas sencillas, donde no se distingue la ejecución del inicio de la etiqueta de su final,
podemos usar la interfaz SimpleTag. Esta interfaz proporciona los siguientes métodos:
• public void doTag(), es llamado una sola vez por cada invocación de la etiqueta. Este único método realiza
las tareas que realizarían los métodos doStartTag() y doEndTag() juntos.
• public void setParent(JspTag parent) para guardar el objeto instanciado para la etiqueta de nivel superior
• public JspTag getParent() para retorna la instancia de la clase de una etiqueta superior.
• public void setJspContext(JspContext pc) para guardar el contexto de la página actual. (JspContext es la
superclase de PageContext.)
Figura 8

168
• public void setJspBody(JspFragment jspBody) proporciona soporte para el contenido del cuerpo. El
contenedor lo invoca con un objeto JspFragment que encapsula el cuerpo de la etiqueta. Se puede utilizar el
método invoke() del fragmento para ejecutarlo y dirigir las salidas a un objeto Writer que se pasa como
argumento (que será el retornado por JspContext.getOut() si el argumento es null).
2.1.1. Características de las etiquetas «SimpleTag».
Cuando una página JSP procesa una etiqueta que implementa la interfaz SimpleTag, el motor JSP realiza las
siguientes acciones en la clase manejadora asociada:
1. Llama a los métodos setJspContext() y setJspBody() para asignar el contexto de la página y el cuerpo de la
página respectivamente.
2. Llama al método setParent() para asignar una etiqueta anidada de nivel superior, si la hubiera.
3. Establece los valores de los atributos, si los hay, mediante métodos setAtributo().
4. Llama al método doTag(), y procesa la funcionalidad de la etiqueta.
Las características de este tipo de etiquetas son:
• Poseen un único método de proceso doTag(), que es el que determina la funcionalidad de la etiqueta.
• Deben evaluar su cuerpo por código.
2.1.2. La clase «SimpleTagSupport».
La clase SimpleTagSupport es usada como base para crear nuevas clases manejadoras de etiquetas sencillas.
Implementa la interfaz SimpleTag y añade métodos adicionales.
Define las siguientes propiedades privadas:
• JspTag parentTag, referencia la etiqueta contenedora.
• JspContext jspContext, referencia el contexto de la página JSP que invoca la etiqueta.
• JspFragment jspBody, referencia el contenido del cuerpo. Posee el método invoke(Writer), el cual permite
evaluar el cuerpo y enviarlo al canal de salida pasado por argumento; si el argumento es null se toma como
canal de salida JspContext.getOut().
Los métodos que añade son:
• JspContext getJspContext(), retorna la propiedad jspContext.
• JspFragment getJspBody(), retorna la propiedad jspBody.
• static final JspTag findAncestorWithClass(JspTag from, Class<?> class), busca la instancia de una de las
etiquetas anidadas contenedoras.
2.1.3. Una etiqueta simple que muestra un saludo.
Añadiremos a la librería LibreriaTest una etiqueta que muestre un mensaje típico de saludo. El uso de esta
etiqueta será el siguiente en una página JSP:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<test:saludo />
</body>
</html>
Para crear la etiqueta comenzaremos añadiendo un fichero Tag Handler al proyecto. En el nodo «Source
Packages» añadiremos un nuevo paquete llamado "test" y mostraremos el menú contextual «Nuevo|Tag
Handler».

169
Figura 9

En el cuadro de diálogo «Nuevo Tag Handler» debemos establecer el nombre de la clase manejadora de la
etiqueta a SaludoTagHanler. Este asistente ofrece la posibilidad de extender SimpleTagSupport (que implementa
SimpleTag) y BodyTagSupport (que implementa Tag). Para este primer ejemplo hay que elegir la opción
«SimpleTagSupport (J2EE 1.4)».

170
Figura 10

Tras pulsar el botón «Siguiente» debemos agregar la nueva etiqueta a la librería creada previamente. También
asignaremos el nombre de etiqueta a saludo, y el contenido del cuerpo a empty. En la parte baja del asistente
podemos agregar atributos a la etiqueta. De momento no agregaremos ninguno.
Figura 11

171
Tras pulsar el botón «Finalizar» se creará la clase test.SaludoTagHandler se mostrará su contenido en el editor
de NetBeans:
package test;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class SaludoTagHandler extends SimpleTagSupport {
@Override
public void doTag() throws JspException {
JspWriter out = getJspContext().getOut();
try {
// TODO: insert code to write html before writing the body content.
// e.g.:
//
// out.println("<strong>" + attribute_1 + "</strong>");
// out.println(" <blockquote>");
JspFragment f = getJspBody();
if (f != null) {
f.invoke(out);
}
// TODO: insert code to write html after writing the body content.
// e.g.:
//
// out.println(" </blockquote>");
} catch (java.io.IOException ex) {
throw new JspException("Error in SaludoTagHandler tag", ex);
}
}
}
La plantilla generada por NetBeans es bastante explícita y nos ofrece un código de ejemplo para poder
escribir el contenido del cuerpo de la etiquete y algo antes y después del mismo. Basta con modificar el
método doTag() para escribir el típico saludo de "¡Hola Mundo!" con formato de título:
public void doTag() throws JspException {
JspWriter out = getJspContext().getOut();
try {
out.println("<h1>¡Hola, Mundo!</h1>");
} catch (java.io.IOException ex) {
throw new JspException("Error en la etiqueta SaludoTagHandler", ex);
}
}
La etiqueta ha quedado definida de la siguiente manera en la librería:
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=
"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
<tlib-version>1.0</tlib-version>
<short-name>test</short-name>
<uri>/com.personal.test</uri>
<tag>
<name>saludo</name>
<tag-class>test.SaludoTagHandler</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
En este primer ejemplo no se ha considerada el cuerpo de la etiqueta ni ningún atributo.
2.1.4. Una etiqueta simple con atributos.
Crearemos ahora una etiqueta llamada <test:correo /> que podamos usar de la siguiente manera:
<body>
<test:correo destinatario="Juan Pérez">

172
Éste es el mensaje del correo.
</test:correo>
</body>
La etiqueta correo tendrá un atributo obligatorio llamado destinatario y un cuerpo. El resultado de la etiqueta
debe ser el escribir el nombre del destinatario en la página y en un párrafo aparte el cuerpo del correo.
Seguiremos los mismos pasos que para la etiqueta saludo:
Figura 12

Pero ahora añadiremos un atributo llamado destinatario. Para que sea obligatorio debemos marcar la casilla
«Atributo requerido», y dejaremos el tipo «java.lang.String».

173
Figura 13

Un atributo puede evaluarse en el momento de la solicitud o en el momento en que la página JSP es


convertida en un servlet dinámico. La primera opción especifica que el valor del atributo puede calcularse
dinámicamente en el momento de la solicitud, mientras que la segunda opción especifica que el valor del
atributo es estático y se determina al generar el servlet.
Las opciones elegidas para esta etiqueta quedarán así:

174
Figura 14

Como la etiqueta debe procesar su cuerpo se ha seleccionado scriptless para el contenido del cuerpo. Las
opciones posibles indican lo siguiente:
• empty, indica que la etiqueta no acepta un cuerpo.
• scriptless (por defecto), indica que el cuerpo puede contener etiquetas estándar y personalizados junto con
texto HTML. Cualquier expresión EL o JSP será evaluada y se mostrará su resultado en la renderización del
cuerpo
• tagdependet, indica otro tipo de contenido del cuerpo, como sentencias SQL pasadas a la etiqueta.
Tras pulsar el botón «Finalizar», se creará la clase CorreoTagHandler. Para ilustrar el uso de la interfaz
SimpleTag, modificaremos el código generado para implementar directamente SimpleTag en vez de extender
SimpleTagSupport.
package test;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class CorreoTagHandler implements SimpleTag {
// Definición de los atributos
private String destinatario;
public void setDestinatario(String destinatario) {
this.destinatario = destinatario;
}
// Datos pasados desde el motor JSP
private JspTag parent;
@Override
public void setParent(JspTag parent) {
this.parent = parent;
}
@Override
public JspTag getParent() {
return this.parent;
}
private JspContext pageContext;
@Override
public void setJspContext(JspContext pc) {

175
this.pageContext = pc;
}
private JspFragment jspBody;
@Override
public void setJspBody(JspFragment jspBody) {
this.jspBody = jspBody;
}
// Método de proceso de la etiqueta
@Override
public void doTag() throws JspException {
JspWriter out = pageContext.getOut();
try {
out.println("<p><strong>" + destinatario + "</strong></p>");
out.print("<p>");
jspBody.invoke(out);
out.print("</p>");
} catch (java.io.IOException ex) {
throw new JspException("Error en etiqueta CorreoTagHandler", ex);
}
}
}
Lo primero a destacar es el método setDestinatario(). Cuando en una etiqueta se define un atributo, el motor
JSP buscará un método setter con el nombre del atributo para pasarle el valor establecido en el fichero JSP.
A continuación, otros métodos setter son invocados por el motor JSP para pasar objetos que representan la
etiqueta contenedora, el contexto de la página, y el cuerpo de la etiqueta.
Por último, el método doTag() procesa la funcionalidad de la etiqueta. En este caso se obtienen del contexto
de la página el canal de salida, y se escribe un párrafo con el destinatario, y otro párrafo con el cuerpo de la
etiqueta.
El código de registro de la etiqueta en la librería LibreriaTest.tld quedaría así:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>correo</name>
<tag-class>test.CorreoTagHandler</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>destinatario</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
2.2. Etiquetas que implementan «Tag».
Se utiliza la interfaz Tag para crear etiquetas con funcionalidades más avanzadas. Esta etiquetas no requieren
renderizar su cuerpo desde el código de la clase manejadora, y permiten diferenciar entre la ejecución de la
etiqueta de inicio de la etiqueta de cierre.
Por tanto permiten los siguientes procesos en una etiqueta personalizada:
<test:etiqueta> <%-- SE PUEDE EJECUTAR UN CÓDIGO AQUÍ --%>
Cuerpo de la etiqueta <%-- EL CUERPO ES RENDERIZADO POR EL MOTO JSP --%>
</test:etiqueta> <%-- SE PUEDE EJECUTAR UN CÓDIGO AQUÍ --%>
2.2.1. Características de las etiquetas «Tag».
Cuando una página JSP procesa una etiqueta que implementa la interfaz Tag, el motor JSP realiza las
siguientes acciones en la clase manejadora asociada:
1. Llama al método setPageContext() para asignar el contexto de la página.
2. Llama al método setParent() para asignar una etiqueta anidada de nivel superior, si la hubiera.
3. Establece los valores de los atributos, si los hay, mediante métodos setAtributo().

176
4. Llama al método doStartTag(), y procesa la funcionalidad inicial de la etiqueta.
5) Si el método doStartTag() devuelve el valor Tag.EVAL_BODY_INCLUDE, el motor JSP renderiza el cuerpo
de la etiqueta, si lo tiene.
Si el método doStartTag() devuelve el valor Tag.SKIP_BODY, el motor JSP no renderiza el cuerpo.
6) Llama al método doEndTag(), y procesa la funcionalidad de cierre de la etiqueta.
7) Llama al método release(), donde se puede liberar cualquier recurso.
Las características de este tipo de etiquetas son:
• Permite delegar en el motor JSP el renderizado del cuerpo de la etiqueta.
• Permite ejecutar algún código antes y después del cuerpo de la etiqueta.
• Permite establecer desde código si se debe renderizar el cuerpo de la etiqueta.
2.2.2. Una etiqueta con parámetro opcional y cuerpo.
Añadiremos a la librería LibreriaTest una etiqueta que muestre un mensaje de saludo personalizado. El uso de
esta etiqueta será el siguiente en una página JSP:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<test:hola cliente='Juan Pérez'>
<p>Bienvenido a nuestra empresa<p>
</test:hola>
</body>
</html>
Al igual que con las etiquetas SimpleTag, comenzaremos añadiendo un fichero Tag Handler al proyecto. En
el nodo «Source Package» añadiremos al paquete "test" un «Nuevo|Tag Handler».
En el cuadro de diálogo «Nuevo Tag Handler» debemos establecer el nombre de la clase manejadora de la
etiqueta a HolaTagHanler. Este asistente ofrece la posibilidad de extender SimpleTagSupport (que implementa
SimpleTag) y BodyTagSupport (que implementa Tag). Para este caso hay que elegir la opción
«BodyTagSupport».

177
Figura 15

Tras pulsar el botón «Siguiente» debemos agregar la nueva etiqueta a la librería creada previamente. También
asignaremos el nombre de etiqueta a hola, y el contenido del cuerpo a JSP. En la parte baja del asistente
podemos agregar atributos a la etiqueta. Añadiremos un atributo llamado cliente. Como será opcional
debemos dejar desmarcada la casilla «Atributo requerido», y dejaremos el tipo «java.lang.String».
Figura 16

178
Las opciones elegidas para esta etiqueta quedarán así:
Figura 17

Como vemos, alguna de las opciones para el contenido del cuerpo es diferente. Las opciones posibles indican
lo siguiente:
• empty, indica que la etiqueta no acepta un cuerpo.
• JSP (por defecto), indica que el cuerpo puede contener etiquetas estándar y personalizadas junto con texto
HTML. Cualquier expresión EL o JSP será evaluada y se mostrará su resultado en la renderización del
cuerpo
• tagdependet, indica otro tipo de contenido del cuerpo, como sentencias SQL pasadas a la etiqueta.
Tras pulsar el botón «Finalizar», se creará la clase HolaTagHandler. NetBeans genera una plantilla de código un
poco compleja, así que la vamos a simplificar para ver todos los métodos que incluye la interfaz Tag:
package test;
import java.io.IOException;
import javax.servlet.jsp.*;
public class HolaTagHandler implements Tag {
// Datos pasados desde el motor JSP
private PageContext pageContext;
@Override
public void setPageContext(PageContext pc) {
this.pageContext = pc;
}
private Tag parent;
@Override
public void setParent(Tag parent) {
this.parent = parent;
}
@Override
public Tag getParent() {
return this.parent;
}
// Definición de los atributos
private String cliente;

179
public void setCliente(String cliente) {
this.cliente = cliente;
}
// Métodos de proceso de la etiqueta
@Override
public int doStartTag() throws JspException {
JspWriter out = pageContext.getOut();
try {
out.println(cliente == null ? "" : "<h2>Hola, sr./sra. " + cliente + "</h2>");
} catch (IOException ex) {
throw new JspException("Excepción en doStarttag.");
}
return Tag.EVAL_BODY_INCLUDE;
}
@Override
public int doEndTag() throws JspException {
return Tag.EVAL_PAGE;
}
@Override
public void release() {
}
}
Se han dispuesto los métodos en el mismo orden en que son invocados por el motor JSP, excepto
getParent(), el cual no es invocado automáticamente.
Primero el motor JSP pasa a la etiqueta el contenido de la página y la instancia de la etiqueta contenedora si
existe. Después busca métodos setter correspondientes a cada atributo. Como el atributo cliente es opcional
puede ocurrir que el método setCliente() no sea invocado; en este caso debemos establecer un valor por
defecto para esta atributo, que en este caso es su valor inicial a null.
A continuación se ejecuta el método doStartTag(). En este método podemos usar el objeto
PageContext.getOut() para obtener el canal de escritura en la página. Es este ejemplo, si la variable cliente es
nula no se escribe nada, sino se escribe una párrafo de saludo. Por último, este método debe decidir si el
motor JSP debe evaluar y renderizar el cuerpo de la etiqueta. Este método puede retornar dos valores:
•Tag.EVAL_BODY_INCLUDE, que indica que debe evaluarse el cuerpo. Este valor de retorno sólo tiene
sentido si hemos establecido el contenido del cuerpo a JSP o tagdependent.
• Tag.SKIP_BODY, que indica que no debe evaluarse el cuerpo.
Después se ejecuta el método doEndTag(). En este método también podemos generar una salida hacia la
página, que se ejecutaría después del renderizado del cuerpo. Este método puede retornar dos valores:
• Tag.EVAL_PAGE, para que el motor JSP sigua evaluando el resto de la página.
•Tag.SKIP_PAGE, para que el motor JSP finalice la interpretación de la página.
Por último es invocado el método release().
El código de registro de la etiqueta en la librería LibreriaTest.tld quedaría así:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>hola</name>
<tag-class>test.HolaTagHandler</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>cliente</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
2.2.3. La clase «TagSupport».
La clase TagSupport implementa la interfaz IterationTag, que a su vez extiende a Tag, proporcionando a su
método funcionalidades por defecto y añade métodos adicionales.

180
Define la siguiente propiedad:
• protected PageContext pageContext, que encapsula el contexto de la página JSP que invoca la etiqueta.
Los nuevos métodos que aporta son:
• static final Tag findAncestorWithClass(Tag desde, Class clase), busca la instancia de una clase manejadora
cuya etiqueta encierre a la etiqueta actual. Este método se aplica para coordinar etiquetas anidadas en varios
niveles. Téngase en cuenta que el método getParent() debe retornar la instancia de la etiqueta anidada
inmediatamente superior. Este método busca desde la instancia de la etiqueta indicada en el primer
parámetro hacia arriba hasta que encuentre una etiqueta que coincida con la clase especificada en el segundo
parámetro.
• void setValue(String k, Object o), asocia un valor con un atributo creado en el ámbito de la etiqueta. Dicho
atributo se conversa durante el ciclo de vida de la etiqueta.
• Object getValue(String k), retorna el valor asociado con un atributo creado mediante el método setValue(),
o null si no existe el atributo.
• void removeValue(String k), eliminar un atributo creado con el método setValue().
• Enumeration<String> getValues(), retorna un enumeración de los atributos creados en el ámbito de la
etiqueta.
Además, reescribe el métododoStartTag() para que retorne el valor SKIP_BODY y el métododoEndTag() para
que retorne el valor EVAL_PAGE
La mayoría de manejadores de etiquetas que necesiten implementar Tag, deben extender TagSupport si sólo
necesitan redefinir pocos métodos.
El código de registro de la etiqueta en la librería LibreriaTest.tld quedaría así:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>hola</name>
<tag-class>test.HolaTagHandler</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>cliente</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
2.2.4. Una etiqueta selectiva que extiende a «TagSupport».
Veremos cómo usar crear una etiqueta condicional que reciba un atributo condicion que debe cumplirse para
evaluar el cuerpo de la etiqueta.
La etiqueta se usará de la siguiente manera:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<test:if condicion="<%=request.getRemoteUser()!=null %>" >
El usuario actual es: <%= request.getRemoteUser()%>
</test:if>
</body>
</html>
En este código de ejemplo, la etiqueta evalúa si hay un usuario autentificado en el sitio web. Si es así se
escribirá el cuerpo de la etiqueta con su nombre, sino no se escribirá nada.
Seguiremos los mismos pasos que en los ejemplos previos para crear y registrar la clase manejadora
IfTagHandler, haciendo que su atributo condicion sea obligatorio.

181
Figura 18

Modificaremos el código generado por NetBeans de la siguiente manera:


package test;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
public class IfTagHandler extends TagSupport {
private boolean condicion;
public void setCondicion(boolean condicion) {
this.condicion = condicion;
}
@Override
public int doStartTag() throws JspException {
return condicion? EVAL_BODY_INCLUDE : SKIP_BODY;
}
}
Como se ve el código se ha simplificado mucho. Simplemente debemos reescribir el método doStartTag()
para evaluar la condición e indicar al motor JSP si debe evaluar o no el cuerpo de la etiqueta.
El código de registro de la etiqueta en la librería LibreriaTest.tld quedaría así:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>if</name>
<tag-class>test.IfTagHandler</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>condicion</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>boolean</type>
</attribute>
</tag>
</taglib>

182
2.3. Etiquetas que implementan «IterationTag».
La interfaz IterationTag extiende a la interfaz Tag, definiendo un método adicional que controla la
reevaluación del cuerpo de la etiqueta:
• int doAfterBody(), es llamado cada vez que el motor JSP evalúa el cuerpo de la etiqueta. El valor de retorno
de este método puede ser:
• IterationTag.EVAL_BODY_AGAIN, para que se vuelva a evaluar el cuerpo.
• Tag.SKIP_BODY, para que se deje de evaluar el cuerpo.
Una clase que implemente IterationTag es tratada como otra que implemente Tag en cuanto a los métodos
doStartTag() y doEndTag(). Después de que el método doStartTag() determine evaluar el cuerpo, se invoca
secuencialmente el método doAfterBody() hasta que este método retorne el valor Tag.SKIP_BODY.
2.3.1. Una etiqueta iterativa.
Veremos cómo utilizar la funcionalidad que nos ofrece la interfaz IteratorTag para crear una etiqueta que
implemente un bucle for para iterar sobre un rango de valores enteros.
El uso de esta etiqueta será el siguiente en una página JSP:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<test:for inicio="1" fin="10" var="valor">
Iteración ${valor} <br />
</test:for>
</body>
</html>
En este código de ejemplo, la etiqueta debe renderizar 10 veces su cuerpo, permitiendo evaluar dentro del
cuerpo un atributo de la página que se llame igual que el contenido del atributo opcional var. Esta variable de
script tomará en cada iteración valores entre inicio y fin.
Seguiremos los mismos pasos que en los ejemplos previos para crear y registrar la clase manejadora
ForTagHandler.

183
Figura 19

Modificaremos el código generado por NetBeans de la siguiente manera:


package test;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
public class ForTagHandler extends TagSupport {
// RECEPCIÓN DE ATRIBUTOS
private int inicio;
public void setInicio(int inicio) {
this.inicio = inicio;
}
private int fin;
public void setFin(int fin) {
this.fin = fin;
}
private String var = "i";
public void setVar(String var) {
this.var = var;
}
// MÉTODOS DE PROCESO
@Override
public int doStartTag() throws JspException {
if (inicio > fin) {
throw new JspException("Rango de valores no válidos");
}
pageContext.setAttribute(var, inicio);
return EVAL_BODY_INCLUDE;
}
@Override
public int doAfterBody() throws JspException {
inicio++;
if (inicio <= fin) {
pageContext.setAttribute(var, inicio);

184
return EVAL_BODY_AGAIN;
} else {
return SKIP_BODY;
}
}
@Override
public int doEndTag() throws JspException {
pageContext.removeAttribute(var);
return EVAL_PAGE;
}
}
En el apartado de atributos, como el atributo var es opcional se le ha puesto como valor por defecto "i". En el
método doStartTag() se evalúa primero si el rango de valores entre inicio y fin es válido, sino se lanza una
excepción. Se crea un atributo de página (conocido como variable de script) con el nombre asignado en el
atributo var e inicializado al valor de inicio y se retorna EVAL_BODY_INCLUDE para que el motor evalúe el
cuerpo por lo menos una vez.
Después de que el motor JSP renderiza el cuerpo de la etiqueta con el valor actual del atributo de script,
invoca el método doAfterBody(). Este método determina si debe seguir iterándose. Debe quedar claro que
antes de llamar a este método el motor JSP ya ha renderizado el cuerpo, y por tanto se está evaluando la
siguiente renderización.
El método doEndTag() se encarga de eliminar la variable de script.
El código de registro de la etiqueta en la librería LibreriaTest.tld quedaría así:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>for</name>
<tag-class>test.ForTagHandler</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>inicio</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>int</type>
</attribute>
<attribute>
<name>fin</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>int</type>
</attribute>
<attribute>
<name>var</name>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
Destacaremos que el atributo var no admitirá expresiones en su contenido (rtexprvalue a false). Esto es
habitual con atributos que se utilizan para crear variables de script.
2.4. Etiquetas que implementan«BodyTag».
La interfaz BodyTag extiende a la interfaz IterationTag y añade métodos adicionales que permiten a la clase
manejadora manipular el contenido del cuerpo evaluado por la etiqueta.
Los nuevos métodos que incluye son:
• void setBodyContent(BodyContent b), asignar el objeto que representa la evaluación del cuerpo de la
etiqueta. Este método es invocado por el motor JSP sólo si el cuerpo no está vacío y el método doStarTag()
retorna EVAL_BODY_BUFFERED.

185
• void doInitBody(), prepara la evaluación del cuerpo. Este método es invocado por el motor JSP después del
método setBodyContent() y antes de la primera vez que el cuerpo es evaluado. El método no se invocará si
el cuerpo está vacío o el método doStarTag() retorna SKIP_BODY o EVAL_BODY_INCLUDE.
Una clase manejadora que implemente BodyTag es tratada como otra que implemente IterationTag, excepto
que el método doStartTag()puede retornar una de las siguientes constantes:
• SKIP_BODY, para que se deje de evaluar el cuerpo.
• EVAL_BODY_INCLUDE, para que el cuerpo se evalúe al menos una vez. El cuerpo será renderizado por el
motor JSP.
• EVAL_BODY_BUFFERED, para que el objeto BodyContent sea creado para capturar la evaluación del cuerpo.
El cuerpo debe ser renderizado por la propia etiqueta.
2.4.1. La clase «BodyContent».
La clase javax.servlet.jsp.tagext.BodyContent es una clase abstracta que extiende a javax.servlet.jsp.JspWriter. Se
utiliza para encapsular la evaluación del cuerpo de una etiqueta de forma que sea accesible a una clase
manejadora que implemente la interfaz BodyTag.
Esta clase tiene métodos que convierten el contenido a un string, para leer el contenido y para vaciarlo. No se
puede invocar su método flush() porque lanza una excepción.
Los métodos que posee son:
• void clearBody(), vacía el cuerpo sin lanzar ninguna excepción.
• Reader getReader(), retorna el valor del BodyContent como un Reader.
• String getString(), retorna el valor del contenido evaluado del cuerpo como un string.
• void writeOut(Writer out), escribe el contenido del cuerpo en un canal de tipo Writer.
• JspWriter getEnclosingWriter(), retorna el canal JspWriter subyacente para enviar respuestas a la página JSP
que evalúan la etiqueta.
2.4.2. La clase «BodyTagSupport».
BodyTagSupport es una clase que implementa la interfaz BodyTag y añade métodos adicionales para manipular
la propiedad bodyContent y un objeto JspWriter.
Define la siguiente propiedad:
• protected BodyContent bodyContent, que encapsula funcionalidades para manipular el contenido del cuerpo.
Los métodos que añade son:
• int doStartTag(), se reescribe para retornar la constanteEVAL_BODY_BUFFERED.
• int doAfterBody(), se reescribe para retornar la constanteSKIP_BODY.
• BodyContent getBodyContent(), retorna el objeto bodyContent.
• JspWriter getPreviousOut(), retorna el canal de salida hacia la página JSP.
2.4.3. Etiqueta que renderiza su cuerpo.
En realidad la clase BodyTagSupport ofrece las mismas funcionalidades que la clase TagSupport, pero con el
añadido de que permite renderizar el cuerpo de la etiqueta desde el código de la clase manejadora. Veremos
un ejemplo de cómo se hace esto.
Crearemos la etiqueta <test:formato />. Esta etiqueta permitirá especificar un elemento HTML de formato
que será aplicado sobre todo su cuerpo. El uso de esta etiqueta será el siguiente en una página JSP:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<test:formato elemento="strong">
Contenido del cuerpo
</test:formato>
</body>
</html>
En este ejemplo, la etiqueta formato renderizará su cuerpo mediante código encapsulándolo entre las
etiquetas <strong>Contenido del cuerpo</strong>.

186
Seguiremos los mismos pasos que en los ejemplos previos para crear y registrar la clase manejadora
FormatoTagHandler.
Figura 20

Modificaremos el código generado por NetBeans de la siguiente manera:


package test;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;
public class FormatoTagHandler extends BodyTagSupport {
// RECEPCIÓN DE ATRIBUTOS
private String elemento;
public void setElemento(String elemento) {
this.elemento = elemento;
}
@Override
// MÉTODOS DE PROCESO
public int doStartTag() throws JspException {
return EVAL_BODY_BUFFERED;
}
@Override
public int doAfterBody() throws JspException {
BodyContent bodyCont = getBodyContent();
JspWriter out = bodyCont.getEnclosingWriter();
try {
out.println("<" + elemento + ">"); // se agrega: <elemento>
bodyCont.writeOut(out); // se agrega el cuerpo
out.println("</" + elemento + ">"); // se agrega: </elemento>
} catch (IOException ex) {
throw new JspException(ex);
}
return SKIP_BODY;

187
}
}
Como el método doStarTag() devuelve el valor EVAL_BODY_BUFFERED, el motor JSP no renderizará el cuerpo
de la etiqueta. El método doAfterBody() se encarga de esto, tal como se muestra en este código.
El código de registro de la etiqueta en la librería LibreriaTest.tld quedaría así:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>formato</name>
<tag-class>test.FormatoTagHandler</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>elemento</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
2.5. Etiquetas que lanzan excepciones «SkipPageException».
Hay ocasiones en las cuales, al procesar una etiqueta, nos interesa detener la ejecución de la página que invoca
la etiqueta. Se puede conseguir esto lanzando una excepción de tipo javax.servlet.jsp.SkipPageException desde
el código de la etiqueta. Cuando la página recibe una excepción SkipPageException cierra la respuesta y envía al
navegador cliente todo lo procesado antes de la excepción.
Por ejemplo, podemos crear una etiqueta llamada testSkip que implemente SimpleTag y que evalúe la
existencia de un atributo de contexto para determinar si se debe o no parar la ejecución del resto de la página:
public void doTag() throws JspException, IOException {
if (getJspContext().getAttribute("unAtributo") != null)
// se procesa la etiqueta
else
throw new SkipPageException();
}
Si un JSP invoca la etiqueta tendremos:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
Esto se escribirá en el navegador.<br>
<c:set var="unAtributo" value="1" />
<test:testSkip />
Esto ya no se escribirá si se lanza una SkipPageException en la etiqueta.
</body>
</html>
Pero, qué pasa si la etiqueta es invocada desde una página incluida en otra. Por ejemplo, la página A incluye a
la página B:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>

188
<body>
<p>Esta página (A) incluye otra página (B). </p>
<jsp:include page="B.jsp" />
<p>Regreso a la página A...</p>
</body>
</html>
Y ahora la página B invoca la etiqueta testSkip.
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<c:set var="unAtributo" value="1" />
<p>Esta página (B) invoca una etiqueta que lanza una SkipPageException.</p>
<test:testSkip />
<p>Algo después de la invocación de la etiqueta...</p>
</body>
</html>
Como resultado de solicitar la página A, el navegador recibe el siguiente contenido:
Esta página (A) incluye otra página (B).
Esta página (B) invoca una etiqueta que lanza una SkipPageException.
Regreso a la página A...
Es decir, la SkipPageException sólo para a la página que invoca directamente la etiqueta que lanza la excepción.
2.6. Etiquetas anidadas.
Podemos incluir etiquetas dentro del cuerpo de otras etiquetas de forma que compartan información entre
ellas. Las etiquetas del cuerpo quedan subordinadas a la etiqueta contenedora, tal como ocurriría con un
método en cuyo cuerpo de código se invocan otros métodos. Sin embargo, la dependencia entre etiquetas
anidadas no queda establecida en la definición de las etiquetas, debe ser algo que se determine por código.
Como ejemplo, vamos a crear etiquetas que implementen la funcionalidad de una estructura switch.Las
etiquetas se podrán usar de la siguiente manera:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<c:set var="unValor" value="2" />
<test:switch valor="${unValor}">
<test:case valor="1">Uno</test:case>
<test:case valor="2" >Dos</test:case>
<test:default>Indefinido</test:default>
</test:switch>
</body>
</html>
Cada una de las etiquetas deberá crearse de manera independiente. Empezaremos por la etiqueta <test:switch
/>. Seguiremos los mismos pasos que en los ejemplos previos para crear y registrar la clase manejadora
SwitchTagHandler. En este caso nos basta con crear una etiqueta que extienda TagSupport y
tenga un atributo
obligatorio llamado valor de tipo Object:

189
Figura 21

Modificaremos el código generado por NetBeans de la siguiente manera:


package test;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
public class SwitchTagHandler extends TagSupport {
public void setValor(Object valor) {
this.setValue("valor", valor);
}
@Override
public int doStartTag() throws JspException {
this.setValue("ejecutado", false);
return EVAL_BODY_INCLUDE;
}
}
Simplemente se almacena el valor a evaluar en el mapa interno que proporciona la clase TagSupport. En el
método doStartTag() se estable un valor "ejecutado" igual a false; si una de las etiquetas anidadas case se
ejecuta deberá poner este valor a true, y de esta manera las demás etiquetas anidadas ya no se evaluarán.
Las etiquetas <test:case /> serán gestionadas por la clase CaseTagHandler y tendrán también un atributo
obligatorio valor de tipo Object:
package test;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
public class CaseTagHandler extends TagSupport {
public void setValor(Object valor) {
this.setValue("valor", valor);
}
@Override
public int doStartTag() throws JspException {
TagHandler parent = (TagHandler) findAncestorWithClass(this, SwitchTagHandler.class);
if (parent == null) {

190
throw new JspException("Falta la etiqueta contenedora switch");
}
if (parent.getValue("ejecutado").equals(false)) { // Si todavía no se ha procesado un caso
Object valorSwitch = parent.getValue("valor");
Object valorCase = this.getValue("valor");
if (valorSwitch.equals(valorCase)) {
parent.setValue("ejecutado", true); // Ya se ha procesado un caso
return EVAL_BODY_INCLUDE;
}
}
return SKIP_BODY;
}
}
Las etiquetas case comprobarán primero que están contenidas dentro de una etiquetas switch. La clase
TagSupport provee el método estático findAncestorWithClass() para obtener la instancia de una etiqueta
contenedora. En este caso, la etiqueta padre más inmediata no tiene que ser de tipo SwitchTagHandler, pero sí
alguna de las etiquetas contenedoras en los niveles superiores.
Para determinar si esta clase debe evaluar su cuerpo se recupera el valor de la etiqueta switch y se compara con
el de la etiqueta case. Si coinciden se procesará el cuerpo y se deja establecido en la etiqueta contenedora que
ya se ha procesado un caso.
La clase manejadora de la etiqueta <test:default /> es parecida a la clase CaseTagHandler, sólo que no define
atributos propios:
package test;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
public class DefaultTagHandler extends TagSupport {
@Override
public int doStartTag() throws JspException {
TagHandler parent = (TagHandler) findAncestorWithClass(this, SwitchTagHandler.class);
if (parent == null) {
throw new JspException("Falta la etiqueta contenedora switch");
}
return parent.getValue("ejecutado").equals(false) ? EVAL_BODY_INCLUDE : SKIP_BODY;
}
}
Si ya fue procesado un caso, se salta su cuerpo y si no lo evalúa. Como se ve las etiquetas hijas deben
coordinarse entre sí a través de su etiqueta contenedora. Obsérvese que las etiquetas hijas se van a evaluar en
el mismo orden en el que aparecen dentro de la etiqueta contenedora. Por tanto, si ponemos una etiqueta
default antes que las etiquetas case, nunca evaluaría su cuerpo.
El código de registro de la etiqueta en la librería LibreriaTest.tld quedaría así:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>switch</name>
<tag-class>test.SwitchTagHandler</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>valor</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.Object</type>
</attribute>
</tag>
<tag>
<name>case</name>
<tag-class>test.CaseTagHandler</tag-class>
<body-content>JSP</body-content>
<attribute>

191
<name>valor</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.Object</type>
</attribute>
</tag>
<tag>
<name>default</name>
<tag-class>test.DefaultTagHandler</tag-class>
<body-content>JSP</body-content>
</tag>
</taglib>

3. Opciones avanzadas en clases manejadoras de etiquetas.


Además de las funcionalidades vistas, las etiquetas permiten automatizar el uso de variables de script, declarar
un número indeterminado de atributos y gestionar clases para realizar inicializaciones y validaciones.
3.1. Variables de script.
La variables de script son atributos del ámbito de la página que son procesadas en el código de la clase
manejadora y sus valores pueden ser utilizados en el cuerpo de la etiqueta.
Las variables de script se definen en la librería junto con la información de la etiqueta que la utiliza. Se deberá
especificar:
•El nombre de la variable de script.
•El tipo de la variable.
•Un booleano indicando si habrá que crear una nueva variable.
•El ámbito de la variable. Pudiendo ser: AT_BEGIN (variable disponible en el interior de la etiqueta y el resto
del JSP),NESTED (variable disponible en el interior de la etiqueta) o AT_END (variable disponible en el resto
del JSP).
Para comprender su uso, crearemos una etiqueta iterador para iterar sobre una colección, que se pasará a
través de un atributo coleccion, y quepermita procesar sus elementos dentro del cuerpo de la etiqueta.
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<%
java.util.Vector vector = new java.util.Vector();
vector.addElement("uno");
vector.addElement("dos");
vector.addElement("tres");
%>
<p>Elementos de la colección:</p>
<test:iterador coleccion="<%= vector %>">
Elemento: ${item} <br />
</test:iterador>
</body>
</html>
El código de la clase manejadora será como sigue:
package test;
import java.util.Collection;
import java.util.Iterator;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.TagSupport;
public class IteradorTagHandler extends TagSupport {
private Iterator iterator; // un iterador para recorrer la colección

192
// Asignación del atributo "coleccion"
private Collection coleccion;
public void setColeccion(Collection coleccion) {
this.coleccion = coleccion;
}
// Al procesar la etiqueta, si la colección está vacía se salta el cuerpo, sino se evalúa.
@Override
public int doStartTag() throws JspException {
if (coleccion.size() > 0) {
iterator = coleccion.iterator();
this.pageContext.setAttribute("item", iterator.next());
return EVAL_BODY_INCLUDE;
} else {
return SKIP_BODY;
}
}
// Cada vez que se evalúa el cuerpo se guarda el elemento actual del iterador en la variable "item".
@Override
public int doAfterBody() throws JspException {
if (iterator.hasNext()) {
this.pageContext.setAttribute("item", iterator.next());
return EVAL_BODY_AGAIN;
} else {
return SKIP_BODY;
}
}
// Se liberan los recursos asignados
@Override
public void release() {
coleccion = null;
iterator = null;
}
}
El archivo TLD deberá contener las siguientes declaraciones:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>iterador</name>
<tag-class>test.IteradorTagHandler</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>coleccion</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.util.Collection</type>
</attribute>
<variable>
<name-given>item</name-given>
<variable-class>java.lang.Object</variable-class>
<declare>true</declare>
<scope>AT_BEGIN</scope>
</variable>
</tag>
</taglib>
Pero es habitual que el nombre de una variable de script pueda ser establecido a través de un atributo var de la
etiqueta. Por ejemplo:
<test:iterador coleccion="<%= vector %>" var="elemento" >
Elemento: ${elemento} <br />
</test:iterador>
Para conseguir esto deberemos realizar las siguientes modificaciones en el TLD:
193
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>iterador</name>
<tag-class>test.IteradorTagHandler</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>coleccion</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.util.Collection</type>
</attribute>
<attribute>
<name>var</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<variable>
<name-from-attribute>var</name-from-attribute>
<variable-class>java.lang.Object</variable-class>
<declare>true</declare>
<scope>AT_BEGIN</scope>
</variable>
</tag>
</taglib>
Ahora el nombre de la variable de script se obtiene a partir del nombre del atributo var. En este caso es
necesario que el atributo var no admita expresiones, sea obligatorio y de tipo String.
La clase IteradorTagHandler quedará modificada de la siguiente manera:
package test;
import java.util.Collection;
import java.util.Iterator;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.TagSupport;
public class IteradorTagHandler extends TagSupport {
// Asignación de atributos
private String var;
public void setVar(String var) {
this.var = var;
}
private Iterator iterator;
private Collection coleccion;
public void setColeccion(Collection coleccion) {
this.coleccion = coleccion;
}
// Métodos de proceso
@Override
public int doStartTag() throws JspException {
if (coleccion.size() > 0) {
iterator = coleccion.iterator();
this.pageContext.setAttribute(var, iterator.next());
return EVAL_BODY_INCLUDE;
} else {
return SKIP_BODY;
}
}
@Override
public int doAfterBody() throws JspException {
if (iterator.hasNext()) {
this.pageContext.setAttribute(var, iterator.next());

194
return EVAL_BODY_AGAIN;
} else {
return SKIP_BODY;
}
}
// Se liberan los recursos asignados
@Override
public void release() {
coleccion = null;
iterator = null;
}
}
También se puede configurar varias variables de script mediante una clase que extienda TagExtraInfo,
reescribiendo su método getVariableInfo(). Este método devuelve un array de objetos VariableInfo. Se creará
uno de estos objetos por cada variable a definir, especificándose:
- Nombre variable.
- Clase de la variable.
- Booleano indicando si habrá que crear una nueva variable.
- Ámbito de la variable.
Por ejemplo, en el siguiente código se define la variable de script item:
package es.deusto.customtags;
public class ItemTagInfo extends TagExtraInfo {
public VariableInfo[] getVariableInfo(TagData data) {
return new VariableInfo[] {new VariableInfo("item", "java.lang.Object", true, VariableInfo.AT_BEGIN) };
}
}
En la librería de etiquetas se declarará la variable de script referenciando esta clase mediante el elemento <tei-
class /> en vez del elemento <variable />.
<tei-class>test.ItemTagInfo</tei-class>
3.2. Atributos dinámicos.
En las etiquetas personalizadas es posible declarar un número indeterminado de atributos. Por ejemplo,
podemos usar una etiqueta como la siguiente:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<test:colores colorBase="blanco" color1="verde" color2="azul" color2="rojo" />
</body>
</html>
Donde podríamos haber añadido más atributos con más colores.Para poder aplicar esta funcionalidad se
requieren dos cosas:
1) En el TLD debemos aplicar el elemento <dynamic-attributes>true</dynamic-attributes> al final de la
definición de la etiqueta personalizada. El código del TLD quedaría como el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>colores</name>
<tag-class>test.ColoresTagHandler</tag-class>
<attribute>
<name>colorBase</name>
<required>true</required>
<type>java.lang.String</type>

195
</attribute>
<dynamic-attributes>true</dynamic-attributes>
</tag>
</taglib>
En la etiqueta colores se define un atributo fijo llamado colorBase,
y cualquier otro atributo que incluya la
etiqueta será considerado como un atributo dinámico.
2) La clase manejadora debe ahora implementar la interfaz javax.servlet.jsp.tagext.DynamicAttributes. Esta
interfaz declara un único método:
public void setDynamicAttribute(String uri, String localName, Object value) throws JspException
Este método es llamado para cada atributo que no está definido en la declaración de la etiqueta en su TLD.
El primer argumento, uri, es el espacio de nombres del atributo o null si se utiliza un espacio de nombres
por defecto; el segundo argumento, localName, es el nombre asignado al atributo; y el tercer argumento,
value, es el valor asignado al atributo.
El código de la clase manejadora podría quedar como sigue:
package test;
import java.util.HashMap;
import javax.servlet.jsp.tagext.DynamicAttributes;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class ColoresTagHandler extends SimpleTagSupport implements DynamicAttributes {
// RECEPCIÓN DE LOS ATRIBUTOS FIJOS
private String colorBase;
public void setColorBase(String colorBase) {
this. colorBase = colorBase;
}
// RECEPCIÓN DE LOS ATRIBUTOS DINÁMICO
HashMap colores = new HashMap(); // mapa para guardar los atributos dinámicos
public void setDynamicAttribute(String uri, String localName, Object value) throws JspException {
colores.put(localName, value); // se guarda el atributo dinámico para su posterior uso
}
// MÉTODOS DE PROCESO
public void doTag() throws JspException {
// se hace algo con el atributo "colorBase" y el mapa "colores"
}
}
3.3. Inicializaciones en una librería.
A veces el uso de las etiquetas de una librería requiere que se realicen ciertas inicializaciones previas. El
fichero TLD ofrece la posibilidad de registrar clases observadoras que implementen las interfaces
ServletContextListener y HttpServletContextListener.
Por ejemplo, puede ser necesario crear e inicializar una lista en el ámbito de sesión antes de que se utilice una
etiqueta de la librería Items.tld. Esta librería puede definirse de la siguiente manera:
<?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>item</short-name>
<uri>www.dominio.com/items</uri>
<listener>
<listener-class>oyentes.InicializadorItems</listener-class>
</listener>
....................
</taglib>
Donde la clase InicializadorItems puede ser como sigue:
package oyentes;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class InicializadorItems implements HttpSessionListener {
@Override

196
public void sessionCreated(HttpSessionEvent se) {
se.getSession().setAttribute("lista", new ArrayList());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
se.getSession().removeAttribute("lista");
}
}
Cada vez que se inicie una sesión se dejará disponible el atributo de sesión lista, que será eliminado cuando
finalice la sesión.
3.4. Validación de la página.
Se pueden establecer condiciones que debe cumplir una página para usar las etiquetas de un TLD. Estas
condiciones se pueden establecer en una clase que extienda javax.servlet.jsp.tagext.TagLibraryValidator, y que
quede registrada en la propia librería.
Supongamos que creamos una librería de etiquetas llamada LibreriaCore.tld que requiere que la página JSP
donde se utilice tenga registrada la librería Core de JSTL.
Podemos definir el TLD de la siguiente manera:
<?xml version="1.0" encoding="UTF-8"?>
<taglib version = "2.1" xmlns = "http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation =
"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
<tlib-version>1.0</tlib-version>
<short-name>core</short-name>
<uri>com.personal.core</uri>
<validator>
<validator-class>validadores.CoreValidator</validator-class>
<init-param>
<param-name>urlCore</param-name>
<param-value>http://java.sun.com/jsp/jstl/core</param-value>
</init-param>
</validator>
……………………………………..
</taglib>
En el nodo <validator> se registra una clase llamadavalidadores.CoreValidator, la cual establecerá los criterios
que debe cumplir la página que utilice esta librería. Esta clase recibirá un parámetro de inicialización con la uri
de la librería Core de JSTL. Esta clase deberá comprobar que el código de la página JSP debe contener esta
uri, y sino no validará la página. Esta clase se define de la siguiente manera.
package validadores;
import java.io.*;
import java.util.Map;
import javax.servlet.jsp.tagext.*;
public class CoreValidator extends TagLibraryValidator {
@Override
public void release() {
// Se utiliza para liberar cualquier recurso.
}
@Override
public ValidationMessage[] validate(String prefix, String uri, PageData page) {
ValidationMessage[] mensajes = null;
InputStream in = page.getInputStream();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
String contenido = "";
String linea;
while ((linea = reader.readLine()) != null) {
contenido += linea;
}
if (!contenido.contains(getInitParameters().get("urlCore").toString())) {
mensajes = new ValidationMessage[] {
new ValidationMessage(null, "Falta referencia a la librería Core de JSTL")};

197
}
} catch (Exception ex) {
}
return mensajes;
}
}
El método principal de la clase TagLibraryValidator es validate(). Este método debe retornar null si se valida la
página y un array de objetos ValidationMensaje si no se valida la página. Para validar la página se utiliza el
tercer parámetro de tipo PageData, el cual proporciona una canal para leer el contenido de la página.
En este código se lee todo el contenido de la página y se busca un texto proporcionado por el parámetro de
inicialización "urlCore".
Un objeto ValidationMensaje puede proporcionar el id de un elemento JSP que haya fallado a efectos de
validación y un mensaje de texto.
3.5. Validación de una etiqueta.
A veces se declaran dos o más atributos opcionales en una etiqueta personalizada, pero interesa que a uno y
sólo a uno de ellos se le pase un valor. En estos casos podemos establecer una validación sobre la propia
etiqueta.
Esta validación se puede establecer en una clase que extienda de javax.servlet.jsp.tagext.TagExtraInfo. Por
ejemplo, supongamos una etiqueta con tres atributos: a, b y c. Estos atributos serán alternativos y sólo uno de
ellos puede recibir valor. Crearemos una clase que valide esto:
package validadores;
import java.util.Enumeration;
import javax.servlet.jsp.tagext.TagData;
import javax.servlet.jsp.tagext.TagExtraInfo;
import javax.servlet.jsp.tagext.ValidationMessage;
public class ValidadorEtiqueta extends TagExtraInfo {
@Override
public ValidationMessage[] validate(TagData data) {
return super.validate(data);
}
@Override
public boolean isValid(TagData data) {
Enumeration<String> atributos = data.getAttributes();
int contador = 0;
while (atributos.hasMoreElements()) {
String nombre = atributos.nextElement();
if (nombre.equals("a") || nombre.equals("b") || nombre.equals("c")) {
contador ++;
}
}
return contador == 1;
}
}
Elmétodo isValid() de esta clase determina la validación de un etiqueta. En este ejemplo se itera sobre los
atributos pasados a la etiqueta evaluando cuantos son pasados. Sólo se validará la etiqueta si aparece sólo uno
de los atributos a, b o c.
Como el método isValid() retorna un booleano que no indica el motivo de la no validación, se puede reescribir
alternativamente el método validate(), el cual retornará algún ValidationMessage sin no valida a la etiqueta.
Ahora debemos registrar esta clase en la declaración de la etiqueta dentro del TLD:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………
<tag>
<name>et</name>
<tag-class>test.EtiquetaTag</tag-class>
<body-content>scriptless</body-content>
<tei-class>validadores.ValidadorEtiqueta</tei-class>
<attribute>

198
<name>a</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<attribute>
<name>b</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<attribute>
<name>c</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>

4. Ficheros de etiqueta
Desde JSP 2.0 se permite desarrollar una etiqueta personalizada como un fichero de tag (tag file). Un «tag file»
es un fichero de texto donde se utilizan elementos JSP para todas las partes dinámicas, y que tiene la misma
funcionalidad que una clase manejadora de etiqueta.
Se diferencia de un JSP en que:
• Es un fichero con extensión .tag
• Usa una directiva<%@tag %> en vez de una directiva<%@page %>.
• Permite especificar entrada de datos y salidas con directivas válidas sólo en tag files.
Los ficheros de etiqueta pueden ubicarse en dos localizaciones: en el directorio «/WEB-INF/tags/» o en uno de
sus subdirectorios, o bien en un archivo JAR ubicado dentro del directorio «/WEB-INF/lib/» de la aplicación
Web. El nombre de la carpeta donde esté ubicado el «tag file» actúa como prefijo del mismo.
4.1. Estructura de un «tag file».
Un fichero de etiqueta debe tener la siguiente estructura básica:
<%@tag pageEncoding="UTF-8"%>
<%-- Lista de atributos de la etiqueta: --%>
<%@attribute name="message"%>
<%-- El contenido de la etiqueta se especifica a continuación.
Como ejemplo se muestra el valor del atributo message --%>
<h2>${message}</h2>
La directiva <%@tag %> determina varias propiedades de la etiqueta. Sus atributos son:
• display-name. Un nombre corto para ser mostrado por herramientas. Por defecto se utiliza el mismo
nombre que el archivo, pero sin la extensión.
• body-content. Establece el tipo de contenido del cuerpo de la etiqueta. Los posible valores son: empty (si
no incluye cuerpo), scriptless (por defecto) o tagdependent (el cuerpo es tratado como texto plano).
• dynamic-attributes. Indica si la etiqueta soporta atributos adicionales con nombres dinámicos.
• small-icon. Establece una Url relativa a un archivo de imagen que contiene un icono pequeño que será
usado por herramientas para asociar con la etiqueta.
• description. Estable una descripción de la etiqueta.
• example. Establece un ejemplo de uso de la etiqueta.
• language. Estable el lenguaje de las instrucciones. Por defecto es "java".
• import. Permite importar clases de java.
• pageEncoding. Establece el juego de caracteres.
• isELIgnored. Puede ser "true" o "false" para indicar si deben ignorarse las expresiones EL.
La directiva <%@attribute %> determina la existencia de atributos en la etiqueta. Sus atributos son:
• name. El nombre del atributo.
• required. Indica si el atributo es requerido ("true") u opcional ("false", por defecto).
• fragment. Indica si el atributo es un fragmento ("true") que debe ser evaluado por el manejador de la
etiqueta, o un atributo normal ("false", por defecto) que debe ser evaluado por el contenedor antes de ser
pasado. Si el valor es "true" se establece rtexprvalue a "true" y type a "javax.servlet.jsp.tagext.JspFragment".

199
• rtexprvalue. Indica si el valor del atributo puede ser calculado en tiempo de ejecución como una expresión
("true", por defecto) o es pasado literalmente ("false").
• type. El tipo del valor del atributo. Por defecto es "java.lang.String".
• description. Una descripción del atributo.
4.2. Primer ejemplo de un «tag file».
NetBeans también proporciona soporte para crear tag files. Debemos agregar un nuevo fichero de tipo «Tag
File» en la categoría «Web»
Figura 22

En el cuadro de diálogo «Nuevo Tag File» debemos poner nombre a la etiqueta (para este ejemplo saluda).
Con este nombre se creará el fichero con extensión .tag. Por defecto el asistente ubica el fichero en la carpeta
/WEB-INF/tags/, aunque también podemos seleccionar una subcarpeta.

200
Figura 23

También se ofrece la opción de añadir el tag file a un fichero TLD. En este caso debemos indicar el fichero y
un nombre para la etiqueta.
Al finalizar, NetBeans creará un fichero con estructura básica:
Figura 24

Podemos modificar este código para crear una etiqueta que podamos utilizar de la siguiente manera:
<tags:saluda nombre="Pedro" />
La modificación cambiará el nombre del atributo e incluirá un texto de saludo. También se establece que no
se permite cuerpo:
<%-- FICHERO saluda.tag --%>
<%@tag body-content="empty" description="Saludo personalizado" pageEncoding="UTF-8"%>
<%@attribute name="nombre"%>
<h2>Saludos, ${nmbre}</h2>

201
Si queremos utilizar esta etiqueta en una página JSP también deberemos registrar una librería con la directiva
<%@taglib %>, pero ahora la carpeta que contiene al tag file actuará como librería y en vez del atributo uri
debemos especificar en el atributo tagdir su ubicación respecto a la carpeta raíz:
<%@taglib prefix="tags" tagdir="/WEB-INF/tags/" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<tags:saluda nombre="Pedro" />
</body>
</html>
4.3. Acceso al valor de los atributos.
Una vez declarado un atributo con la directiva <%@attribute %>, podemos acceder al valor pasado al atributo
de dos formas. Mediante una expresión EL con la sintaxis:
${atributo}
O bien mediante una expresión JSP con la sintaxis:
<%= this.atributo %>
4.4. Variables de script.
La directiva <%@variable %> permite declarar una variable de script que será visible por la página que invoca
a la etiqueta.
Los atributos de esta directiva son:
• name-given o name-from-attribute. Define el nombre de la variable EL que será usada por la página que
invoca a la etiqueta. El atributo name-from-attribute obtiene el nombre de la variable a partir del valor de
uno de los atributos de la etiqueta.
• alias. Define una variable, local a la etiqueta, que contendrá el valor de la variable EL. El contenedor
sincronizará este valor con la variable. Es requerido cuando se especifica name-from-attribute.
• variable-class. Especifica el tipo de la variable, siendo por defecto "java.lang.String".
• declare. Indica si la variable es declarada ("true", por defecto) o no ("false").
• scope. Determina la visibilidad de la variable. Los valores posibles son AT_BEGIN (variable disponible en
interior etiqueta y el resto del JSP), NESTED (variable solo disponible en el interior de la etiqueta, por
defecto) o AT_END (variable disponible solo en el resto del JSP).
• description. Una descripción de la variable.
El siguiente ejemplo declara una etiqueta llamada suma. Esta etiqueta permitirá sumar el valor de dos
atributos var1 y var2, y dejará disponible el resultado en una variable de script llamada resultado.
<%-- FICHERO suma.tag --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@tag body-content="empty" pageEncoding="UTF-8"%>


<%@attribute name="var1" required="true" rtexprvalue="true" type="java.lang.Integer" %>
<%@attribute name="var2" required="true" rtexprvalue="true" type="java.lang.Integer" %>
<%@variable name-given="resultado" scope="AT_END" variable-class="java.lang.Long" %>

<c:set var="resultado" value="${var1+var2}" />


Hay que tener en cuenta, sobre el tipo de los atributos y variables de script, que no se admiten tipos
primitivos. En los tag files debemos usar siempre tipos de clases. Al realizar la suma de los atributos var1 y
var2 es necesario definir la variable de script de tipo Long, puesto que la expresión EL retornará un Long.
Al definir la variable de script con scope="AT_END" ésta estará disponible en la página JSP después de
evaluarse la etiqueta.
<%@taglib prefix="tags" tagdir="/WEB-INF/tags/" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>

202
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<tags:suma var1="45" var2="76" />
El resultado es ${resultado}
</body>
</html>
Pero en muchos escenarios es preferible que el usuario determine el nombre de la variable de script. Para ello
modificaremos el tag file de la siguiente manera:
<%-- FICHERO suma.tag --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@tag body-content="empty" pageEncoding="UTF-8"%>


<%@attribute name="var1" required="true" rtexprvalue="true" type="java.lang.Integer"%>
<%@attribute name="var2" required="true" rtexprvalue="true" type="java.lang.Integer"%>
<%@attribute name="var" required="true" rtexprvalue="false" type="java.lang.String"%>
<%@variable name-from-attribute="var" alias="resul" scope="AT_END" variable-class="java.lang.Long" %>

<c:set var="resul" value="${var1+var2}" />


Se ha añadido un nuevo atributo var, que es el que dará nombre a la variable de script. Es necesario que este
atributo sea obligatorio, no admita expresiones y sea de tipo String.
Ahora la variable de script se define con name-from-attribute en vez de name-given. Como debemos
referenciarlo con algún nombre también se le define un alias con alias="resul". Cualquier uso de este alias será
sincronizado por el motor JSP con su nombre real.
Ahora ya podemos utilizar la etiqueta de la siguiente manera en un fichero JSP:
<tags:suma var1="45" var2="76" var="valor" />
El resultado es ${valor}
4.5. Atributos dinámicos.
Los «tag file» también permiten declarar un número indeterminado de atributos. Por ejemplo, podemos
modificar la etiqueta suma para que admita un número indeterminado de operando:
<%@taglib prefix="tags" tagdir="/WEB-INF/tags/" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<tags:suma var="valor" a="45" b="76" c="78" />
El resultado es ${valor}
</body>
</html>
El código para implementar esta etiqueta implica que debemos asignar el atributo dynamic-attributes de la
directiva @tag. Este atributo define un mapa cuyas claves son los nombres de los atributos de la etiqueta, y
los valores asociados son los valores pasados a cada atributo.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@tag dynamic-attributes="operandos" body-content="empty" pageEncoding="UTF-8"%>

<%@attribute name="var" required="true" rtexprvalue="false" type="java.lang.String"%>


<%@variable name-from-attribute="var" alias="resul" scope="AT_END" variable-class="java.lang.Long" %>

<c:set var="resul" value="0" />


<c:forEach var="operando" items="${operandos}">
<c:set var="resul" value="${resul + operando.value}" />
</c:forEach>

203
Ahora el código inicializa la variable de script a cero e itera sobre el mapa de los atributos dinámicos. Como
operandos es un mapa formado por elementos key=value, se acumula la propiedad value de cada elemento.
4.6. Evaluación de fragmentos pasados a los «tag file».
Cuando se ejecuta un fichero de etiqueta el contenedor web le pasa dos tipos de fragmentos: los atributos-
fragmento y el cuerpo de la etiqueta.
Dentro de la etiqueta se puede usar el elemento <jsp:invoke /> para evaluar un atributo-fragmento y el
elemento <jsp:doBody /> para evaluar el cuerpo. El resultado de evaluar un fragmento puede ser enviado al
canal de respuesta o a una variable EL indicada en el atributo var de ambos elementos, o a un java.io.Reader
indicado en el atributo varReader. También se puede indicar el nivel en el cual se crearán las variables EL.
El siguiente ejemplo muestra cómo acceder al valor de los atributos:
<%-- Archivo JSP que utiliza una etiqueta con dos atributos (uno como fragmento) --%>
<%@taglib prefix="tags" tagdir="/WEB-INF/tags/" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<tags:carta para="Pedro" >
<jsp:attribute name="contenido">
Querido amigo…
</jsp:attribute>
</tags:carta>
</body>
</html>
Con la sintaxis <jsp:attribute /> podemos utilizar atributos cuyo contenido puede ocupar varias líneas. Estos
atributos se pasan a la etiqueta de manera diferente a los atributos embebidos en la etiqueta de inicio.
Por tanto en la definición del tag file se diferenciará estos atributos con fragment="true":
<%-- FICHERO carta.tag --%>
<%@tag pageEncoding="UTF-8"%>
<%@ attribute name="para" required="true" %>
<%@ attribute name="contenido" required="true" fragment="true" %>
<%-- Se muestra la carta: --%>
<h3>Destinatario: ${para}</h3>
<jsp:invoke fragment="contenido" />
La acción <jsp:invoke /> se encarga de renderizar el contenido del atributo de tipo fragmento.
El siguiente ejemplo muestra una variante de la etiqueta «carta.tag» para obtener el contenido de la carta a
partir del cuerpo de la etiqueta. Queremos utilizar la etiqueta de la siguiente manera:
<body>
<tags:carta para="Pedro" >
<jsp:attribute name="preambulo">
Querido amigo…
</jsp:attribute>
<jsp:body>
Estoy encantado de ...
</jsp:body>
</tags:carta>
</body>
Importante. Hay que destacar que cuando una etiqueta incluye un atributo de fragmento,
obligatoriamente su cuerpo debe encapsularse con la etiqueta <jsp:body />.
Ahora, el tag file quedará así:
<%-- FICHERO carta.tag --%>
<%@tag pageEncoding="UTF-8"%>
<%@attribute name="para" required="true"%>
<%@ attribute name="preambulo" required="true" fragment="true" %>
<%-- Se muestra la carta: --%>

204
<h3>Destinatario: ${para}</h3>
<h4><jsp:invoke fragment="preambulo" /></h4>
<h4><jsp:doBody /></h4>
Si una etiqueta no usa atributos-fragmento, el uso de <jsp:body> para encapsular su cuerpo es opcional.
4.7. «Tag files»referenciados mediante un TLD.
Podemos incluir los «tag file» en una librería TLD de forma parecida a como se hace con una clase
manejadora de etiqueta. Para ello debemos utilizar el elemento <tag-file> dentro de un archivo TLD de la
siguiente forma:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
……………………..
<tag-file>
<name>carta</name>
<path>/WEB-INF/tags/carta.tag</path>
</tag-file>
</taglib>
Podemos usar la nueva etiqueta tal como se haría con una etiqueta creada con el elemento <tag>.

5. Técnicas avanzadas con TLDs


Además de definir etiquetas que podemos utilizar en ficheros JSP, un TLD también permite declarar métodos
que podemos invocar desde expresiones EL. Veremos además cómo crear un fichero JAR que contenga
librerías de etiqueta que podamos reutilizar en varias aplicaciones web.
5.1. Invocación de métodos desde expresiones EL.
Desde una expresión EL podemos invocar un método público de una clase usando la siguiente sintaxis:
${ prefijo:nombre_de_metodo(parametro1, parametro2, ... ) }
La función debe ser declarada en una librería TLD con la siguiente sintaxis:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
<tlib-version>1.0</tlib-version>
<short-name> prefijo </short-name>
<uri> la_uri_de_la_librería </uri>
…………………………..
<function>
<name> nombre_de_metodo </name>
<function-class> nombre_de_la_clase </function-class>
<function-signature>
tipo_de_retorno nombre_de_metodo ( tipo_parametro1, tipo_parametro2, ...)
</function-signature>
</function>
</taglib>
La clase Java que contenga al método no tiene que implementar ninguna interfaz en especial, pero el método
debe ser público y estático. Por ejemplo, supongamos la siguiente clase:
package test;
public class MetodosEL {
public static int suma(int a, int b) {
return a + b;
}
}
Vamos a incluir el método suma() en el TLD LibreriaTest.tld para que pueda ser usado en una expresión EL
mediante el nombre sumar().
<?xml version="1.0" encoding="UTF-8"?>
<taglib version = "2.1" xmlns = "http://java.sun.com/xml/ns/javaee"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation =
"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
<tlib-version>1.0</tlib-version>
<short-name>test</short-name>
<uri>/com.personal.test</uri>

205
……………………………..
<function>
<name>sumar</name>
<function-class>test.MetodosEL</function-class>
<function-signature>
int suma(int,int)
</function-signature>
</function>
</taglib>
Ahora podemos utilizar esta función en una página JSP:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
${test:sumar(5,6)}
</body>
</html>
Sólo debemos tener en cuenta lo siguiente sobre el método:
• Debe ser pública y estática.
• Puede ser un método de tipo void.
• No debe tener parámetros variables.
5.2. ¿Cómo distribuir librerías de etiquetas mediante ficheros JAR?
Al igual que las librerías JSTL son distribuidas como ficheros JAR, podemos empaquetar en un fichero JAR
nuestras librerías de etiquetas, clases manejadoras y tag files.
Para ello empezaremos creando un nuevo proyecto de Java vacío con NetBeans. En este proyecto debemos
crear el directorio META-INF.
Figura 25

La estructura del proyecto debe corresponderse con las siguientes reglas:


• Debe incluirse un directorio META-INF en el primer nivel de fichero JAR. En esta carpeta deben
ubicarse directamente los ficheros TLD.
• Las clases manejadoras de las etiquetas deben organizarse dentro de sus paquetes tal como se haría con
cualquier otro proyecto de Java.
• Para incluir tag files, deben ubicarse dentro del directorio META-INF/tags o un subdirectorio de éste. Los
tag files deben ser referenciados desde un TLD

206
Podemos compilar el proyecto y generar un fichero JAR usando la opción «Limpiar y construir proyecto».
Esta opción genera un fichero JAR con el nombre del proyecto en la carpeta dist. Podemos distribuir este
fichero y utilizarlo de igual manera que se utilizan las librerías JSTL.

PRÁCTICA
01. Debéis crear un nuevo proyecto web con una librería de etiquetas llamada "funciones.tld". En esa librería
debéis añadir dos versiones de una etiqueta personalizada denominada "iteraSegun" que será usada de la
siguiente forma:
<func:iteraSegun lista="unaLista" >
Índice ${i}: ${elemento}
</func:iteraSegun>
Esta etiqueta debe procesar el cuerpo tantas veces como elementos tenga un objeto de tipo lista que se pase
como valor en el atributo "lista". Dentro del cuerpo del elemento deben estar disponibles dos atributos
llamados i (que devolverá el índice de iteración empezando por 1) y elemento (que contendrá el elemento
actual de la lista).
Un ejemplo para pasar una lista en la etiqueta es el siguiente:
<%
java.util.ArrayLista miLista = new java.util.ArrayList();
miLista.add("uno");
miLista.add("dos");
%>
<func:iteraSegun lista="<%= miLista %>">
Índice ${i}: ${elemento} <br />
</func:iteraSegun>
En este ejemplo, el texto "Un texto a iterar" debe aparecer dos veces en la página JSP y mostrar:
Índice 1: uno
Índice 2: dos
Crea primero esta etiqueta con el nombre iteraSegun1 usando una clase manejadora (Tag handler), y después
con el nombre iteraSegun2 usando un Tag file.

02. Debéis crear una librería de etiquetas llamada "carro.tld" que usará el prefijo carro. En estalibrería se
incluirán etiquetas y funciones para gestionar un carro de compra a nivel del ámbito de sesión. Cread primero
una clase llamada Producto con dos propiedades: codigo (un valor entero) y nombre (un string). La librería
gestionará internamente una lista de objetos Producto.
Añadid a la librería las siguientes etiquetas:

<carro:vacia />
Debe vaciar la lista de productos. No tiene atributos.

<carro:add producto="${unProducto}" />


Debe añadir un producto a la lista. Tiene un atributo obligatorio producto que recibirá un objeto Producto
como argumento.

<carro:tabla begin="0" end="2" />


Debe renderizar en la página una tabla con filas de productos entre los índices indicados. El atributo begin
indica el primer índice, y el atributo end el último índice exclusive. El aspecto de la tabla debe ser parecido
al siguiente:
CÓDIGO NOMBRE
1002 Producto 1002
1003 Producto 1003
También debes incluir en la librería una función que devuelva la lista de productos como objeto de tipo List.
Esta función debe poder invocarse con la siguiente expresión EL:

${carro:listaCarro}

207
UNIDAD 16. ACCESO A DATOS. SEGURIDAD.
PATRONES DE DISEÑO
1. Tecnologías de acceso remoto a datos.
Cuando todos los componentes de una aplicación web se ejecutan en el mismo servidor, dentro de una
misma Máquina Virtual, las cosas son simples a la hora de referenciar métodos de clases. Pero esto se
complica cuando necesitamos invocar métodos de componentes que se ejecutan en otros servidores u otras
Máquinas Virtuales.
Java y J2EE proporcionan dos mecanismos para invocar el código de objetos remotos a través de una red: la
tecnología RMI y la tecnología JNDI.
1.1. RMI.
RMI es el acrónimo de Remote Method Invocation (Invocación Remota de Métodos), un mecanismo que
simplifica enormemente el proceso de obtener objetos mediante comunicaciones a través de una red.
Se plantea el siguiente problema:
En una Máquina Virtual A se ha cargado una clase, y desde otra Máquina Virtual B queremos instanciar un
objeto de dicha clase e invocar sus métodos. En la Máquina B necesitamos un objeto local que permita
hacer las invocaciones a los métodos de la instancia remota de la Máquina A.
La tecnología RMI se encarga de resolver este problema.
Figura 1

Como se muestra en este esquema, desde una máquina cliente se quiere invocar el método getDato() de un
objeto remoto que se está ejecutando en una máquina servidora. Usando RMI, se crea un objeto proxy (el
objeto stub) que es enviado al lado cliente y se registra usando algún tipo de registro. Cuando el cliente quiera
invocar el método lo hará sobre el stub, el cual se encargará de las comunicaciones para realizar la invocación
al objeto remoto de la máquina servidora. En el lado servidor, por su parte, se crea una instancia de la clase
invocada (el objeto remoto) que es invocado por el stub.
Ejemplo de RMI.
Crearemos un sencillo ejemplo de una aplicación servidora y cliente RMI. En el servidor debemos seguir los
siguientes pasos:
• Crear una interfaz remota. Esta interfaz contendrá los métodos que queramos invocar remotamente. Esta
interfaz debe implementar la interfazjava.rmi.Remote. Todos aquellos métodos que puedan ser invocados
remotamente deben relanzar una RemoteException.
package rmitest;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface InterfazRemota extends Remote {
int suma(int a , int b) throws RemoteException;
}
• Crea la implementación de la clase remota. La clase remota debe implementar la interfaz remota además
de extender a java.rmi.server.UnicastRemoteObject.
package rmitest;
import java.rmi.RemoteException;

208
import java.rmi.server.UnicastRemoteObject;
public class ClaseRemota extends UnicastRemoteObject implements Interfaz {
public ClaseRemota() throws RemoteException {
}
@Override
public int suma(int a, int b) throws RemoteException {
return a+b;
}
}
Esta interfaz debe distribuirse a las aplicaciones cliente.
• Registrar la clase remota en RMI. Para ello crearemos una clase Servidor que debe ejecutarse para que
registre una instancia de la clase ClaseRemota:
package rmitest;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.net.MalformedURLException;
import java.rmi.RemoteException;
public class Servidor {
public static void main(String [] args) {
try {
InterfazRemota intR = new Implementacion();
InterfazRemota stub = (Interfaz) UnicastRemoteObject.exportObject(intR, 0);
Registry registry = LocateRegistry.getRegistry();
registry.rebind("interfaz", stub);
System.out.println("Servidor a la escucha....");
} catch (RemoteException | MalformedURLException ex) {
System.out.println("Error:" + ex);
}
}
La clase java.rmi.UnicastRemoteObject se utiliza para exportar un objeto remoto y obtener un stub que se
comunique con el objeto remoto. La clase java.rmi.registry.LocateRegistry se utiliza para obtener una
referencia al registro de objetos remotos sobre un host dado (incluido localhost), o para crear un registro
que acepte llamadas sobre un puerto específico. En este ejemplo se conectará al registro del host actual. El
método rebind() se encarga de registrar una instancia en el registro de RMI asociándolo a un nombre. Pero
para que este registro se efectúe es necesario ejecutar el comando rmiregistry en una consola del sistema:
Figura 2

La primera vez que ejecutemos rmiregistry solicitará permiso de uso a través del cortafuegos. Para registrar
la instancia de ClaseRemota será necesario decirle a RMI dónde encontrar la clase y la interfaz; para ello
debemos incluir en la variable de entorno CLASSPATH la ruta donde encontrarlas.
Ya se puede ejecutar la clase Servidor. Si todo fue bien se debería mostrar en la consola de salida el mensaje:
Servidor a la escucha....
El método rebind() lanza una tarea asíncrona que está a la escucha de solicitudes cliente, por tanto debemos
asegurarnos de no detener el proceso servidor.
Ahora podemos crear una aplicación cliente. El siguiente servlet utilizará un stub para invocar el método
suma() de la interfaz remota:
package servlet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;

209
import javax.servlet.http.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
@WebServlet(name = "ServletRMI", urlPatterns = {"/clientermi"})
public class ServletRMI extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain");
try (OutputStream out = response.getOutputStream()) {
try {
Registry registry = LocateRegistry.getRegistry("localhost");
InterfazRemota remoto = (InterfazRemota) registry.lookup("interfaz");
out.write("Suma remota = " + remoto.suma(5, 6));
} catch (Exception ex) {
out.write("Error: " + ex);
}
}
}
}
El método lookup() se encarga de realizar una búsqueda remota a través del registro de RMI. Si la conexión
fue exitosa debería mostrarse en el navegador el mensaje:
Suma remota = 11
En este ejemplo, tanto el servidor como el cliente están ejecutándose en la máquina local. Si no es así, tanto el
servidor como el cliente deben indicar el host que corresponda.
1.2. Java Naming Directory Interface (JNDI).
Encontrar un objeto en un sistema de cierta complejidad, como pueden ser las aplicaciones web, puede ser un
problema. ¿Dónde se ubica? ¿Cuáles son sus características? Es necesario un sistema de nombrado que nos
facilite el problema de encontrarlo, sea cual sea su ubicación y la aplicación que lo utiliza.
El sistema de directorios y nombres de Java facilita el mantenimiento de aplicaciones, permitiendo a diversos
programas localizar y recuperar objetos. La tecnología JNDI es un servicio de nombres, semejante a unas
páginas amarillas. Requiere que asignemos nombres que sirvan de referencia de los objetos. Algo semejante
ocurre en el sistema DNS (Domain Name Service) donde se asocia un nombre, como "www.proactiva-
calidad.com", con una dirección IP.
Los sistemas de nombres además implican cierta jerarquía de nombres, como ocurre en la forma de nombrar
una clase en Java, donde hacemos referencia a los paquetes y subpaquetes en los que se encuentra:
paquete.subpaquete.nombre_clase.
Cuando asociamos un nombre con un objeto hablamos de "unión (binding) del objeto". Un conjunto de
uniones nombre-objeto es un contexto. Los contextos tienen también una relación jerárquica, como ocurre
con el espacio de directorios y subdirectorios.
Un sistema de nombrado tiene un espacio de nombres, que no es más que el conjunto de todos los contextos
de dicho sistema.
La tecnología JNDI está dividida en cinco paquetes:
javax.naming
javax.naming.directory
javax.naming.event
javax.naming.ldap
javax.naming.spi
1.2.1. ContextoJNDI del servidor web.
La idea de JNDI implica universalidad y abstracción: es un API genérico, de tal forma que es necesario
integrarlo con otros sistemas de nombres. Para ello la arquitectura JNDI incluye un interfaz de provisión de
servicio (Service Provider Interface, SPI), que sirve de intermediario con otros proveedores de servicios de
nombres/directorios.

210
Figura 3

Los proveedores de servicio (LDAP, DNS, NIS, ...) hacen el trabajo de traducir las llamadas al API JNDI en
llamadas reales sobre un servidor concreto de nombres o directorios.
Los servidores web como Tomcat, Glassfish y otros proporcionan un contexto JNDI en el cual podemos
registrar recursos. Por ejemplo, Tomcat define las siguientes propiedades del sistema relacionadas con JNDI:
Propiedad del sistema Valor
java.naming.factory.url.pkgs org.apache.naming
java.naming.factory.initial org.apache.naming.java.javaURLContextFactory
La clase javaURLContextFactory se encarga de crear el proveedor de servicio de directorio, proporcionando un
contexto JNID para Tomcat.
El contexto raíz de los espacios de nombre en Tomcat es "java:comp". De momento sólo existe este espacio
de nombres raíz. Este espacio de nombres "comp" es una abreviatura de componente, y está reservado para
ubicar recursos de componentes. Sin embargo, la idea es que en el futuro se puedan crear raíces como
"java:org" o "java:user" para otro tipo de recursos.
En el contexto "comp" hay tres subnodos: "env", "Resources" y "UserTransaction". Pero sólo "env" está ligado a
un contexto. Este subnodo está reservado para los enlaces relacionados con el entorno de Tomcat. Por tanto,
normalmente para acceder a cualquier recurso en Tomcat deberemos acceder al espacio de nombres
"java:comp/env" o a uno de sus subespacios.
1.2.2. Configuración del contexto JNDI.
Antes de realizar cualquier operación sobre el servicio de nombres JNDI es necesario adquirir un contexto
inicial, el cual será el punto de partida en el espacio de nombres. Esto se debe a que todos los métodos de los
servicios de nombres y directorios se realizan con respecto a un cierto contexto. En general, para obtener un
contexto inicial, debemos seguir estos pasos.
1) Seleccionar el proveedor de servicio del servicio correspondiente al que se desea acceder.
2) Especificar cualquier configuración que necesite el contexto inicial.
3) Crear un objeto de tipo InitialContext.
Se puede especificar el proveedor de servicios a utilizar para el contexto inicial mediante la creación de un
conjunto de propiedades de entorno (mediante un Hashtable) y añadiendo el nombre de la clase de proveedor
de servicios a la misma. Como Tomcat ya define el proveedor en una propiedad del sistema no es necesario
especificar nada más. Pero a modo de ejemplo haríamos lo siguiente:
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put (Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
Los clientes de los diferentes directorios podrían necesitar diversa información para contactar con el
directorio. Por ejemplo, es posible que haya que especificar en qué máquina se está ejecutando el servidor y la
información para identificar al usuario en el directorio. Esta información se pasa al proveedor de servicios a
través de propiedades de entorno. JNDI especifica algunas propiedades de entorno genéricas que los
proveedores de servicios pueden usar. Por ejemplo, Tomcat especifica el prefijo de los paquetes que se usan
durante la carga del fabricador de contextos:
env.put(Context.URL_PKG_PREFIXES, "org.apache.naming");
Por último se crea el contexto inicial. Para ello se instancia un objeto javax.naming.InitialContext, pasándole
como argumento en su constructor las propiedades de entorno que se han creado anteriormente:
Context contexto = new InitialContext(env);

211
Como se ha especificado ya, para Tomcat no es necesario pasar ningún argumento y es igualmente válido:
Context contexto = new InitialContext();
A través del objeto contexto tendremos acceso al servicio de nombres. Una vez que hayamos acabado de usar
el contexto podemos cerrarlo:
contexto.close();
Para realizar operaciones de directorio, como crear y destruir contextos con atributos, necesitamos usar un
javax.naming.directory.InitialDirContext, usando uno de sus constructores:
DirContext dirCtx = new InitialDirContext(env);
1.2.3. Creando y destruyendo subcontextos y objetos.
La interfaz javax.naming.Context posee métodos para crear y destruir un subcontexto. Para ilustrar con
ejemplos esta sección crearemos la siguiente clase personalizada:
package recurso;
public class Repositorio {
@Override
public String toString() {
return "un repositorio";
}
}
Crearemos nuevos contexto donde guardaremos instancias de esta clase. Para crear un contexto de nombres
se utiliza el método createSubcontext() con el nombre del contexto que queramos crear.
Context ctx = new InitialContext(env);
try {
Context sub = ctx.createSubcontext("recursos");
sub.rebind("repositorio", new Repositorio());
} catch (NameAlreadyBoundException ex) {
// ya existe el subcontexto
}
Para destruir un contexto se utiliza el método destroySubcontext() con el nombre del contexto a destruir:
ctx.destroySubcontext("recursos");
Nota. No se pueden crear ni destruir subcontextos dentro del espacio de nombres "java:comp/env"
puesto que es de solo lectura.
Los métodos bind() y rebind() de la interfaz Context permiten ligar un recurso del contexto con un nombre. El
método rebind() permite asociar un nombre existente con un nuevo recurso. El nombre del contexto puede
incluir rutas, como "recurso/datos". Cada ruta implica un nivel de subcontexto, pero para crear un
subcontexto deben existir sus nodos padres.
Para desligar un objeto del contexto se utiliza el método Context.unbind().
sub.unbind("repositorio");
1.2.4. Búsqueda de objetos en un contexto.
Para buscar un objeto en un servicio de nombres se usa el método Context.lookup(), al cual hay que pasar el
nombre del objeto que queremos recuperar. Este nombre puede incluir una ruta de subcontextos.
Vamos a recuperar el objeto Repositorio guardado en el contexto "recursos":
Context ctx = new InitialContext();
Repositorio rep = (Repositorio) ctx.lookup("recurso/repositorio");
La clase InitialContext ofrece un método estático para instanciar el contexto inicial y recuperar un recurso. La
siguiente instrucción es equivalente al código previo:
Repositorio rep = (Repositorio) InitialContext.doLookup("recurso/repositorio");
En vez de obtener un único objeto de cada vez con Context.lookup(), podemos recuperar una lista con todos
los objetos de un contexto en un única operación. Hay dos métodos que listan un contexto: uno retorna los
enlaces y otro retorna solo los pares nombre-objeto.
El método Context.list() retorna una enumeración de tipo NameClassPair. Cada NameClassPair consiste del
nombre del objeto y su nombre de clase. El siguiente código lista el contenido del contexto "recursos":
Context ctx = new InitialContext();
NamingEnumeration<NameClassPair> list = ctx.list("recurso3");
while (list.hasMore()) {
NameClassPair npc = list.next();
System.out.println(npc.getName() + "=" + npc.getClassName());
}

212
ctx.close();
Si no se ha eliminado previamente el recurso añadido, el resultado debería ser:
repositorio=recurso.Repositorio
El método Context.listBindings() retorna un enumeración de Binding. Binding es una subclase de
NameClassPair. Un objeto Binding contiene además del nombre y nombre de la clase, el objeto mismo. El
siguiente código lista el contenido del contexto "recursos":
Context ctx = new InitialContext();
NamingEnumeration<Binding> list = ctx.listBindings("recurso3");
while (list.hasMore()) {
Binding bind = list.next();
System.out.println(bind.getName() + "=" + bind.getObject());
}
ctx.close();
1.2.5. Creación de objetos de contexto JNDI en ficheros de configuración.
El servidor Tomcat permite usar el fichero descriptor web.xml para añadir automáticamente variables de
entorno al contexto "java:comp/env". Esto se hace mediante el elemento <env-entry>.
En el siguiente ejemplo sea crea una variable de entorno con el nombre JDNI "ejemplos/server":
FICHERO web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<env-entry>
<env-entry-name>ejemplos/server</env-entry-name>
<env-entry-value>http://miservidor.com</env-entry-value>
<env-entry-type>java.lang.String</env-entry-type>
</env-entry>
</web-app>
En un servlet podemos acceder al contexto de estas variables usando InitialContext y lookup():
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "JNDIServlet", urlPatterns = {"/variables"})
public class JNDIServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
try {
Context contexto = new InitialContext();
String servidorHttp = (String) contexto.lookup("java:comp/env/ejemplos/server");
out.print(servidorHttp);
} catch(NamingException e) {
out.print("ERROR: " + e);
}
}
}
}
Como alternativa, también se puede utilizar el fichero /META-INF/context.xml para crear variables de entorno.
La siguiente sintaxis es equivalente a la del fichero web.xml.
FICHERO context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Environment name="ejemplos/server"
type="java.lang.String"
value="http://miservidor.com" />
</Context>

213
2. Pool de conexiones en servidores web
Si buscamos dónde consume mayor tiempo nuestra aplicación en la mayoría de los casos terminamos en el
acceso a base de datos. Una vez establecida una conexión, el tiempo en que tardan en realizarse operaciones
en la base de datos es mínimo.
Figura 4

La técnica de conexión a una base de datos mediante un pool de conexiones permite establecer de antemano
un número de conexiones abiertas. De esta forma, cuando un proceso requiere acceder a la base de datos
solicita una de las conexiones del pool, siendo éstas gestionadas de la manera más eficiente posible.
Database Connection Pool (DBCP), cuya librería se encuentra en RUTA_TOMCAT/lib/tomcat-dbcp.jar, proporciona
las clases necesarias para implementar un pool de conexiones. La interfaz base que define un pool de
conexiones es javax.sql.DataSource.
2.1. Configuración y uso de un pool de conexiones.
Como ejemplo, supongamos una base de datos llamada EjemploDB creada en Java Derby, donde se añade la
siguiente tabla:
CREATE TABLE CITA (
ID INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
FECHA DATE NOT NULL,
MOTIVO VARCHAR(200) NOT NULL
);
Se desea crear una página JSP que permita añadir citas a la tabla CITA. Para ello crearemos un DataSource que
permita obtener conexiones para realizar las operaciones con la base de datos. El DataSource estará disponible
como un recurso en el contexto "java:comp/env" de Tomcat.
El servidor Tomcat permite definir componentes JNDI en su fichero de configuración general <Carpeta raíz
de Tomcat>/conf/context.xml. Los recursos creados en este fichero estarán disponibles para todas las
aplicaciones web ejecutadas por el servidor. En cada aplicación, el fichero /META-INF/context.xml permite
reescribir las configuraciones del fichero general, y los recursos creados en este fichero local serán sólo
accesibles por la aplicación
Los pasos a seguir para crear un pool de conexiones como recurso local son los siguientes:
1) En la aplicación web se crea un recurso de tipo DataSource en el archivo META-INF/context.xml mediante
el elemento <Resource />.
FICHERO context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/WebJNDI">
<Resource name="db/dataSource"
auth="Container"
type="javax.sql.DataSource"
username="APP"
password="app"
driverClassName="org.apache.derby.jdbc.ClientDriver"
url="jdbc:derby://localhost:1527/EjemploDB"
maxActive="8"
maxIdle="4"
maxWait="5000"
/>
</Context>
El significado de los atributos de la etiqueta <Resource> es el siguiente:

214
Atributo Significado
name El nombre JNDI del recurso.
auth Especifica si la aplicación web autentificará el recurso programáticamente, o si es el
contenedor web quien lo hará en nombre de la aplicación. El valor de este atributo
debe ser Application o Container.
Este atributo es necesario si se utiliza el elemento <resource-ref> en el fichero
descriptor, pero es opcional si se utiliza el elemento <resource-env-ref> en su lugar.
type Especifica el tipo de recurso. Debe incluirse el nombre completo de la clase con su
ruta de paquetes.
useranme El nombre de la cuenta de usuario de la base de datos para crear las conexiones.
password La contraseña de acceso a la cuenta de la base de datos.
driverClassName La clase del driver de acceso a la base de datos.
url La cadena de conexión.
maxActive En número máximo de conexiones que tendrá el pool. Un valor cero indica
conexiones sin límite.
maxIdle En número mínimo de conexiones que deben mantenerse en el pool.
maxWait El tiempo de espera, en milisegundos, para obtener una conexión del pool. Si se
excede de este tiempo el código de recuperación de la conexión lanzará una
excepción. Un valor -1 indica un tiempo de espera ilimitado.
2) Agregar el JAR con el driver especifico de la base de datos en la carpeta <Carpeta raíz de Tomcat>/libo
bien de forma local en la carpeta WEB-INF/lib la aplicación web.
3) Opcionalmente podemos registramos el recurso en el descriptor web.xml de la aplicación web.
FICHERO web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>.
<resource-ref>
<description></description>
<res-ref-name>db/dataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
4) Por último, podemos acceder al recurso desde el código de la aplicación web:
Context initCtx = new javax.naming.InitialContext();
Context envCtx = (Context) initCtx.lookup("");
DataSource ds = (DataSource) envCtx.lookup("java:comp/env/db/dataSource");
Connection conn = ds.getConnection();
………………………….
conn.close();
Si queremos usar el recurso en una página JSP también tenemos al opción de utilizar las etiquetas de la librería
SQLJSTL, tal como se expone a continuación.
2.2. La librería SQL JSTL.
JSTL incluye varias etiquetas agrupadas en la librería sql.tld, que permiten acciones para realizar consultas,
actualizaciones y transacciones utilizan un DataSource. La tabla siguiente describe someramente estas
etiquetas:
Etiqueta Sintaxis básica Descripción
setDataSource <sql:setDataSource /> Crea o recupera un DataSource que utilizarán las demás
etiquetas.
query <sql:query var="result"> Ejecuta un comando SQL de selección. El comando se
</sql:query> escribe dentro del cuerpo o en el atributo sql y puede
incluir parámetros. El resultado de la consulta se guarda
en la variable definida en el atributo var.

215
update <sql:update var="result"> Ejecuta un comando SQL de actualización. El comando
</sql:update> se escribe dentro del cuerpo o en el atributo sql y puede
incluir parámetros. El resultado de la operación se
guarda en la variable definida en el atributo var.
transaction <sql:transaction> Estable una transacción para las operaciones realizadas
</sql:transaction> dentro de su cuerpo. Las operaciones se confirman o
anulan en conjunto.
En entorno de NetBeans proporciona el grupo de iconos «Database» en la Paleta cuando se edita una página
JSP. En la siguiente figura se muestra el código que puede generar el icono «DB Query»
Figura 5

2.2.1. Acceder a un pool de conexiones.


JSTL soporta varias formas para hacer que un objeto DataSource esté disponible para las acciones de bases de
datos. Como ya se ha visto, en un contenedor Web con soporte JNDI, se puede definir un DataSource como
un recurso JNDI con un parámetro de contexto en el ficherocontext.xml.
El siguiente código muestra cómo dejar disponible para operaciones posteriores, en el código de un JSP, el
DataSource creado en el ejemplo previo:
<%@taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Ejemplo JNDI</title>
</head>
<body>
<sql:setDataSource dataSource="db/dataSource" />

</body>
</html>
La etiqueta <sql:setDataSource> se encarga de recuperar el recurso JNDI del contexto raíz y dejarlo
disponible para las siguientes operaciones de las etiquetas <sql: >.
El atributo datasource de la etiqueta <sql:setDataSource> también permite pasarle una referencia a un
DataSource ya instanciado. Por ejemplo, se puede dejar disponible un DataSource en un atributo del ámbito de
sesión y recuperarlo con un expresión EL en el atributo datasource.
Como alternativa al uso del elemento <sql:setDataSource> también podemos instanciar un DataSource y
dejarlo disponible para JSTL usando la clase javax.servlet.jsp.jstl.core.Config.
package listener;
import javax.servlet.ServletContextEvent;

216
import javax.servlet.ServletContextListener;
import javax.servlet.jsp.jstl.core.Config;
import org.apache.derby.jdbc.ClientDataSource;
public class AppListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
// Se crea el DataSource
ClientDataSource dataSource = new ClientDataSource();
dataSource.setServerName("localhost");
dataSource.setPortNumber(1527);
dataSource.setDatabaseName("EjemploDb");
dataSource.setUser("APP");
dataSource.setPassword("app");
// Se registra el DataSource con JSTL
Config.set(sce.getServletContext(), Config.SQL_DATA_SOURCE, dataSource);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
Una vez que hayamos registrado la clase AppListener en el fichero descriptor, en cualquier página donde
usemos operaciones de base de datos con JSTL, se tomará por defecto el DataSource registrado.
Por último, la etiqueta <sql:setDataSource> también permite configurar el DataSource pasando toda la
información necesaria en la propia etiqueta:
<sql:setDataSource
url="jdbc:derby://localhost:1527/EjemploDB"
driver="org.apache.derby.jdbc.ClientDriver"
user="APP"
password="app" />
Esta técnica puede venir bien cuando queremos realizar pruebas en las primeras fases de desarrollo, pero no
se recomienda en la fase de producción. No es una buena idea incluir información sensible como la URL de
la base de datos, el nombre de usuario y la contraseña en una página JSP, ya que sería posible para alguien
acceder al código fuente de la página.
2.2.2. Leer datos de la base de datos.
Con un DataSourceya a disposición de JSTL,podemos realizar una consulta a la base de datos. La etiqueta
<sql:query> solicita una conexión al DataSource y crea un PreparedStatement para realizar una consulta.
A continuación podemos ver el código de consulta de la tabla CITA.
<%@taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Ejemplo JNDI</title>
</head>
<body>
<sql:setDataSource dataSource="db/dataSource" />
<sql:query var="resultado">
SELECT * FROM Cita
</sql:query>

</body>
</html>
Primero necesitamos declarar la librería JSTL que contiene las acciones de bases de datos, usando la directiva
taglib de la parte superior de este ejemplo. La acción <sql:query> ejecuta la sentencia SQL SELECT
especificada por el atributo sqlo como el cuerpo del elemento, y guarda los resultados en una variable
nombrada por el atributo var.
El resultado de la consulta a la base de datos se devuelve como un bean del tipo javax.servlet.jsp.jstl.sql.Result,
el cual tiene varias propiedades de sólo lectura:

217
Propiedad Tipo Java Descripción
rows SortedMap[] Un array de objetos SortedMap. Cada mapa representa un registro. Las
claves de los mapas son insensibles a mayúsculas y minúsculas y se
corresponden con el nombre de una columna del resultado. El valor
asociado es el valor de la columna en el registro.
rowsByIndex Object[][] Una matriz, donde cada fila se corresponde con un registro. Cada fila es
un array con los valores en las columnas de cada registro.
columnNames String[] Un array con los nombres de columnas del resultado.
rowCount int El número de registros retornados.
limitedByMaxRows boolean Verdadero si no se han incluido todas las filas debido a que se ha
alcanzado el límite máximo de filas especificado.
La etiqueta <sql:query> permite especificar el primer registro, y el número de registros que debe retornar la
consulta. El siguiente código muestra cómo renderizar el contenido de cada registro de CITA devuelto por la
consulta. En una lista no ordenada se muestra la fecha y el motivo de cada cita:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Ejemplo JNDI</title>
</head>
<body>
<sql:setDataSource dataSource="db/dataSource" />
<sql:query var="resultado"startRow="${param.inicio}" maxRows="${param.noDeFilas}">
SELECT * FROM Cita
</sql:query>
<ul>
<c:forEach var="cita" items="${resultado.rows}" >
<li>${cita.fecha}, ${cita.motivo}</li>
</c:forEach>
</ul>
</body>
</html>
El atributo startRowdetermina el primer registro que se mostrará, y para ese ejemplo toma su valor de un
parámetro de solicitud. El atributo maxRows determina el número máximo de registros que deben ser
retornados.
Si encapsulamos todos los accesos a una base de datos en clases Java en lugar de usar las acciones de JSTL,
disponemos de una clase que nos puede ser útil: javax.servlet.jsp.jstl.sql.ResultSupport, con estos dos métodos:
public static Result toResult(java.sql.ResultSet rs);
public static Result toResult(java.sql.ResultSet rs, int maxRows);
Podemos usar esta clase para convertir un objeto java.sql.ResultSet estándar JDBC en un objeto Resultde
JSTL antes de reenviarlo a la página JSP para mostrarlo. Las acciones JSTL pueden acceder fácilmente a los
datos de un objeto Result, como vimos anteriormente. Otra aproximación, todavía mejor, es pasar el resultado
de la consulta a la página JSP como una estructura de datos personalizada, como una List de beans que
contengan los datos de cada fila, pero el objeto Result aún es un buen candidato para prototipos y pequeñas
aplicaciones.
2.2.3. Realizar actualizaciones en la base de datos.
La etiqueta <sql:update> permite realizar operaciones de actualización, incluyendo inserciones, borrados y
modificaciones. Al igual que en las consultas, esta etiqueta utiliza un PreparedStatement para realizar las
operaciones y por tanto admite el uso de parámetros.
En el siguiente código se muestra como dar de alta a una nueva cita a partir de los datos posteados desde un
formulario:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql"%>

218
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Ejemplo JNDI</title>
</head>
<body>
<sql:setDataSource dataSource="db/dataSource" />
<c:catch var="error">
<fmt:parseDate var="fecha" value="${param.fecha}" pattern="yyyy-MM-dd" />
</c:catch>
<c:if test="${error != null}">
<jsp:useBean id="fecha" class="java.util.Date" />
</c:if>
<sql:update>
INSERT INTO Cita (fecha, motivo) VALUES (?, ?)
<sql:dateParam value="${fecha}" type="date" />
<sql:param value="${param.motivo}" />
</sql:update>
</body>
</html>
Antes de insertar la cita, este ejemplo ilustra cómo usar las acciones JSTL para realizar validación sobre la
fecha. Antes de que la fecha se pueda insertar en la base de datos, debemos convertirla a su forma nativa Java.
Esto es lo que hace la acción <fmt:parseDate>. El atributo value contiene una expresión EL que obtiene el
valor del parámetro de solicitud fecha. La acción trata de interpretarlo como una fecha escrita en el formato
especificado por el atributo pattern (un año de cuatro dígitos, seguido por dos dígitos del mes y dos dígitos
del día, separados por guiones). Si tiene éxito, almacena la fecha en su forma nativa con el nombre
especificado por el atributo var.
La acción <c:catch> tiene en cuenta los strings inválidos. Si el valor del parámetro no puede ser interpretado
como una fecha, el <fmt:parseDate> lanza una excepción, que la acción <c:catch> captura y graba en la
variable especificada. Cuando esto sucede, la condición de comprobación de la acción <c:if> se evalúa a true,
por lo que fecha es creada por la acción <jsp:useBean> anidada.
Para insertar la fila, usamos la acción <sql:update>. Como la acción de consulta, la sentencia SQL se puede
especificar como el cuerpo del elemento o mediante un atributo sql. La acción <sql:update> se puede usar
para ejecutar sentencias INSERT INTO, UPDATE yDELETE FROM, así como sentencias para crear o eliminar
objetos en la base de datos, como CREATE TABLE y DROP TABLE. El número de filas afectado por la sentencia
puede capturarse de forma opcional en una variable nombrada por el atributo var.
El comando especificado en la etiqueta <sql:update> se utiliza como argumento para el objeto
PreparedStatment, por ello se especifican parámetros con los símbolos ?. Para especificar el valor de
parámetros de tipo Date se utiliza la etiqueta <sql:dateParam>, para los demás tipos de utiliza <sql:param>.
En este ejemplo se usan la parte de la fecha, por eso se ha asignado el atributo opcional type a date. Otros
valores válidos son time y timestamp (por defecto), para las columnas que sólo toman la hora o la fecha y la
hora.
2.2.4. Transacciones.
JSTL simplifica el realizar varias operaciones en el contexto de una misma transacción. Basta con encapsular
todas las operaciones dentro del cuerpo del elemento <sql:transaction />. En el siguiente código se muestra
cómo realizar una modificación y un borrado en una misma transacción:
<sql:transaction>
<sql:update>
UPDATE CITA SET FECHA=FECHA+1
</sql:update>
<sql:update>
DELETE FROM CITA WHERE ID = 1
</sql:update>
</sql:transaction>
Si se lanza una excepción desde una de las operaciones se aplicará un rollback automático, si no se producirá
un commit automático.
219
3. Seguridad en aplicaciones Web
En aquellas aplicaciones web donde queramos aplicar seguridad, por lo general el usuario que acceda a
nuestras páginas deberá identificarse.Normalmente lo hará presentando su nombre de usuario y contraseña. A
este proceso se le denomina autenticación o autentificación. Una vez autenticado, debe determinarse si esa
identidad puede tener acceso a un recurso específico. Este último proceso se conoce como autorización.
Otros conceptos asociados con la seguridad son los siguientes:
• La integridad de datos es el proceso que garantiza que los datos que son enviados llegan a su destino sin
modificaciones. Esto se garantiza enviando un código de hash o signatura acompañando a los datos. El
receptor verifica que los datos y sus códigos de hash coincidan.
• La confidencialidad es el proceso que garantiza que datos sensibles no podrán ser interceptados por
otros destinatarios diferentes de los previstos. Se suele garantizar encriptando la información, de forma que
sólo los destinatarios autorizados puedan desencriptarla.
• Auditar es el proceso de registrar todos aquellos eventos ocurridos en el sistema que sean relevantes, de
forma que los usuarios puedan tomar las acciones correctoras adecuadas. Normalmente se audita generando
archivos de log desde las propias aplicaciones.
• El código malicioso es aquel que puede causar efectos no deseados en el sistema. Incluimos en él a los
virus, gusanos y troyanos. En aquellas aplicaciones Web donde permitimos el envío de código desde los
usuarios, deberemos verificar que no incluya código malicioso.
Los servidores web, como Tomcat y Glassfish, incluyen mecanismos integrados para autentificar y autorizar a
los usuarios. En este capítulo veremos como configurar estos mecanismos en Tomcat.
3.1. Proceso de autenticación.
Autenticar es el proceso que permite identificar a un usuario, y que permite determinar si es un usuario
reconocido. La autenticación consta de dos pasos: solicitar información de autentificación, y utilizar esta
información para validar al usuario.
Figura 6

Los tipos de autenticación suelen estar basados en la validación de un nombre de usuario (login ) y su
correspondiente clave secreta o contraseña (password ). Estos dos datos se denominan credenciales del
usuario.
Los programas de servidor Web, como Tomcat y Glassfish, aplican un mecanismo de seguridad bajo
demanda. Esto significa que el servidor no pedirá identificarse a un usuario hasta que intente acceder a un
recurso protegido. En ese momento se solicitarán las credenciales de usuario.
El proceso general de autentificación es el siguiente:
1) Un usuario solicita una página protegida.
2) El servidor mira si ya hay un usuario autentificado y si es así pasa al paso 5. Si no es así pasa al paso 3.
3) Se solicitan credenciales al usuario. Estas credenciales son validadas contra un almacén de datos.
4) Si las credenciales son válidas el usuario queda establecido como el usuario autentificado, si no son
válidas se vuelven a solicitar credenciales.
5) El servidor mira si el usuario autentificado tiene permisos para acceder a la página protegida. Si es así se
la envía, y si no es así devuelve el código de error 403.
Los servidores web incluyen varios mecanismos para solicitar las credenciales de usuario y varios mecanismos
para validar las credenciales contra un almacén de datos. Estos mecanismos se describen a continuación.

220
3.2. Tipos de autenticación HTTP.
Primero describiremos los mecanismos de solicitud de credenciales que ofrecen los servidores Web. Estos
mecanismos se basan en unos estándares que cada servidor web se encarga de implementar. El mecanismo de
solicitud de credenciales se ejecuta cuando un navegador cliente intenta acceder a un recurso protegido, por
ello se denomina autenticación bajo demanda. En ese momento el servidor Web solicita las credenciales del
usuario.
En este capítulo veremos cómo el servidor Tomcat implementa estos mecanismos.
3.1.1. Autenticación HTTP Basic (BASIC).
Cuando se aplica autentificación Basic ocurren los siguientes pasos:
1. El navegador cliente solicita un recurso protegido y envía una cabecera de solicitud HTTP normal.
GET /servlet/ServletVentas HTTP/1.1
Host: ejemplo.com
2. El servidor recibe la solicitud y detecta que el recurso está protegido. En vez de responder con el recurso,
envía un código de estado 401 Unauthorized, e incluye una cabecera que le indica al navegador que es
necesaria la autenticación básica para acceder al recurso. La cabecera especifica el dominio de seguridad
(realm) en el cual la autenticación será válida. Por ejemplo:
HTTP/1.0 401 Unauthorized
Server: Tomcat/4.0.1
WWW-Authenticate: Basic realm="admin"
Content-Length=311
Content-Type=text/html
<html>
<head>
<title>Error</title>
</head>
<body>
401 Unauthorised
</body>
</html>
En la tercera línea se especifica el tipo de autentificación Basic y el dominio de seguridad admin.Este
dominio es el nombre asignado por el servidor para identificar el espacio protegido.
3. El navegador abre un cuadro de diálogo para solicitar el nombre de usuario y contraseña.
4. El navegador reenvía la petición con las credenciales introducidas por el usuario:
GET /servlet/ServletVentas HTTP/1.0
Authorization: Basic am9objpqamo=
Las credenciales son codificadas en Base64.
5. El servidor valida el usuario y contraseña. Si se autentifica se sirve el recurso, si no envía otra vez el
mensaje 401 Unauthorized.
6. El navegador muestra el recurso (o muestra otra vez el cuadro de diálogo de autenticación).
Este método de autenticación es sencillo y está implementado por todos los navegadores. Por la contra, el
cuadro de diálogo de autenticación no puede personalizarse, y no es seguro, puesto que no encripta los
valores de usurario y contraseña.
Para aplicar este mecanismo en Tomcat debemos editar el fichero descriptor web.xml e incluir las siguientes
etiquetas:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>admin</realm-name>
</login-config>
</web-app>
Para configurar el mecanismo de solicitud de credenciales mediante NetBeans se puede utilizar el editor del
fichero descriptor. En la sección «Seguridad» hay que desplegar el nodo «Configuración de Login» y
seleccionar el modo elegido, en este caso Basic.

221
Figura 7

En esta misma categoría, en el nodo «Restricciones de seguridad», podremos establecer más tarde una
restricción de seguridad para proteger recursos. Hay que recordar que el mecanismo de solicitud de
credenciales no se activará hasta que se solicite un recurso protegido.
Como resumen, la autenticación Basic tiene las siguientes características:
• Existe desde el protocolo HTT 1.0.
• Es muy simple y poco seguro.
• Las credenciales se codifican en base de 64 bits, pero no están cifradas.
• Puede producir un efecto no deseado. Si el usuario es autentificado sus credenciales quedan establecidas
en el sistema operativo subyacente y pueden ser enviadas automáticamente la próxima vez que se soliciten
credenciales.
3.1.2. Autenticación HTTP Digest (DIGEST).
Este mecanismo de solicitud de credenciales está basado en el Basic, pero con el añadido de que las
credenciales son cifradas. Por tanto ofrece mayor protección sobre la información de credenciales, pero
tienen las desventaja es que no todos los navegadores soportan este método.
Para aplicar este mecanismo en Tomcat debemos editar el fichero descriptor web.xml e incluir las siguientes
etiquetas:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<login-config>
<auth-method>DIGEST</auth-method>
<realm-name>admin</realm-name>
</login-config>
</web-app>
Como resumen, la autenticación Digest tiene las siguientes características:
• Se incorporó con el protocolo HTT 1.1.
• Es algo más compleja internamente que la autentificación Basic.
• Las credenciales se codifican en base de 64 bits y se cifran.
• No está soportada por todos los navegadores y servidores.
3.1.3. Autenticación HTTPS Client (CLIENT-CERT).
HTTPS es HTTP sobre el protocolo SSL (Secure Socket Layer). El protocolo SSL proporciona privacidad sobre
los datos que transmite. En este mecanismo la autenticación se realiza cuando la conexión SSL se establece
entre el navegador y el servidor. Los datos son transmitidos en forma cifrada usando clave criptográfica
pública.
Sus ventajas son que es el mecanismo más seguro y lo soportan todos los navegadores. Sus desventajas son
que requiere que el usuario tenga un certificado, y es costoso de implementar y mantener.
222
• Requiere el uso del protocolo HTTPS en vez de HTTP.
• Se basa en el mecanismo de Clave Pública (PKI) SSL (o TLS).
• Requiere certificados generados por una autoridad certificadora (CEA).
3.1.4. Autenticación basada en formulario (FORM).
Este método de autenticación es similar al mecanismo Basic, excepto que usa una página web con un
formulario HTML personalizado para obtener el usuario y la contraseña.
Para Tomcat, el desarrollador debe crear el formulario HTMLasignando en el atributo action el valor
j_security_check y debe tener dos campos de texto con los nombres: j_username y j_password.
El código para crear una página HTML que contenga este tipo de formulario puede ser como sigue:
<%-- FICHERO login.jsp -->
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Solicitud de credenciales</title>
</head>
<body>
<form action="j_security_check">
Nombre de usuario <input type="text" name="j_username"><br>
Contraseña<input type="password" name="j_password"><br>
<input type="submit" value="Autentifica">
</form>
</body>
</html>
Necesariamente el valor del action del formulario debe ser j_security_check. El nombre del cuadro que solicita
el login de usuario debe ser j_username, y el nombre del cuadro que solicita la contraseña debe ser j_password.
Tienen la ventaja de que es sencillo de implementar, lo soportan todos los navegadores y puede
personalizarse. Sus desventajas son que no es seguro porque no se cifran el usuario y la contraseña, y sólo
puede mantenerse usando cookies o HTTPS.
Para aplicar este mecanismo en Tomcat debemos editar el fichero descriptor web.xml e incluir las siguientes
etiquetas:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<login-config>
<auth-method>FORM</auth-method>
<realm-name>admin</realm-name>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login.jsp</form-error-page>
</form-login-config>
</login-config>
</web-app>

223
Figura 8

La configuración de este método permite establecer una página de error. Esta página será invocada si las
credenciales del usuario no son validadas. Para este ejemplo se ha puesto la misma página de login, de esta
forma damos al usuario la opción de volver a introducir sus credenciales por si se he equivocado. Incluso
podemos aprovechar este comportamiento para establecer el número de intentos posibles.
Por ejemplo, vamos a establecer en 3 el número de intentos para introducir credenciales:
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Solicitud de credenciales</title>
</head>
<body>
<c:choose>
<c:when test="${sessionScope.intentos_login + 0 < 3}">
<c:set var="intentos_login" value="${sessionScope.intentos_login + 1}" scope="session" />
<form action="j_security_check" method="POST">
Nombre de usuario <input type="text" name="j_username"><br>
Contraseña <input type="password" name="j_password"><br>
<input type="submit" value="Autentifica">
</form>
</c:when>
<c:otherwise>
<c:remove var="intentos_login" scope="session" />
<h1>Ha agotado sus intentos de autentificarse</h1>
</c:otherwise>
</c:choose>
</body>
</html>
En este ejemplo, cada vez que se invoca el formulario de login se evalúa una variable de sesión llamada
intentos_login. La primera vez no existirá y por tanto se evaluará a cero. En cada intento se incrementa esta
variable. Si el intento es menor que 3 se muestra el formulario de login, sino se muestra un mensaje y se
elimina la variable de sesión para reiniciar todo.

224
3.3. Mecanismos de validación de credenciales.
Tomcat define un modelo de seguridad basado en roles. Bajo este modelo un usuario siempre estará asignado
al menos a un rol y los permisos se le otorgarán al rol en lugar de al usuario.Esto da flexibilidad al modelo
porque podemos tener muchos usuarios desempeñando el mismo rol, como también es posible que un
usuario desempeñe varios roles, teniendo como permisos la suma de todos los roles.
Un dominio de seguridad es un mecanismo del Tomcat que se usa para proteger los recursos de la
aplicación Web. Esto nos da la capacidad de proteger un recurso con una restricción de seguridad
predeterminada y luego definir qué roles pueden acceder al recurso protegido. (La interfaz del Tomcat que
hace posible esta funcionalidad es org.apache.catalina.Realm, que proporciona un mecanismo a través del cual
la lista de usuarios, contraseñas y roles pueden ser integrados al Tomcat.)
Tomcat implementa varios tipos de dominios de seguridad, según el origen de la información de
autenticación:
• MemoryRealm: la información se guarda en un documento XML (normalmente conf/tomcat-users.xml).
• JDBCRealm: la información se ubica en una base de datos relacional accesible mediante un driver JDBC.
• DataSourceRealm: la información también se ubica en una base de datos, pero accesible mediante un
DataSource JNDI JDBC.
• JNDIRealm: la información se guarda en un servidor de directorio LDAP, vía un proveedor JNDI.
• JAASRealm: se usa el servicio Java Authentication & Authorization Service (JAAS), permitiendo personalizar
al máximo el mecanismo de autentificación.
En todos los casos la configuración del mecanismo de seguridad se realiza en el archivo web.xml de la
aplicación Web. La diferencia está en el origen de dónde se obtienen los usuarios, contraseñas y roles.
Las consideraciones a tener en cuenta con estos mecanismos son las siguientes:
• Si el Realm está mal configurado en el servidor y no se consigue autentificar a los usuarios, se mostrará
una y otra vez la ventana de login. En cambio, si el usuario es autentificado pero no tiene acceso a la
aplicación (no está en un grupo que tenga acceso) se mostrará una página de Error 403 indicando que el
usuario no tiene permisos para acceder al recurso que solicita.
• La autentificación por este método tiene la peculiaridad de que una vez que el usuario es autentificado, las
credenciales son utilizadas por el navegador hasta que el mismo se cierra independientemente de que
finalice la sesión del usuario.
• La autentificación integrada con Windows, llamada también NTLM, que permite autentificar a los
usuarios con sus credenciales de Windows sin necesidad de que introduzcan usuario y contraseña, no se
implementa mediante Realms de Tomcat.
A continuación analizaremos alguno de estos mecanismos.
3.2.1. Mecanismo de autentificación «MemoryRealm».
En este primer mecanismo, MemoryReal, los usuarios autorizados para ingresar a nuestra aplicación se
guardarán en el archivo <RUTA_TOMCAT>/conf/tomcat-users.xml. Por ejemplo, en este archivo podemos
escribir algo como esto bajo el nodo raíz:
<tomcat-users>
<role name="rol1" />
<role name="rol2" />
<user name="usuario1" password="usuario1" roles="rol1" />
<user name="usuario2" password="usuario2" roles="rol2" />
<user name="ambos" password="usuario" roles="rol1,rol2" />
</tomcat-users>
En este ejemplo se declaran tres usuarios: usuario1, usuario2 y ambos. Cada uno con sus respectivas
contraseñas y los roles que juega cada uno. En el caso del usuario ambos pertenece a los roles usuario1 y rol1.
Para que este primer mecanismo funcione deberemos incluir la declaración del Realm el archivo
<RUTATOMCAT>/conf/server.xml, o bien en el archivo /META-INF/context.xml de la aplicación web:
<Context>
<Realm className="org.apache.catalina.realm.MemoryRealm" />
</Context>
3.2.2. Mecanismo de autentificación «JDBCRealm».
Con el mecanismo JDBCRealm podemos validar el usuario contra una base de datos en vez del archivo tomcat-
users.xml. Para ello debemos crear las siguientes tablas en una base de datos:
• Una tabla de usuarios para almacenar los usuarios, con su nombre y contraseña. Por ejemplo:

225
CREATE TABLE USUARIO (
nombre_usuario VARCHAR(15) NOT NULL,
password_usuario VARCHAR(15) NOT NULL
);
• (Opcional) Una tabla de roles que contendrá el nombre de los mismos. Por ejemplo:
CREATE TABLE ROL (
nombre_rol VARCHAR(15) NOT NULL
);
• Una tabla que relaciona los usuarios con los roles. Por ejemplo:
CREATE TABLE USUARIO_ROL (
nombre_usuario VARCHAR(15) NOT NULL,
nombre_rol VARCHAR(15) NOT NULL
);
En estas tablas se han creado con el esquema mínimo que requiere el mecanismo. Se puede cambiar el
nombre de las tablas y columnas, y a mayores se pueden incluir más columnas y restricciones de claves
primarias y foráneas. Sólo es necesario que la columna con el nombre de usuario se denomine igual en las
tablas USUARIO y USUARIO_ROL
Ahora, en el ficheroconf/server.xmlde Tomcat o en el fichero META-INF/context.xml de la aplicación web,
debemos incluir el siguiente elemento <Realm />:
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/WebJNDI">
<Realm className="org.apache.catalina.realm.JDBCRealm"
driverName="org.apache.derby.jdbc.ClientDriver"
connectionURL="jdbc:derby://localhost:1527/CREDENCIALES"
connectionName="APP"
connectionPassword="app"
userTable="USUARIO"
userNameCol="NOMBRE_USUARIO"
userCredCol="PASSWORD_USUARIO"
userRoleTable="USUARIO_ROL"
roleNameCol="NOMBRE_ROL" />
</Context>
El significado de cada atributo del elemento Realm se explica a continuación:
Atributo Significado
className La clase que va a implementar el mecanismo.
driverName La clase del driver JDBC.
connectionURL La cadena de conexión JDBC.
connectionName La cuenta de usuario de la base de datos.
connectionPassword La contraseña para acceder a la cuenta de usuario de la base de datos.
userTable El nombre de la tabla que contiene las credenciales de usuario.
userRoleTable El nombre de la tabla que relaciona un usuario con sus roles.
userNameCol La columna con el nombre de usuario de las credenciales, tanto en la tabla
"userTable" como en la tabla "userRoleTable".
userCredCol La columna con la contraseña de las credenciales
roleNameCol La columna con el nombre de rol en la tabla "userRoleTable".
3.2.3. Mecanismo de autentificación «DataSourceRealm».
El mecanismo DataSourceRealm es análogo al JDBCRealm. Sólo cambia la forma de especificar la base de
datos, es este caso mediante un recurso JNDI de tipo DataSource.
Previamente debemos configurar el recurso JNDI (tal como se ha visto al crear un pool de conexiones) en el
archivo context.xml. En el siguiente ejemplo se identificará con el nombre "jdbc/credenciales":
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/WebJNDI">
<Resource name="jdbc/credenciales"
auth="Container"
type="javax.sql.DataSource"
username="APP"
password="app"
driverClassName="org.apache.derby.jdbc.ClientDriver"

226
url="jdbc:derby://localhost:1527/CREDENCIALES"
/>
<Realm className="org.apache.catalina.realm.DataSourceRealm"
dataSourceName="jdbc/credenciales"
localDataSource="true"
userTable="USUARIO"
userNameCol="NOMBRE_USUARIO"
userCredCol="PASSWORD_USUARIO"
userRoleTable="USUARIO_ROL"
roleNameCol="NOMBRE_ROL" />
En el atributo dataSourceName se asigna el nombre JNDI del recurso DataSource. Si se referencia un recurso
JNDI declarado en el archivo server.xml debemos asignar el atributo localDataSource a valor "false" (valor por
defecto), mientras que si lo declaramos en el archivo context.xml debemos asignar el valor "true".
3.4. Mecanismo de autorización.
Una vez validado y autenticado un usuario, el servidor debe verificar si tiene autorización para acceder al
recurso solicitado.
Por defecto, todos los recursos son accesibles a todo el mundo. Para restringir el acceso debemos identificar
tres cosas (esto es denominado "seguridad declarativa"):
• La colección de recursos Web. Identifica los recursos que deben ser protegidos del acceso público. Un
usuario debe tener la apropiada autorización para acceder a un recurso identificado en esta colección.
•Restricciones de autorización. Identifica los roles que un usuario puede tener asignado para tener
permiso de acceso a los recursos. Es más eficiente asignar permisos por roles que por usuario.
•Restricciones de datos de usuario. Especifica el medio por el que los datos deben ser transmitidos entre
el origen y el destino. El medio elegido va a determinar la integridad y confidencialidad de los datos.
Estos tres conceptos se configuran en el archivo web.xml usando el elemento <security-constraint>
directamente bajo <web-app>. Este elemento incluye los siguientes subelementos:
•<display-name>, es opcional, y especifica un nombre para identificar la restricción.
•<web-resource-collection>, especifica la colección de recursos que queremos proteger.
•<auth-constraint>, especifica los roles de aquellos usuarios que tendrán acceso a los recursos especificados
en la colección de recursos a través de los métodos http especificados previamente.
•<user-data-constraint>, especifica cómo se deben comunicar los datos.
Pero para aplicar autorización basada en roles, el mecanismo necesita que dichos roles queden configurados
también en el fichero descriptor web.xml.
3.4.1. Declaración de roles permitidos.
Antes de aplicar autorización basada en roles, debemos configurar los nombres de roles permitidos en el
propio fichero web.xml. Y esto es independiente del mecanismo de validación de credenciales utilizado.
Se declara cada rol con un elemento <security-role> bajo en nodo raíz <web-app>. En el siguiente ejemplo se
declaran el rol1 y rol2:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
…………………………..
<security-role>
<description/>
<role-name>rol1</role-name>
</security-role>
<security-role>
<description/>
<role-name>rol2</role-name>
</security-role>
</web-app>
Se pueden configurar estos roles usando el editor de NetBeans:

227
Figura 9

3.4.2. La colección de recursos a proteger.


El sub-elemento <web-resource-collection>de <security-constraint>especifica la colección de recursos que
queremos proteger. Se puede crear una colección que incluya un único recurso o varios recursos.
Por ejemplo, si en nuestro sitio web queremos proteger una página llamada "informes.jsp" ubicada en la
carpeta raíz, podemos configurar lo siguiente en el editor de NetBeans:
Figura 10

228
Tras aceptar se generará el siguiente código de marcado:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<security-constraint>
<display-name>Informes protegidos</display-name>
<web-resource-collection>
<web-resource-name>Informe protegido</web-resource-name>
<description/>
<url-pattern>/informe.jsp</url-pattern>
</web-resource-collection>
<!-- más configuraciones -->
</security-constraint>
……………………
</web-app>
Pero se puede añadir más de una URL a la misma colección de recursos. Por ejemplo, también protegeremos
todas las páginas de la carpeta /protegido:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<security-constraint>
<display-name>Informes protegidos</display-name>
<web-resource-collection>
<web-resource-name>Informe protegido</web-resource-name>
<description/>
<url-pattern>/informe.jsp</url-pattern>
<url-pattern>/protegido/*</url-pattern>
</web-resource-collection>
<!-- más configuraciones -->
</security-constraint>
……………………
</web-app>
Al juntar las dos URLs en una misma colección, a ambas les afectará las mismas configuraciones de acceso
por roles y de configuraciones de comunicación.
En este ejemplo se ha especificado «All HTTP Methods», que es la opción por defecto y por tanto no genera
etiquetas en el marcado. Esta opción indica que los recursos de la colección estarán protegidos a través de
todos los métodos HTTP. La subetiqueta <http-method>permite especificar el método HTTP que requerirá
autorización. Por ejemplo, podemos forzar autorización sólo a través de los métodos POST yPUT:
<web-resource-collection>
<web-resource-name>Informe protegido</web-resource-name>
<description/>
<url-pattern>/informe.jsp</url-pattern>
<url-pattern>/protegido/*</url-pattern>
<http-method>PUT</http-method>
<http-method>POST</http-method>
</web-resource-collection>
Si accedemos a estas páginas protegidas por cualquier otro método que no sea PUT y POST tendremos acceso.
Las reglas que se aplican para los elementos <http-method>son las siguientes:
• Si se especifica simplemente el elemento <http-method /> (sin cuerpo), entonces los recursos permitirán
el acceso mediante cualquier método HTTP a cualquier usuario.
• Si se especifica un método HTTP, entonces los recursos sólo permitirán el acceso mediante ese método
a los usuarios que tengan el rol especificado en la restricción.
• Si no se especifica ningún elemento <http-method> entonces se controlan por defecto todos los
métodos HTTP.
3.4.3. Los roles permitidos.
La subetiqueta <auth-constraint>de <security-constraint>especifica los roles que tendrán acceso a los recursos
especificados en la colección de recursos a través de los métodos HTTP especificados previamente.
Por ejemplo, dejaremos acceso al rol rol1 a los recursos protegidos:
<security-constraint>
<display-name>Informes protegidos</display-name>

229
<web-resource-collection>
<web-resource-name>Informe protegido</web-resource-name>
<description/>
<url-pattern>/informe.jsp</url-pattern>
<url-pattern>/protegido/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<description/>
<role-name>rol1</role-name>
</auth-constraint>
</security-constraint>
En <role-name> se puede usar * para indicar todos los roles definidos por los elementos <security-role>.
Las reglas que se aplican para el elemento <auth-constraint> son las siguientes:
• Dentro del elemento <security-constraint>, el elemento <auth-constraint> es opcional.
• Si existe un <auth-constraint>, el contenedor web debe realizar una autentificación para las URLs
asociadas.
• Si no existe un <auth-constraint>, el contenedor web debe permite acceso a los usuarios no autentificados
para todas las URLs asociadas.
Las reglas que se aplican para el elemento <role-name> son las siguientes:
• Dentro de un elemento <auth-constraint>, los elementos <role-name> son opcionales.
• Si existen elementos <role-name>, éstos le dicen al Contenedor qué roles son permitidos.
• Si un elemento <auth-constraint> no tiene elementos <role-name>, entonces ningún usuario será
permitido.
• Si se añade <role-name>*</role-name>, entonces todos los usuarios serán permitidos.
• Los nombres de roles son sensibles a mayúsculas y minúsculas.
3.4.4. Seguridad en la comunicación de los datos.
El subelemento<user-data-constraint>de <security-constraint>especifica cómo se deben comunicar los datos
de autorización. Por ejemplo:
<security-constraint>
…………………………………
<user-data-constraint>
<description>Requiere que la transmisión de datos sea integral</description>
<transport-guarantee>INTEGRAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
El elemento <transport-guarantee> puede tomar los siguientes valores en su cuerpo:
NONE (el valor por defecto), no garantiza ninguna protección de los datos.
INTEGRAL, garantiza que los datos no serán cambiados por el camino.
CONFIDENTIAL, garantiza que los datos no sean vistos por nadie por el camino.
Se usa normalmente HTTP para NONE y HTTPS para los otros.
3.5. Seguridad Web mediante programación.
El mecanismo de autentificación bajo demando a veces es insatisfactorio. Una vez autentificado un usuario ya
no se solicitan nuevas credenciales si falla su acceso a una página. Puede que se desee dejar autentificado a un
usuario antes de acceder a páginas protegidas y que demos la posibilidad de anular al usuario actual para que
se soliciten nuevas credenciales.
A veces no basta sólo con dar autorización de acceso a un recurso a usuarios. Puede que queramos que el
recurso ofrezca una respuesta diferenciada según el rol del usuario que accede a un recurso.
3.5.1. Gestión de validación y autorización.
La interfaz HttpServletRequest provee varios métodos para evaluar al usuario autentificado y que permiten
invocar por código los mecanismos establecidos de autentificación y autorización. Estos métodos son:
•String getRemoteUser(), retorna el nombre del usuario que fue autentificado, o null si no hay usuario
autentificado.
•Principal getUserPrincipal(), retorna un objeto java.security.Principal que contiene información sobre el
usuario autentificado, o null si no fue autentificado.
•boolean isUserInRole(String rolename), indica si el usuario autentificado está incluido en un rol determinado.
Si el usuario no fue autentificado siempre retorna false.

230
•boolean authenticate(HttpServletResponse), invoca el mecanismo configurado en el contenedor para
autentificar a un usuario. Esto provoca que se soliciten las credenciales como primer paso de
autentificación. Retorna false si no se ha podido completar la autentificación.
•void login(String username, String password), valida las credenciales de un usuario usando el dominio de
validación establecido en el fichero de configuración de contexto. Si las credenciales son válidas queda
establecido como usuario autentificado.
•void logout(), finaliza la autentificación actual estableciendo un usuario anónimo. Esto provoca que los
métodos getUserPrincipal(), getRemoteUser() y getAuthType() retornen null.
Para ilustrar el uso de estos métodos crearemos dos páginas. En una página inicial, index.jsp, si no hay usuario
autentificado la página mostrará un formulario para solicitar credenciales. La información posteada por el
formulario se utilizará para dejar autentificado al usuario. Si un usuario está autentificado se mostrará un
enlace para volver a un usuario anónimo. Tanto el formulario como el enlace de la página inicial invocarán
una página dummy.jsp que se encargará de registrar o finalizar el registro del usuario actual.
<%-- PÁGINA index.jsp --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Ejemplo autentificación</title>
</head>
<body>
<div>
<c:if test='<%= request.getRemoteUser() == null%>'>
<form action="dummy.jsp?logon" method="POST">
Nombre <input type="text" name="login" value="" />
Contraseña <input type="password" name="password" value="" />
<input type="submit" value="registrar" />
</form>
</c:if>
<c:if test='<%= request.getRemoteUser() != null%>'>
Usuario actual: <%= request.getRemoteUser()%>
<a href="dummy.jsp?logout">finalizar registro</a><br />
</c:if>
</div>
<h3>Página inicial</h3>
</body>
</html>
La primera vez la página index.jsp se verá así:
Figura 11

Cuando un usuario introduzca su nombre y contraseña y pulse el botón de registro se invocará la página
dummy.jsp?logon.
<%-- PÁGINA dummy.jsp --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>

231
<html>
<body>
<c:if test='${param.logon != null}'>
<jsp:scriptlet>
request.login(request.getParameter("login"), request.getParameter("password"));
</jsp:scriptlet>
</c:if>
<c:if test='${param.logout != null}'>
<jsp:scriptlet>
request.logout();
</jsp:scriptlet>
</c:if>
<c:redirect url="/index.jsp" />
</body>
</html>
La página dummy.jsp evalúa la existencia del parámetro logon y valida las credenciales posteadas usando el
método login(). Si las credenciales no son validadas se generará el código de error 500. Aunque en este
ejemplo no se hace, podemos capturar el error mediante una página de error para redireccionar al usuario
hacia la página inicial. Cuando el usuario sea validado se redirecciona hacia la página inicial, que ahora se verá
así:
Figura 12

Para finalizar el registro del usuario actual, la página dummy.jsp invoca simplemente el método logon().
3.5.2. Gestión de usuarios y roles.
Ampliaremos el ejemplo previo para mostrar cómo gestionar los usuarios y sus roles. La interfaz
HttpServletRequest dispone de los métodos getRemoteUser(), getUserPrincipal() y isUserInRole() para ello. Pero
también podemos acceder a estos métodos en las páginas JSP mediante expresiones EL. Por ejemplo, las
siguientes expresiones permiten recuperar el nombre del usuario autentificado:
${pageContext.request.remoteUser}
${pageContext.request.userPrincipal.name}
Y podemos evaluar si el usuario actual pertenece a un rol determinado con:
${pageContext.request.isUserInRole("rol")}
Para ilustrar el uso de estas funciones vamos a diseñar la siguiente aplicación web:

232
Figura 13

Se definirán dos roles: rol1 y rol2. Y tres usuarios: usuario1 pertenecerá al rol1, usuario2 pertenecerá al rol2, y
ambos pertenecerá al rol1 y rol2. Para proteger las carpetas /usuario1 y /usuario2 se configura la siguiente
información de seguridad en el fichero descriptor:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<!-- RESTRICCIÓN DE SEGURIDAD -->
<security-constraint>
<display-name>Paginas de rol1</display-name>
<web-resource-collection>
<web-resource-name>Páginas de rol1</web-resource-name>
<description/>
<url-pattern>/usuario1/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<description/>
<role-name>rol1</role-name>
</auth-constraint>
</security-constraint>
<!-- RESTRICCIÓN DE SEGURIDAD -->
<security-constraint>
<display-name>Páginas de rol2</display-name>
<web-resource-collection>
<web-resource-name>Páginas de rol2</web-resource-name>
<description/>
<url-pattern>/usuario2/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<description/>
<role-name>rol2</role-name>
</auth-constraint>
</security-constraint>
<!-- CONFIGURACIÓN DE LOGIN -->
<login-config>
<auth-method>FORM</auth-method>
<realm-name>admin</realm-name>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login.jsp</form-error-page>
</form-login-config>
</login-config>
<!-- LISTA DE ROLES -->
<security-role>
<description/>

233
<role-name>rol1</role-name>
</security-role>
<security-role>
<description/>
<role-name>rol2</role-name>
</security-role>
</web-app>
En la página inicial index.jsp mostraremos la información del usuario actual y una lista de enlaces a las
diversas páginas protegidas. Sólo se mostrarán aquellas páginas sobre las que el usuario actual tenga derechos
de acceso:
<%-- PÁGINA index.jsp --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Ejemplo autentificación</title>
</head>
<body>
<div>
<c:if test='${pageContext.request.remoteUser==null}'>
<form action="dummy.jsp?logon" method="POST">
Nombre <input type="text" name="login" value="" />
Contraseña <input type="password" name="password" value="" />
<input type="submit" value="registrar" />
</form>
</c:if>
<c:if test='${pageContext.request.remoteUser!=null}'>
Usuario actual: ${pageContext.request.remoteUser}
<a href="dummy.jsp?logout">finalizar registro</a><br />
</c:if>
</div>
<div>
<c:if test='${pageContext.request.isUserInRole("rol1")}'>
<a href='<c:url value="/usuario1/index.jsp" />'>Página inicial para rol1</a><br>
</c:if>
<c:if test='${pageContext.request.isUserInRole("rol2")}'>
<a href='<c:url value="/usuario2/index.jsp" />'>Página inicial para rol2</a><br>
</c:if>
</div>
</body>
</html>
Siregistramos al usuario "ambos" deberían mostrarse los dos enlaces, puesto que este usuario pertenece a los
dos roles.
3.5.3. Alias sobre nombres de roles en servlets.
Los nombres de rolutilizados en servlets y página JSP deben coincidir con los registrados en el archivo
descriptor web.xml. Sin embargo, para obtener mayor flexibilidad, a nivel de servlets se pueden definir alias
para los roles en el fichero descriptor:
<servlet>
<servlet-name>MiServlet</servlet-name>
<servlet-class>MiServlet</servlet-class>
<security-role-ref>
<role-link>rol1</role-link>
<role-name>administrador</role-name>
</security-role-ref>
</servlet>
El elemento <role-link> estable el nombre declarado del rol en un elemento <security-role>, mientras que el
elemento <role-name> establece el alias del rol para usar en el servlet.

234
También podemos utilizar alias en las páginas JSP si declaramos la página en el fichero descriptor como si
fuese un servlet:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>index_jsp</servlet-name>
<jsp-file>/index.jsp</jsp-file>
<security-role-ref>
<role-link>rol1</role-link>
<role-name>administrador</role-name>
</security-role-ref>
</servlet>
<servlet-mapping>
<servlet-name>index_jsp</servlet-name>
<url-pattern>/index.jsp</url-pattern>
</servlet-mapping>
……………………
</web-app>
Pero ¿qué pasa si un alias coincide con un nombre de rol existente? Supongamos que existen y son válidos los
roles Admin y Administrador, y que se define el siguiente alias:
<security-role-ref>
<role-link>Administrador</role-link> <!-- nombre original -->
<role-name>Admin</role-name> <!-- alias -->
</security-role-ref>
¿Qué rol se evaluará en el siguiente código?
if (req.isUserInRole("Admin")) {
// hacer algo
}
La respuesta es: el rol Administrador. Es decir, el alias de un rol solapa a un rol existente.

4. Patrones de diseño

Un patrón describe, con algún nivel de abstracción, una solución experta a un problema. Normalmente, un
patrón está documentado en forma de una plantilla. Aunque es una práctica estándar documentar los
patrones en un formato de plantilla especializado, esto no significa que sea la única forma de hacerlo.
Además, hay tantos formatos de plantillas como autores de patrones; esto permite la creatividad en la
documentación de patrones.
4.1. Problemas más comunes con el software.
El trabajo más importante para una aplicación web es proporcionar al usuario final una experiencia confiable,
útil, y correcta. En otras palabras, el programa debe satisfacer los requerimientos funcionales como
"selecciona un tipo de producto" o "añade un producto a mi carro de compras". Una vez que hemos
asegurado que el sistema soporta los casos de uso, deberemos afrontar otro conjunto de exigencias no
funcionales:
• Rendimiento. Si nuestro sitio web es demasiado lento, obviamente perderemos usuarios. Los patrones de
diseño pueden ayudarnos a responder más rápido a las solicitudes y a soportar un número mayor de
usuarios simultáneos.
• Modularidad. Para que las diversas partes de nuestra aplicación se puedan ejecutar simultáneamente,
nuestro software debe ser diseñado de manera modular. Esto implica que cada parte del software que
realice una función diferenciada debe cumplir una tarea específica que no dependa de lo que esté
ocurriendo en otra parte.
• Flexibilidad, Mantenibilidad y Extensibilidad. La flexibilidad implica poder cambiar nuestro sistema
sin que eso afecte mucho al ciclo de desarrollo. Por ejemplo, podemos descubrir un error en un nuevo
componente y necesitar cambiarlo por el antiguo componente temporalmente. Necesitamos que el sistema
se flexible para que este cambio no lo bloquee. La mantenibilidad implica que podamos actualizar nuestro
sistema rápidamente. Por ejemplo, puede ser necesario cambiar el servidor de base de datos a otra

235
compañía. La extensibilidad implica que nuestro sistema debe ser capaz de poder integrar nuevas
funcionalidades según las necesidades.
4.2. Patrones Java EE.
La tecnología J2EE es una arquitectura en sí misma que comprende otras arquitecturas, incluyendo Servlets,
Páginas JSP, EJBs y otras, que proporciona su propio conjunto de patrones de diseño para crear varios tipos
de aplicaciones de desarrollo.
Los diversos componentes involucrados en el sistema se distribuyen en particiones lógicas denominadas
capas. Cada capa se comunica sólo directamente con sus capas adyacentes.
4.2.1. Las cinco capas en J2EE.
En la arquitectura J2EE se describen cinco capas:
• Cliente (Client). En esta capa se incluyen todos los componentes clientes de nuestras aplicaciones, como
por ejemplo el navegador web u otras aplicaciones que acceden a los servicios de nuestras aplicaciones
remotamente.
• Presentación (Presentation). Esta capa se comunica con la de Cliente, y encapsula toda la lógica de
presentación o interfaz visual de las aplicaciones. Acepta los requerimientos del cliente, maneja la
autentificación y autorización, gestiona las sesiones, delega los procesos a la capa de lógica del negocio, y
trasmite al cliente las respuestas a sus requerimientos. Incluye los filtros, servlets, páginas JSP, JavaBeans y
otras clases útiles.
• Lógica del negocio (Business). Esta capa es el corazón de las aplicaciones e implementa el núcleo de sus
servicios y procesos. Incluye los componentes EJB que manejan las reglas del negocio de la aplicación.
• Integración (Integration). El trabajo de esta capa es integrar los diferentes recursos externos de la capa de
Recursos con los componentes de la capa de lógica del negocio. Se usan varios mecanismos como JDBC,
tecnologías de conexión J2EE, o soluciones propietarias de terceros.
• Recursos (Resource). En esta capa se exponen los recursos externos que proporcionan los datos a las
aplicaciones. Los recursos pueden ser datos guardados en bases de datos relacionales, bases de datos
basadas en ficheros, gestores de base de datos ejecutándose en servidores, etc.
Sobre cada una de estas capas se pueden definir varios patrones de diseño. En este curso explicaremos los
siguientes: Intercepting Filter, Model-View-Controler, Front Controller, Service Locator, Bussines Delegate, y
Transfer Object.
Figura 14

4.3. Patrón Intercepting Filter/Decorating Filter (Filtros interceptores).


El mecanismo que gestiona la solicitud de recursos de la capa de presentación recibe muchos tipos diferentes
de solicitudes. Algunas simplemente requieren enviar el recurso solicitado, pero otras pueden requerirvarios
tipos de procesamiento.
Planteamiento.
El problema que surge es quese requiere un pre-procesamiento y un post-procesamiento de algunas
solicitudes antes de enviar la respuesta al cliente web. Algunos de estos procesos pueden tener que ver son las
siguientes cuestiones:

236
¿Se ha autentificado el cliente?
¿Tiene el cliente una sesión válida?
¿La dirección IP del cliente es de una red conocida?
¿Viola alguna restricción la ruta de la petición?
¿Qué codificación usa el cliente para enviar los datos?
¿Soportamos el tipo de navegador del cliente?
Algunos de estas comprobaciones son simples, y basta con evaluar una respuesta afirmativa o negativa que
determine si continuamos con el procesamiento. Otras comprobaciones requieren manipular el canal de datos
entrantes a una forma aceptable para el procesamiento.
La solución básica consiste en una serie de comprobaciones condicionales, de forma que si cualquiera de ellas
falla la petición se aborta. Se pueden usar sentencias if/else anidadas en cada recurso solicitado (si son servlets
y páginas JSP), pero esta solución es poco flexible, porque el flujo del filtrado y la acción de los filtros se
compila dentro de la aplicación.
La clave para solventar este problema de una forma flexible y no obstrusiva es tener un mecanismo simple
para añadir y eliminar componentes de procesamiento, en el que cada componente completa una acción de
filtrado específica.
Solución.
La solución es crear filtros conectables para procesar servicios comunes de una forma estándar sin requerir
cambios en el código principal del procesamiento de la solicitud. Los filtros interceptan las peticiones
entrantes y las respuestas salientes, permitiendo un pre- y post-procesamiento. Podemos añadir y eliminar
estos filtros a discreción, sin necesitar cambios en nuestro código existente.
Tal como se ha visto en unidades anteriores, Java EE proporciona un mecanismo de filtros mediante clases
que implementan la interfaz Filter.
Un ejemplo de filtros.
Un uso característico de los filtros es para implementar un mecanismo de seguridad. Podemos usarlos en vez
de los mecanismos ya vistos, o podemos usarlos solo para la parte de autorización.
package filtros;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.*;
@WebFilter(filterName = "FiltroSeguridad", urlPatterns = {"/*"})
public class FiltroSeguridad implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
HttpSession sesion = req.getSession();
// 1) Si recibimos credenciales desde el formulariode login
if (req.getServletPath().equals("/security_check")) {
String login = req.getParameter("username");
String password = req.getParameter("password");
if (validaCredenciales(login, password)) {
sesion.setAttribute("usuario", login);
} else {
res.sendError(HttpServletResponse.SC_UNAUTHORIZED, "ccccccccc");
}
}
// 2) Si no hay un usuario auntentificado solicitamos credenciales
if (sesion.getAttribute("usuario") == null) {
sesion.setAttribute("direccionSolitud", req.getServletPath());
req.getRequestDispatcher("/login.jsp").forward(request, response);
return;
// 3) Si hay un usuario auntentificado redirigimos
} else {
String ruta = (String) sesion.getAttribute("direccionSolitud");
sesion.removeAttribute("direccionSolitud");
if (ruta != null) {

237
req.getRequestDispatcher(ruta).forward(request, response);
return;
} else {
chain.doFilter(request, response);
}
}
}
public void destroy() {
}
public void init(FilterConfig filterConfig) {
}
private boolean validaCredenciales(String login, String password) {
return login.equals(password);
}
}
En este ejemplo, el filtro FiltroSeguridad se encarga de solicitar credenciales si no hay usuario registrado, de
validar las credenciales y de redirigir al recurso solicitado. En otros escenarios más reales seguramente se
utilizarán varios filtros encadenados. A continuación se explica el funcionamiento de este filtro:
1) El filtro primero mira si es invocado el recurso "/security_check". Este recurso debe ser invocado por un
formulario de login para pasar credenciales. Las credenciales son validadas por la función
validaCredenciales(); en este ejemplo se ha simplificado su código validando credenciales donde coincida el
nombre con la contraseña. Si las credenciales son válidas se guarda el nombre de usuario en un atributo de
sesión y se sigue ejecutando. Si las credenciales no son válidas se devuelve el código de error 401 (no
autorizado).
2) A continuación se mira si no hay todavía un usuario autentificado. Si no lo hay se redirige a una página
de login con un formulario que solicite las credenciales. Antes se guarda en un atributo de sesión la
dirección del recurso solicitado. La página de login puede ser así:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Solicitud de credenciales</title>
</head>
<body>
<form action="security_check" method="POST">
Nombre de usuario <input type="text" name="username"><br>
Contraseña <input type="password" name="password"><br>
<input type="submit" value="Autentifica">
</form>
</body>
</html>
3) Si hay un usuario autentificado se mira si hay almacenada una variable de sesión con la dirección a la que
se debe redirigir, sino se sigue con la cadena natural de redirección.
Características destacables de este patrón.
Como resumen, el patrón Intercepting Filter tiene las siguientes características:
• Es un patrón de la capa de presentación.
•Permite centralizar el control de procesos sobre un recurso con un acoplamiento ligero. No interfieren con
la lógica interna de un recurso, sólo aplican un proceso antes y después de la ejecución del recurso.
•Permiten la reutilización. Al ser interceptores conectables se añaden y eliminan de forma transparente,
pudiendo combinarse en formas diferentes.
•Utilizan una configuración declarativa y flexible. Se pueden combinar numerosos servicios en varias
permutaciones sin tener que recompilar ni una sola vez el código fuente.
•No permiten una compartición de información eficiente. Compartir información entre filtros puede ser
ineficiente, porque por definición todo filtro debe tener acoplamiento ligero. Si se deben compartir grandes
cantidades de información entre los filtros, esta aproximación podría ser muy costosa.

238
4.4. Patrón Front Controller/Front Component (Controlador de Entrada).
En aplicaciones cliente-servidor basadas en componentes a veces se producen las siguientes situaciones:
• Se produce un diálogo completo entre cliente y servidor es un proceso de múltiples pasos de solicitudes y
respuestas.
• Los pasos tienen que producirse mediante secuencias bien definidas.
• El siguiente paso en una secuencia es decidido dinámicamente basándose en las respuestas del paso
previo.
• Cada paso es ejecutado por un componente diferente.
Planteamiento.
Los clientes envían solicitudes a cada componente del sitio web según unas condiciones. Antes de responder
a cada solicitud, cada componente debe autentificar el origen del requerimiento. Cada componente debe
mantener el estado de la sesión actual y saber qué componente es el siguiente. Un cambio en la lógica de la
secuencia requiere un cambio en las acciones de los componentes involucrados.
Por ejemplo, en una aplicación web que acepta pagos mediante tarjetas de crédito, se deben completar varios
pasos:
- Mostrar un catálogo.
- Añadir productos a un carro de compra.
- Confirmar la lista de compra.
- Obtener el nombre y dirección del receptor.
- Obtener la información de la tarjeta de crédito.
Cada página JSP necesita conocer en qué paso se produce cada circunstancia y a qué recursos debe invocar
para avisar de errores o responder a las acciones del usuario. Cada página debe autentificar el requerimiento y
autorizar al usuario para realizar la tarea requerida.
Solución.
Debemos crear un componente web que conozca todos los pasos en la aplicación, incluido el estado del
cliente, en qué paso está, cómo las peticiones serán autentificadas, y el componente que debe procesar el
siguiente paso. Este objeto actúa como una puerta de entrada para los clientes y es llamado Controlador de
Entrada (Front Controller). Entre varias estrategias sugeridas por Java EE, las más sencillas usan un servlet o
página web como objeto de entrada. Todas las peticiones serán enviadas al Controlador de Entrada pasando
una acción como parámetro de llamada.
Algunas veces, los programadores confunden el patrón de diseño Model-View-Controller y Front Controller.
Model-View-Controller es más un patrón de arquitectura, con tres sub-sistemas (Modelo, Vista y
Controlador), cada uno de los cuales consta de varios componentes. Por ejemplo, el subsistema Controlador
puede tener varios servlets intercomunicados, y uno de estos servlets puede actuar como el Front Controller
para varias secuencias o conjunto de pasos.
Un ejemplo de Controlador.
Un Front Controller es un componente que proporciona un punto de entrada común a todos los
requerimientos de los clientes. De esta forma, el controlador unifica y dinamiza la autenticación y
autorización; y envía el trabajo al componente de trabajo apropiado, facilitando así la gestión de las
condiciones de proceso. Este componente:
• Recibe los requerimientos.
• Gestionar el flujo de proceso de la aplicación web
• Gestionar la secuencia de pasos
• Gestiona las diversas condiciones de proceso
Supongamos que tenemos que crear un sitio web donde la navegación por páginas debe ser dirigida en un
orden preestablecido. En este sitio tendremos las siguientes páginas:
datospersonales.jsp: será el punto de entrada al sitio web. Desde aquí se accederá a las demás páginas
mediante enlaces.
solictud.jsp: debe ser accedida desde datospersonales.jsp y permitirá rellenar un formulario con los datos de
una solicitud. El formulario deberá enviar los datos a la página recepcionsolicitud.jsp.
recepcionsolicitud.jsp: debe ser accedida desde solicitud.jsp, y procesará los datos de solicitud. Después
debe redirigir a la página inicial datospersonales.jsp.
Para controlar el acceso a estas páginas y otras que podrían crearse en el sitio web podemos usar un Front
Controller implementado mediante un servlet. El servlet ServletController tendrá asociado el patrón Url

239
"/control". Se puede poner como página por defecto del sitio web "/control" para que la primera solicitud se
redirija hacia este servlet.
Cada solicitud desde una página tendrá un parámetro de solicitud llamado destino que indicará la página
siguiente a la que debe redirigir. Si este parámetro no existe se redirigirá a la página inicial. Además se utiliza el
valor de una cookie "origen". El uso de esta cookie permite controlar falsas solicitudes.
package servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletControlador", urlPatterns = {"/control"})
public class ServletControlador extends HttpServlet {
private static final String SOLICITUD = "1";
private static final String RECEPCION_SOLICITUD = "2";
private static final String DATOS_PERSONALES = "0";
// Método auxiliar para recupear el valor de la cookie "origen"
private String getOrigenCookie(Cookie[] cookies) {
if (cookies != null) {
for (Cookie ck : cookies) {
if (ck.getName().equals("origen")) {
return ck.getValue();
}
}
}
return "";
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String origen = getOrigenCookie(request.getCookies());
String destino = request.getParameter("destino");
if (destino == null) destino = "";
switch (destino) {
case SOLICITUD:
if (origen.equals(DATOS_PERSONALES)) {
response.addCookie(new Cookie("origen", SOLICITUD));
response.sendRedirect(request.getContextPath() + "/solicitud.jsp");
return;
}
break;
case RECEPCION_SOLICITUD:
if (origen.equals(SOLICITUD)) {
response.addCookie(new Cookie("origen", RECEPCION_SOLICITUD));
response.sendRedirect(request.getContextPath() + "/recepcionsolicitud.jsp");
return;
}
break;
case DATOS_PERSONALES:
default:
response.addCookie(new Cookie("origen", DATOS_PERSONALES));
response.sendRedirect(request.getContextPath() + "/datospersonales.jsp");
return;
}
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
}

240
}
Aunque un usuario puede llamar directamente a una de las páginas, cual dato introducido en un formulario o
a través de un enlace será controlador por el servlet controlador, el cual siempre comprobará un cookie
previa. Esto impedirá la suplantación de páginas. Un contenido simplificado para estas páginas JSP es como
sigue:
<%-- FICHEROdatospersonales.jsp--%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Página de entrada de datos</title>
</head>
<body>
<h1>datos personales</h1>
<a href="control?destino=1">sollicitud</a>
</body>
</html>
<%-- FICHEROsolicitud.jsp--%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Página de solicitud</title>
</head>
<body>
<h1>solicitud</h1>
<a href="control?destino=2">recepción solicitud</a>
</body>
</html>
<%-- FICHERO recepcionsolicitud.jsp--%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Página de recepción</title>
</head>
<body>
<h1>recepcionsolicitud</h1>
<a href="control?destino=0">datos personales</a>
</body>
</html>
Características destacables de este patrón.
Como resumen, el patrón Front Controller tiene las siguientes características:
• Es un patrón de la capa de presentación.
• Centraliza el control. Un controlador proporciona un lugar central para manejar los servicios del sistema y
la lógica de negocios entre varias peticiones.
• Mejora el control sobre la seguridad. Un controlador proporciona un punto de choque para intentos de
accesos ilícitos en la aplicación Web. Además, auditar una sola entrada en la aplicación requiere menos
recursos que distribuir las comprobaciones de seguridad entre todas las páginas.
• Mejora la reutilización. Un controlador promueve el particionamiento limpio de la aplicación y aconseja la
reutilización, ya que el código que es común entre los componentes se mueve dentro de un controlador o es
manejado por un controlador.
4.5. Patrón Service Locator (Localizador de Servicios).
En aplicaciones Java EE la creación y búsqueda de servicios y recursos suele involucrar complejas interfaces y
operaciones de red.

241
Planteamiento.
Los clientes de Java EE suelen interactuar con componentes de servicio, como Enterprise JavaBeans (EJB),
componentes de Servicios de Mensajería Java (JMS), etc.
Para interactuar con estos componentes, los clientes deben localizar los componentes de servicio (hablaremos
de operaciones de búsqueda) o crear un nuevo componente que realice la búsqueda.Todas las aplicaciones
cliente de la plataforma Java EE suelen usar la facilidad común de búsqueda JNDI que ya hemos visto. Así
pues nos encontramos normalmente con que muchos módulos cliente suelen repetir el código de consulta de
JNDI. Esto causa una duplicación innecesaria de código.
Solución.
Se puede usar un objeto localizador de servicios (un service locator) para abstraer todo el uso JNDI y ocultar
la complejidad de la creación del contexto inicial, y búsqueda de objetos. Varios clientes pueden reutilizar el
objeto Service Locator para reducir la complejidad del código, proporcionar un único punto de control, y
mejorar el rendimiento proporcionando facilidades de caché.
Un ejemplo de localizador de servicios.
Para que un localizador de servicios sea fácilmente accesible y utilizable por los cliente es habitual que utilice
el patrón de diseño singleton. Por ejemplo, la siguiente clase Localizador, provee una única instancia que
proporciona métodos para localizar objetos desde diversas fuentes.
package recurso;
package recurso;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Localizador {
private static Localizador localizador;
public static Localizador getInstance() {
if (localizador == null) {
localizador = new Localizador();
}
return localizador;
}
private Context contextoJNDI;
public Localizador() {
try {
contextoJNDI = new InitialContext();
} catch (NamingException ex) {
throw new RuntimeException(ex);
}
// Otras inicializaciones
}
public Object getJNDI(String nombre) {
try {
return contextoJNDI.lookup(nombre);
} catch (NamingException ex) {
throw new RuntimeException(ex);
}
}
}
Este ejemplo se ha simplificado mucho. En teoría la clase Localizador podría usarse para obtener recursos de
contexto JNDI y otros servicios. Bastaría con incluir más métodos específicos de búsqueda.
Características destacables de este patrón.
Como resumen, el patrón Service Locator tiene las siguientes características:
• Es un patrón de la capa de la lógica del negocio.
• Abstrae de la complejidad de acceso a servicios. El cliente no necesita tratar con la búsqueda de objetos
fabricantes de componentes (InicitalContext, EJBHome, QueueConnectionFactory y TopicConnectionFactory,
además de otros) porque el Service Locator se encarga de esto.
• Proporciona a los clientes un acceso uniforme a los servicios.
• Facilita añadir nuevos componentes de negocio.

242
• Aumenta el rendimiento de red. Los clientes no están implicados en la búsqueda JNDI y la creación de
objeto. Como el Service Locator realiza este trabajo, puede agregar las llamadas de red requeridas para
buscar y crear objetos de negocio.
• Aumenta el rendimiento del cliente mediante cachés. El Service Locator puede almacenar en caché los
objetos de contexto inicial y las referencias a objetos fabricantes (EJBHome, fábricas de conexión JMS) para
eliminar la actividad JNDI innecesaria que ocurre cuando se obtiene el contexto inicial y otros objetos. Esto
mejora el rendimiento de la aplicación.
4.6. Business Delegate (Delegado del Negocio).
En una aplicación distribuida de forma escalada se pueden dar las siguientes situaciones:
• Existen componentes independientes que deben interactuar con el usuario final y manejar la lógica del
negocio. Es el caso, por ejemplo, de un Service Locator.
• Estos componentes independientes residen en diversos subsistemas separados en la red.
• Los componentes que manejan la lógica del negocio actúan como componentes servidores porque
proporcionan servicios a los clientes a través de una interfaz formada por métodos (una API).
• Los componentes clientes que usan estas APIs residen en sistemas remotos en la red.
• Existe más de un cliente usando las APIs.
• Hay más de un componente servidor proveyendo servicios similares, pero con pequeñas diferencias en la
API.
Planteamiento.
Los servicios implementados en la capa del negocio son accedidos directamente por los componentes de la
capa de presentación a través de las APIs expuestas por los servicios. Sin embargo, las interfaces de tales
servicios sufren cambios cuando los requerimientos evolucionan. Esto afecta a todos los componentes de la
capa de presentación. Es más, todos los componentes del lado del cliente son dependientes de la localización
particular de los servicios del negocio; esto es, cada componente tiene que usar un servicio JNDI concreto
para localizar las interfaces remotas requeridas.
Ejemplo de delegado del negocio.
En el caso de la arquitectura Java EE, los componentes servidores que exponen las APIs suelen ser beans de
sesión o un componente Service Locator. Los clientes suelen ser servlets y objetos bean en las páginas JSP;
para abtraerlos del acceso a servicios concretos de la lógica del negocio podemos crear un componente que se
base en una interfaz que se adapte a las invocaciones necesarias.
Como ejemplo crearemos una interfaz para acceder a varios recursos de servicios, uno de ellos será la clase
Localizador creada en el ejemplo de Service Locator:
package recurso;
public interface IDelegado {
Object getObjetoJNDI(String nombre);
}
Los servlets y páginas JSP usarán invocaciones a los métodos de esta interfaz. Una implementación concreta
de esta interfaz puede ser la siguiente:
package recurso;
public class DelegadoImpl implements IDelegado {
@Override
public Object getObjetoJNDI(String nombre) {
return Localizador.getInstance().getJNDI(nombre);
}
}
Si se usan técnicas de inyección de código, a través del contenedor web podremos decidir una
implementación concreta de la interfaz IDelagado a medida que evolucione nuestra aplicación.
Características destacables de este patrón.
Como resumen, el patrón Bussines Delegate tiene las siguientes características:
• Es un patrón de la capa de presentación pero que interactúa con la capa de la lógica del negocio.
•Evita la repetición de código; cada componente no tiene que incluir el código que llama a las interfaces
remotas e invoca sus métodos.
• Oculta la API de los servicios remotos a los componentes cliente, lo cual reduce los cambios requeridos
cuando cambia la API.

243
• El Delegado del Negocio puede encargarse de todas la tareas relacionadas con los servicios, como la
captura de excepciones. Los resultados de las invocaciones remotas pueden ser guardarse en cachés, lo cual
mejora el rendimiento.
• El Delegado del Negocio puede a su vez usar otros patrones de diseño, como losService Locator para
localizar los servicios remotos.
4.7. Patrón Value Object/Transfer Object/Replicate Object (Objeto Valor).
Cuando en aplicaciones Java EE necesitamos que un componente cliente interactúe con un componente
servidor se pueden dar las siguientes situaciones son típicas:
• Los componentes del lado-cliente y del lado-servidor residen en localizaciones remotas y se comunican a
través de la red global.
• El servidor maneja la base de datos a la cual no tiene acceso el cliente.
• El servidor provee métodos de acceso para que los clientes obtengan datos.
• El servidor proporciona métodos de captura para que los clientes puedan enviar datos que deben ser
guardados en la base de datos
Planteamiento.
En las aplicaciones Java EE, cada comunicación entre un cliente y un servidor se realiza con una llamada
remota a un método con los consecuentes problemas de red. Cada aplicación cliente llama a métodos setter y
getter individualmente, provocando un elevado tráfico de red con la consecuente degradación del sistema.
En la arquitectura Java EE, la capa de lógica del negocio accede a una base de datos directamente o a través
de la capa de recursos, y encapsula los datos en beans de entidad y de sesión. Estos beans exponen los datos a
través de interfaces remotas. Los servlets y páginas JSP en la capa de presentación que necesitan acceder a los
datos del negocio, pueden hacerlo llamando a los métodos de las interfaces remotas implementados por los
beans. Por ejemplo, necesitamos mantener información sobre direcciones de usuarios registrados en una base
de datos. La dirección (que incluye calle, ciudad, código postal) puede encapsularse en una entidad lógica del
negocio. Para acceder a la información se usa un bean que expone métodos para clientes remotos, como
getCalle(), setCalle(), getCiudad(), etc. Los servlet y páginas JSP deben llamar a los métodos uno a uno sobre el
servidor remoto lo cual degrada las comunicaciones provocando tiempos de espera grandes.
Solución.
La solución es tan sencilla como crear un objeto que encapsule todos los valores de los atributos requeridos
por el cliente. Este objeto es llamado el Value Object. Cuando el cliente demando datos, el servidor compone
los valores y construye el Value Object, el cual es enviado al cliente mediante serialización. El cliente
reconstruye el objeto localmente con el contenido del Value Object.
De esta forma, en vez de múltiples llamadas, se invoca un único método, getDireccion(), el cual retorna todos
los atributos estructurados en un objeto.
Ejemplo de objetos de valor.
Casi todas las tecnologías de acceso a datos vistas en este curso utilizan objetos de valor u objetos de
transferencia. Por ejemplo, las clases de entidad de Java Persistence son objetos de valor.
Características destacables de este patrón.
Como resumen, el patrón Transfer Object tiene las siguientes características:
• Es un patrón de la capa de la lógica del negocio.
• Simplifica las interfaces remotas porque ahora hay un único método que retorna un objeto que encapsula
todos los valores. Se reduce el número de llamadas remotas.
• Si el cliente quiere dar de alta un valor, modifica el objeto local y lo envía al servidor.
• El objeto local puede quedar obsoleto, puesto que el registro de la base de datos puede haber sido
modificado por otro cliente. Esto puede dar lugar a inconsistencia de datos.
4.8. Patrón Model-View-Controller (MVC).
En sistemas que trabajan con interfaces de usuario es habitual que el sistema tenga que aceptar datos del
usuario, darlos de alta en una base de datos, y retornar datos al usuario pasado un tiempo. Además pueden
existir varios caminos para aceptar datos y presentarlos al usuario. Y que los datos suministrados por un
formulario deban ser recuperados por otro formulario.
Planteamiento.
Si el sistema despliega un simple componente que interactúa con el usuario para mantener unabase de datos,
entonces una solicitud que incorpore nuevos tipos de datos o vistas necesitará rediseñar el componente.

244
Supongamos un banco que proporciona acceso on-line a cuentas bancarias. Cuando accedemos al sitio, la
aplicación web permite al usuario ver los movimientos durante un periodo de tiempo de varias formas, como
un gráfico de barras, como un gráfico de línea o como una tabla plana. Los mismos datos son representados
de varias formas, pero son controlados por una misma entidad, la aplicación web. En el contexto de este
problema, observamos lo siguiente:
• Hay tres tareas a realizar: gestionar la interacción del usuario con el sistema, gestionar los datos actuales, y
dar formato a los datos para cada tipo de presentación.
• Un mismo componente que realiza todas las tareas debe ser dividido en tres componentes independientes.
• Cada tarea puede ser manejada por diferentes componentes.
Solución.
La solución es separar la presentación de datos del mantenimiento de datos y tener un tercer componente que
coordine los dos primeros. Estos tres componentes son llamados el Modelo, la Vista y el Controlador. Sus
responsabilidades respectivas son:
• El Modelo es responsable de gestionar los datos o el estado de la aplicación. También maneja la copia y
obtención de los datos cuando cambian.
• La Vista contiene la presentación lógica. Muestra los datos contenidos en el Modelo a los usuarios.
También permite al usuario interaccionar con el sistema y notificar al Controlador de las acciones del
usuario.
• El Controlador gestiona todo lo que ocurre en el sistema. Instancia el Modelo y la Vista y los asocia.
Dependiendo de los requerimientos de la aplicación puede instanciar múltiples vistas y puede asociarlas con
el mismo modelo.
Características destacables de este patrón.
Como resumen, el patrón MVC tiene las siguientes características:
• Es considerado más como una estructura que como un patrón., aunque puede ser aplicado sobre la capa
de presentación.
• Al separar la representación de los datos (Modelo) de su visualización (Vista) permite múltiples vistas
sobre los mismos datos.
• Permite cambios en ambos componentes de forma independiente, lo cual aumenta la mantenibilidad del
sistema.
• Al separar el control de la aplicación de la presentación de datos, permite al Controlador crear una vista
apropiada durante la ejecución basada en el modelo.
• Al separar el control de la representación de los datos en el modelo, permite que las respuestas al usuario
puedan ser mapeadas a diferentes niveles del modelo.
En unidades posteriores se verán ejemplos de este patrón: Spring y Struct.
4.9. Otros patrones de diseño.
En este capítulo haremos referencia a otros patrones de diseño que se complementan con los ya vistos.
4.9.1. Patrón Session Façade/Session Entity Façade/Distributed Façade (Fachada de Sesión).
Este patrón satisface la necesidad de centralizar el acceso a los diversos servicios web y EJBs en la capa de la
lógica del negocio.
Un componente de fachada de sesión hace la misma labor respecto a la capa de la lógica que un Front
Controller respecto a la capa de presentación. Proporciona una interfaz más simple a los clientes ocultando
todas las interacciones complejas entre componentes del negocio. También reduce el número de objetos del
negocio que son expuestos al cliente a través de la capa de servicios sobre la red.
El Session Façade usa objetos de transferencia para simplificar las interacciones con los servicios. También
puede usar componentes localizadores de servicio para reducir la complejidad del código. Y a su vez es usado
por delegados del negocio.
4.9.2 Data Access Object/Data Access Component/DAO (Objeto de Acceso a Datos).
Este patrón de diseño cubre el problema de que los clientes de datos tengan que acceder a la lógica del
negocio para recuperar datos desde diversos orígenes. Un componente DAO ofrece una interfaz flexible y
mantenible que encapsula los datos y los protege de cambios.
En unidades anteriores ya se han mostrado ejemplos de componentes DAO. Un DAO es un objeto Java que
se dirige a la base de datos subyacente y proporciona a otros componentes de la aplicación una interfaz
limpia, simple, y común para tener acceso a los datos. De esta forma reduce la dependencia de otros
componentes sobre los detalles de usar la base de datos.

245
Los DAO usan a los Transfer Object para encapsular los datos, y es usado por los servicios web y EJBs para
acceder a los datos.

PRÁCTICA
01. Debes crear un sitio web que debe autentificar a los usuarios en cuanto se accede a la página inicial. El
sitio gestionará una base de datos con dos tablas y una vista:
CREATE TABLE USUARIO (
ID INTEGER PRIMARY KEY,
NOMBRE VARCHAR(20) NOT NULL,
PASSWORD VARCHAR(20) NOT NULL,
DATOS_PERSONALES VARCHAR(200)
);
CREATE TABLE USUARIO_ROLES (
ID_USUARIO INTEGER NOT NULL,
NOMBRE_ROL VARCHAR(20) NOT NULL,
PRIMARY KEY (ID_USUARIO, NOMBRE_ROL)
);
CREATE VIEW ROLES AS SELECT U.NOMBRE, R.NOMBRE_ROL
FROM USUARIO U JOIN USUARIO_ROLES R ON U.ID=R.ID_USUARIO;
Implementa el mecanismo de autentificación usando este base de datos mediante un DataSoure disponible a
través de JNDI. Si el usuario es autentificado accederá a la página principal, donde se mostrará su nombre y
datos personales. Si el usuaro no es autentificado accederá a una página de error con un enlace para volver a
autentificarse.
Crea un Service Locator, un Bussines Delegate y un Transfer Object para poder acceder a las base de datos
mediante el DataSource.

246
UNIDAD 17. DOCUMENTOS XML.
1. Las tecnologías XML.
XML son las siglas en inglés de Extensible Markup Language (lenguaje de marcas extensible). Se trata de un
metalenguaje extensible de etiquetas desarrollado por el World Wide Web Consortium (W3C). Por lo tanto
XML no es realmente un lenguaje en particular, sino una manera de definir lenguajes para diferentes
necesidades. Algunos de estos lenguajes que usan XML para su definición son XHTML, SVG, MathML.
Es un formato ideal para serializar objetos en formato de texto, y en ese sentido es utilizado por los
protocoles que realizan llamadas a servicios web.
Java incluye las tecnologías JAXP (Java API for XML Processing) para procesar datos en formato XML en
aplicaciones Java EE. Las diversas tecnologias de JAXP aligeran el analizar el contenido de un documento
XML y convertirlo en datos manejables mediante objetos. Para usar documentos XML en páginas JSP
también se pueden utilizar el grupo de etiquetas JSTL XML.
Pero antes de describir todas estas tecnologías daremos un repaso por el estandar XML y sus tecnologías
asociadas.
1.1 Estructura de un documento XML.
La tecnología XML surge como una solución para el problema de expresar información estructurada de la
manera más abstracta y reutilizable posible. Que la información sea estructurada quiere decir que se compone
de partes bien definidas, y que esas partes se componen a su vez de otras partes. Esta manera de
estructuración recursiva permite definir árboles y sub-árboles de información que a su vez pueden ser
tratados como documentos y sub-documentos XML.
Por ejemplo, el siguiente documento permite estructurar información sobre ventas:
<?xml version="1.0" ?>
<ventas>
<venta id="1">
<fecha>2014-03-12</fecha>
<precio>34,20</precio>
</venta>
<venta id="2">
<fecha>2014-04-02</fecha>
<precio>45,25</precio>
</venta>
</ventas>
Cada árbol de información se estructura mediante un elemento formado por una etiqueta de inicio, como
<venta>, y un etiqueta de cierre, como </venta>. Cada documento XML suele tener un elemento raíz que
contiene a los demás elemenetos de datos. En este ejemplo el elemento raíz es <ventas></ventas>.
Las etiquetas de inicio admiten el uso de atributos, como id="1" en el caso de la etiqueta <venta>. Dentro
del cuerpo de un elemento se pueden poner más elementos o un texto de contenido. En el caso del elemento
<venta></venta> que está formado por subelementos, los cuales están formados en su contido por un
contenido de texto.
Un elemento que contiene subelementos se denomina nodo intermedio, mientras que un elemento que
contiene un texto de datos se denomina nodo hoja. El propio contenido de texto es considerado como un
nodo final de texto.
1.1.1. Documentos bien formados.
Se dice que un documento XML está bien formado cuando cumple con unas definiciones básicas de formato
y puede, por tanto, ser analizado correctamente por cualquier analizador sintáctico (parseador) que cumpla
con la norma. Se diferencia esto del concepto de validez que se explica más adelante. Estas definiciones
básicas son:
• Los documentos han de seguir una estructura estrictamente jerárquica con lo que respecta a las etiquetas
que delimitan sus elementos. Una etiqueta debe estar correctamente incluida en otra, es decir, las etiquetas
deben estar correctamente anidadas. Los elementos con contenido deben estar correctamente cerrados.
• Los documentos XML sólo permiten un elemento raíz del que incluye a todos los demás elementos.
• Los valores de atributos en XML siempre deben estar encerrados entre comillas simples o dobles.

247
• El nombre de las etiquetas y atributos son sensibles a mayúsculas y minúsculas.
Por ejemplo, si tenemos el siguiente documento XML:
<fecha>
<venta id="1">
2014-03-12
</fecha>
<precio>34,20</precio>
</venta>
Diremos que no está bien formado, porque el elemento <fecha> contiene a un elmento <venta> que se
cierra despues de cerrarse el elemento <fecha>. Sin embargo, el siguiente documentos XML sí está bien
formado:
<fecha>
<venta id="1">
2014-03-12
</venta>
<precio>34,20</precio>
</fecha>
Aunque sintacticamente pueda no tener mucho sentido, las etiquetas están perfectamente estructuradas.
Además, en un documento XML existe un conjunto de caracteres llamados espacios en blanco (espacios,
tabuladores, retornos de carro, saltos de línea) que los procesadores XML tratan de forma diferente en el
marcado XML. Algunos procesadores ignoran estos espacios en blanco, mientras que otros procesadores
pueden considerarlos como nodos de texto intermedios.
1.1.2. Las partes de un documento XML.
Un documento XML está formado normalmente por un prólogo y por el cuerpo del documento, así como
texto de etiquetas que contiene una gran variedad de contenido adicional.
Aunque el prólogo no es obligatorio, los documentos XML pueden empezar con unas líneas que describen la
versión XML, el tipo de documento y otras cosas. El prólogo de un documento XML contiene:
• Una instrucción XML. Es la sentencia que declara al documento como un documento XML:
<?xml version="1.0" encoding="UTF-8"?>
• Una declaración del tipo de documento. Esta declaración permite definir un fichero para validar al
documento. Estos ficheros hacen referencia a un DTD (Definición de Tipo de Documento).
<!DOCTYPE ventas SYSTEM "ventas.dtd">
• La referencia a una hoja de estilo externa que indica cómo representar el documento.
<?xml-stylesheet href="estiloVentas.css" type="text/css"?>.
• Uno o más comentarios e instrucciones de procesamiento. Los comentarios se encapsulan entre los
elementos <!-- y -->.
<!-- Ventas del negocio -->
A diferencia del prólogo, el cuerpo no es opcional en un documento XML, y debe contener solo un elemento
raíz, característica indispensable también para que el documento esté bien formado.
Uniendo todas las partes, el documento XML resultaría así:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ventas SYSTEM "ventas.dtd">
<?xml-stylesheet href="estiloVentas.css" type="text/css"?>.
<!-- Ventas del negocio -->
<ventas>
<venta id="1">
<fecha>2014-03-12</fecha>
<precio>34,20</precio>
</venta>
<venta id="2">
<fecha>2014-04-02</fecha>
<precio>45,25</precio>
</venta>
</ventas>
1.1.3. Instrucciones de proceso XML.
En su prólogo, un documento XML puede incluir una serie de instrucciones de procesamiento, delimitadas
por <? y ?>, en las que se puede indicar el sistema de codificación empleado (Unicode por defecto: UTF-8),

248
especificar la hoja de estilo XSLT que se empleará para visualizar el documento, declarar espacios de nombres
y definir el esquema del documento, etc. La única intrucción requerida es la siguiente:
<?xml version="1.0"?>
Un ejemplo más detallado en el que se especifica la hoja de estilo que se aplica al documento para su
presentación es el siguiente:
<?xml versión="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="template.xsl"?>
<raiz>
</raiz>
1.1.4. Definición de entidades.
Dado que los caracteres > y < son utilizados por la sintaxis de los documentos XML, el estandar establece
una entidades para representar caracteres especiales para que, de esta forma, no sean interpretados como
marcado en el procesador XML.
En XML 1.0 se definen cinco entidades para representar caracteres especiales:
• &amp; para representar el símbolo &.
• &lt;para representar el símbolo <.
• &gt;para representar el símbolo >.
• &apos;para representar el símbolo '.
• &quot;para representar el símbolo ".
Por ejemplo, si en un documento XML incluimos el siguiente nodo:
<descripcion>Esta descripción incluye una etiqueta &lt;test&gt; de ejemplo</descripcion>
Un procesador XML que analice su contenido mostrará lo siguiente:
Esta descripción incluye una etiqueta <test> de ejemplo
Se pueden definir más entidades del siguiente modo:
<!ENTITY copy "&#169;">
Lo que nos permite usar &copy; en vez del carácter ©.
Las secciones CDATA nos permiten especificar también información que no es interpretada como marcado
XML. Por ejemplo:
<ejemplo>
<![CDATA[<HTML><HEAD></HEAD>]]>
</ejemplo>
1.2. Documentos XML en NetBeans.
El entorno de desarrollo NetBeans también da apoyo a la tecnología XML proporcionando varias plantillas y
editores. Podemos crear un documento XML con el ménú «Archivo|Nuevo fichero». En la categoría «XML»
disponemos de varias plantillas. La primera permite crear un nuevo documento XML, de las demás iremos
viendo su uso posteriormente:

249
Figura 1

Tras seleccionar la plantilla «Documento XML» debemos especificar el nombre del fichero y su ubicación:
Figura 2

En el siguiente paso podemos especificar si simplemente queremos crear un documento bien formado o si
queremos validarlo usando un DTD o un esquema XML. De momento crearemos un documento bien
formado:

250
Figura 3

Por último se crea el documento con un nodo raíz <root>. Podemos modificarlo y añadir subnodos.
Figura 4

El editor XML de NetBeans ofrece un menú contextual para comprobar y validar el documento. Es
aconsejable que cada vez que modifiquemos un documento XML lo comprobemos con la opción Alt+F9.
De las demás opciones hablaremos más adelante.

251
Figura 5

1.3. Definición de tipo de documento (DTD).


Un archivo DTD define los tipos de elementos, atributos y entidades permitidas en un documento XML, y
puede expresar algunas limitaciones para combinarlos.
Los documentos XML que se ajustan a su DTD se denominan "válidos". Un documento "bien formado"
simplemente respeta la estructura y sintaxis definidas por la especificación de XML.
Un DTD define la estructura válida para un tipo de documento XML, es decir:
• Los elementos que pueden aparecer en el documento.
• Los atributos que pueden utilizarse junto a cada elemento.
• Cómo se pueden anidar los elementos (padres e hijos).
• El orden en el que deben aparecer los elementos hijos de un mismo padre.
• El número permitido de elementos hijos.
• Si un elemento puede ser vacío o no.
• Tipos de datos para elementos y atributos.
• Valores por defecto y fijos para elementos y atributos.
1.3.1. Estructura de un DTD.
Podemos declarar el DTD en un archivo externo (con extensión dtd) y referenciarlo en el archivo XML de la
siguiente forma:
<?xml version="1.0"?>
<!DOCTYPE raiz SYSTEM "etiqueta.dtd">
<raiz>
</raiz>
O bien dentro del propio documento XML, como parte de su declaración de tipo de documento:
<?xml version="1.0" ?>
<!DOCTYPE raiz[
<!ELEMENT raiz (nodo1*, nodo2+)>
<!ATTLIST raiz att1 CDATA #IMPLIED>
<!ELEMENT nodo1 (nodo11? | nodo12)>
<!ELEMENT nodo11EMPTY>
<!ATTLIST nodo11 att2 CDATA #REQUIRED>
<!ELEMENT nodo12 (#PCDATA)>
]>

252
<raiz att1="1">
<nodo1></nodo1>
<nodo1>
<nodo11 attr2="1" />
<nodo12></nodo12>
</nodo1>
<nodo2></nodo2>
</raiz>
Donde ELEMENT declara elementos, y ATTLIST atributos de un elemento indicado.
1.3.2. Declaración de elementos.
En un DTD se empieza declarando el elemento raíz dentro de un nodo <!ELEMENT >, con su nombre y la
lista de elementos hijos entre paréntesis. A su vez, cada elemento hijo tendrá su propia declaración con un
contenedor <!ELEMENT >.
Un contenedor puede especificar las siguientes cosas:
• Un elemento vacío. Son elementos sin cuerpo, que pueden estar formados por atributos. Por ejemplo:
<!ELEMENT nodo11EMPTY>
• Cualquier tipo de contenido (infrecuente y desaconsejado, sólo útil durante el desarrollo de la DTD). Por
ejemplo:
<!ELEMENT elemento ANY>
• Sólo texto. El elemento no podrá contener elementos hijos. Por ejemplo:
<!ELEMENT nodo12 (#PCDATA)>
• Sólo otros elementos hijos. Se indica entre paréntesis las combinaciones posibles de los elementos hijos.
Por ejemplo:
<!ELEMENT raiz (#PCDATA | (nodo1*, nodo2+))>
En la lista de elementos hijos, el combinador coma (,) indica una secuencia de elementos seguidos. El
combinador asterisco(*)indica que el subelemento puede aparecer cero o varias veces. El combinador
(+)indica que el subelementodebe aparecer al menos una vez. Y el combinador interrogación (?) indica que el
elemento puede no aparecer o lo hará una sola vez.
El orden en el cual aparecen definidos los subelementos en <!ELEMENT > determina el orden en que deben
aparecer dentro de su elemento contenedor. Si queremos que el orden de los sub-elementos pueda ser
arbitrario podemos usar el combinador de barra vertical (|):
<!ELEMENT raiz (nodo1 | nodo2)>
Esto permitiría que fuese válidos los siguientes nodos raíz:
<raiz att1="1">
<nodo1></nodo1>
<nodo2></nodo2>
</raiz>
<raiz att1="1">
<nodo2></nodo2>
<nodo1></nodo1>
</raiz>
Se pueden realizar combinaciones entre los combinadores para crear estructuras XML complejas. Por
ejemplo, la siguiente definición:
<!ELEMENT raiz ((nodo1 | nodo2)?, nodo3+)>
Indicaría que el elemento raíz puede estar formado por una secuencia de eleemenos hijos que comience por
un elemento nodo1 o nodo2 o por ninguno de ellos, pero que después debe ir un elementos nodo3 que se
puede repetir varias veces seguidas.
1.3.3. Declaración de atributos.
Cada elemento debe declarar los atributos que lo forman mediante el contenedor <!ATTLIST >. Ese
contenedor puede declarar más de un atributo con la sintaxis:
<!ATTLIST elemento atributo1 tipo1 valorPorDefecto1 atributoN tipoN valorPorDefectoN>
El tipo de los atributos puede ser uno de los siguientes:
• CDATA, indica que el valor del atributo debe serinterpretado como un string.
•NMTOKEN o NMTOKENS, como CDATA, pero restringido a una serie de caracteres que puedan formar
un nombre XML válido (o una lista de ellos).

253
• ID, indicaque debe ser interpretado como un código de valor único para todo el documento.
•IDREF o IDREFS, referencia a un identificador único (o una lista de ellos) asociado como valor a un
atributo de tipo ID de algún elemento del documento.
•ENTITY o ENTITIES, entidad (o lista de ellas).
enumerados:
• Una lista de valores. Se puede establecer una enumaración con los posibles valores entre paréntesis y
separados por barras verticales. Por ejemplo:
<!ATTLIST mensaje prioridad (maxima | alta | normal | baja | minima) "normal">
En el valor por defecto podemos especificar una de las opciones siguientes:
•#REQUIRED, el atributo será obligatorio, y no toma ningún valor por defecto
•#IMPLIED, el atributo será opcional, pero si no aparece no toma ningún valor por defecto
•#FIXED "valor", el valor del atributo será fijo.
• Si se pone un valor por defecto, será el valor del atributo si éste no aparece.
1.3.4. Generación de DTD a partir de un documento XML.
El editor incorporado de NetBeans para documentos XML permite generar un DTD que valide el
documento actual.
Figura 6

Se genera un DTD que infiere su estructura del contenido actual del XML, por tanto deberemos modificarlo
para adaptarlo a la definición de documento deseada.
Una vez añadida la referencia al DTD en el documento XML podemos ejecutar la opción de menú «Validar
XML» para comprobar que el documento es válido:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ventas SYSTEM "ventas.dtd">
<ventas>
…………………..

254
1.4. Esquemas XML.
Los esquemas XML son una alternativa a los DTD para validar documentos XML. Tienen la ventaja de que
usan sintaxis de XML, permiten especificar los tipos de datos, y son extensibles. Se guardan en archivos con
extensión xsd.
1.4.1. Asociar esquemas a documentos XML.
En los documentos XML que se basen en un esquema, deberemos incluir una referencia al archivo .xsd. Por
ejemplo, en el siguiente documento se hace referencia a un esquema "documento.xsd":
<?xml version="1.0"?>
<documento
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns=' http://espacio.de.nombres'
xsi:schemaLocation=' http://espacio.de.nombres documento.xsd">
<persona id="10">
<nombre>Fulano Menganez</nombre>
<profesion>Profesor</profesion>
</persona>
</documento>
El esquema "documento.xsd"podría ser algo parecido a esto:
<?xml version="1.0"?>
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://espacio.de.nombres"
elementFormDefault="qualified">
<xsd:element name="documento">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="persona" >
<xsd:complexType>
<xsd:attribute name="id" type="xs:integer" use="required"/>
<xsd:sequence>
<xsd:element name="nombre" type="xsd:string"/>
<xsd:element name="profesion" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element >
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
El primer elemento del esquema, <xsd-schema>, le dice al analizador que esto es un esquema y no otro
documento XML cualquiera. El atributo xmlns:xsd referencia que es un esquema que cumple con la
normativa estándar establecida por el consorcio W3C. El atributo targetNamespace establece un nombre
de espacio de nombres para el esquema; la idea es que el espacio de nombres definido por un esquema sea
único, y por ello se suelen utilizar dominios de Internet para definirlos. El atributo elementFormDefault
indica si los elementos deben cualificarse o no para referenciar el espacio de nombres; en ese caso los
elementos en el documento XML deberán estar prefijados con una abreviatura asociada con el espacio de
nombres.
<?xml version="1.0"?>
<ns1:documento
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ns1="http://espacio.de.nombres"
xsi:schemaLocation="http://espacio.de.nombres documento.xsd">
<ns1:persona id="10">
<ns1:nombre>Fulano Menganez</ns1:nombre>
<ns1:profesion>Profesor</ns1:profesion>
</ns1:persona>
</ns1:documento>
El segundo elemento del esquema, <xsd:element>, nos permite definir los elementos XML.
255
1.4.2. Elementos de un esquema.
Los elementos utilizados en la creación de un esquema "proceden" del espacio de nombres
http://www.w3.org/2001/XMLSchema.
El elemento <schema> es el elemento raíz del documento en el que se define el esquema:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
</xsd:schema>
Un elemento simple es un elemento que sólo puede contener texto (cualquier tipo de dato), pero no a otros
elementos ni atributos. Para definir un elemento simple, se utiliza la sintaxis:
<xsd:element name="unNombre" type="unTipo" default="unValor" fixed="unValor" />
A continuación se muestra un ejemplo en su mínima expresión:
<xsd:element name="apellido" type="xs:string" />
Los tipos de datos más utilizados son: xsd:string, xsd:decimal, xsd:integer, xsd:boolean, xsd:date,
xsd:time.
Un elemento simple puede tener un valor por defecto (con el atributo default) y un valor "fijo" (con el
atributo fixed).
Los atributos se deben declarar de forma similar a los "elementos simples". (Si un elemento puede ir
acompañado de atributos, el elemento se deberá declarar como un elemento "complejo".)
Un atributo se declara de la siguiente forma:
<xsd:attribute name="unNombre" type="unTipo" use="required u optional" />
Los atributos también pueden tener valores por defecto (con default) y valores fijos (con fixed). Por defecto,
los atributos son opcionales; para indicar que sea obligatorio, se debe añadir a su declaración el atributo
use="required".
1.4.3. Restricciones.
Las facetas o restricciones permiten restringir el valor que se puede dar a un elemento o atributo XML.
Mediante restricciones podemos indicar que un valor debe estar comprendido en un rango determinado, debe
ser un valor de una lista de valores "cerrada", o debe ser mayor o menor que otro valor. Los tipos de facetas
son los siguientes:
• Valor comprendido en un rango. Se indica con los elementos xsd:minInclusive y xsd:maxInclusive.
Por ejemplo, acotamos una edad entre 0 y 100 años:
<xsd:element name="edad">
<xsd:simpleType>
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="0"/>
<xsd:maxInclusive value="100"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
También se pueden usar los elementos minExclusive y maxExclusive.
• El valor está restringido a un conjunto de valores posibles. Por ejemplo, acotamos los valores de un
elemento marca de coche a una lista:
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Audi"/>
<xsd:enumeration value="Golf"/>
</xsd:restriction>
• Restringir el valor de un elemento a una serie de caracteres. Por ejemplo, acotamos el valor de un
elemento inicial a dos letras, la primera sólo en minúsculas, y la segunda admite minúsculas y mayúsculas:
<xsd:restriction base="xsd:string">
<xsd:pattern value="[a-z][a-zA-Z]"/>
</xsd:restriction>
En el atributo value podemos usar un patrón con * para indicar combinaciones de cero o más caracteres,
{número} para indicar repetición del patrón precedente (p.e, [a-z]{3} indica 3 letras en minúsculas).
• Longitud de los valores de los elementos. Por ejemplo, un elemento de longitud 8 caracteres:
<xsd:restriction base="xsd:string">
<xsd:length value="8"/>
</xsd:restriction>

256
Los elementos length, minLength y maxLength permiten indicar el número exacto, mínimo y máximo
de caracteres que puede tener un valor de un elemento. El elemento totalDigits permite indicar el número
exacto de dígitos permitidos.
1.4.4. Elementos complejos.
Los elementos complejos son elementos que contienen a otros elementos hijos, o que tienen atributos. Se
suelen dividir en 4 tipos: elementos vacíos, elementos no vacíos con atributos, elementos con elementos hijos,
elementos con elementos hijos y con "texto" o valor propio (como el contenido mixto de las DTD).
Para definir elementos complejos se utiliza la siguiente sintaxis:
<xsd:element name="empleado">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="nombre" type="xsd:string"/>
<xsd:element name="apellidos" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
Podemos usar otra sintaxis para reutilizar la "definición" de los elementos hijos en varios elementos:
<xsd:element name="empleado" type="personainfo"/>
<xsd:element name="estudiante" type="personainfo"/>
<xsd:complexType name="personainfo">
<xsd:sequence>
<xsd:element name="nombre" type="xsd:string"/>
<xsd:element name="apellidos" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
En la declaración de elementos complejos, es posible utilizar un mecanismo de "herencia" para reutilizar o
extender elementos definidos con anterioridad (por ejemplo el tipo personainfo).
<xsd:element name="trabajador" type="extiendepersonainfo"/>
<xsd:complexType name=" extiendepersonainfo ">
<xsd:complexContent>
<xsd:extension base="personainfo">
<xsd:sequence>
<xsd:element name="direccion" type="xsd:string"/>
<xsd:element name="empresa" type="xsd:string"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
Para declarar un elemento vacío con atributos, se utilizará la siguiente sintaxis:
<xsd:element name="producto">
<xsd:complexType>
<xsd:attribute name="prodID" type="xsd:positiveInteger"/>
</xsd:complexType>
</xsd:element>
Para declarar un elemento no vacío (es decir, con un dato) con atributos, y sin elementos hijos, se utilizará la
siguiente sintaxis:
<xsd:element name="ciudad">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:integer">
<xsd:attribute name="pais" type="xsd:string" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
Para declarar un elemento con contenido "mixto", basta con añadir un atributo mixed al elemento
xsd:complexType:
<xsd:element name="carta">

257
<xsd:complexType mixed="true">
<xsd:sequence>
<xsd:element name="nombre" type="xsd:string"/>
<xsd:element name="pedidoID" type="xsd:positiveInteger"/>
<xsd:element name="fechaCompra" type="xsd:date"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
La declaración anterior permitiría un texto como el siguiente:
<carta>Estimado cliente: <nombre>Juan Pérez</nombre>. Su pedido número
<pedidoID>1032</pedidoID>
se enviará el día <fechaCompra>2001-07-13</fechaCompra>. </carta>
El elemento xsd:sequence indica que los elementos anidados en él deben aparecer en un orden
determinado. Los esquemas XML nos ofrecen otras alternativas, además de xsd:sequence, para indicar
cómo se deben tratar los elementos que aparecen anidados en un elemento complejo. Las opciones o
"indicadores" son: xsd:all y xsd:choice.
El indicador xsd:all indica que los elementos que contiene pueden aparecer en cualquier orden, pero como
máximo sólo una vez:
<xsd:element name="persona">
<xsd:complexType>
<xsd:all>
<xsd:element name="nombre" type="xsd:string"/>
<xsd:element name="apellidos" type="xsd:string"/>
</xsd:all>
</xsd:complexType>
</xsd:element>
El indicador xsd:choice indica que puede aparecer sólo uno de los elementos que contiene:
<xsd:element name="propietario">
<xsd:complexType>
<xsd:choice>
<xsd:element name="nif" type="xsd:string"/>
<xsd:element name="cif" type="xsd:string"/>
</xsd:choice>
</xsd:complexType>
</xsd:element>
Los atributos minOccurs y maxOccurs se utilizan para indicar el número máximo y mínimo de veces que
puede aparecer un elemento hijo de un elemento complejo. El atributo maxOccurs puede tomar el valor
"unbounded", que indica que no existe ningún límite.
<xsd:element name="nombreHijo" type="xsd:string" minOccurs="0" maxOccurs="10"/>
1.4.5. Editores de esquemas en NetBeans.
Dada la complejidad de la sintaxis de los esquemas XML, el entorno NetBeans ofrece un editor muy
completo para crear esquemas de forma gráfica. Pero es posible que este editor gráfico no esté disponible en
la instalación estándar de NetBeans.
En la página http://plugins.netbeans.org/PluginPortal/ se pueden descargar complementos de XML que
incluyen nuevas plantillas y editores.

258
Figura 7

En este caso veremos cómo instalar el complemento XMLTools4NetBeans para la versión 7.1 o posterior.
Primero debemos descargar el fichero .zip y descomprimirlo. Este fichero está compuesto por archivos .nbm
que deberemos instalar en NetBeans. Los pasos a seguir son los siguientes:
1) Ejecutar el menú «Herramientas|Complementos» y pulsar en la pestaña «Descargas».
2) Pulsar el botón «Añadir complementos» y localizar los fichero .nbm. Hay que seleccionar todos los
ficheros y aceptar.
3) En el panel izquierdo aparacerán marcados todos los complementos disponibles. Podemos instalarlos
todos o aquellos que nos interesen. En este caso selecconaremos al menos los relacionados con esquemas y
pulsaremos el botón «Instalar».
4) Hay que aceptar y confirmar las instalaciones.
5) Una vez acabada la instalación hay que cerrar el cuadro de diálogo de complementos. Las nuevas
plantillas estarán disponibles en la categoría «XML», y si abrimos un documento .xsd veremos que el editor
incorpora tres opciones: Source, Schema y Design. El modo Design es el más cómodo para trabajar
gráficamente. Haciendo clic con el botón secundario del ratón sobre «Elements» podemos agregar el
elemento raíz <documento>:

259
Figura 8

Podemos repetir la operación sobre el nuevo nodo «documento» para añadir un elemento hijo <persona>:
Figura 9

260
Al añadir un elemento hijo a un nodo se creas automáticamente una secuencia. De modo parecido se pueden
añadir atributos a los elementos. Y podemos editar las propiedades de cada elementos y atributo con la
opción de menú contextual «Propiedades»
1.5. XPath, el lenguaje de consultas para XML.
XPath (XML Path Language) es un lenguaje de expresiones con el cual podemos seleccionar y hacer
referencia a texto, elementos, atributos y cualquier otra información contenida dentro de un fichero XML.
XPath es a su vez la base sobre la que se han especificado nuevas herramientas que aprovechan el tratamiento
de documentos XML, tales como XSTL, XPointer, XLink y XQL (el lenguaje que maneja los documentos
XML como si de una base de datos se tratase), etc.
1.5.1. Expresiones XPath.
La forma en que XPath selecciona partes del documento XML se basa en la representación arbórea que se
genera del documento en su formato DOM. De hecho, los "operadores" de que consta este lenguaje nos
recordarán la terminología que se utiliza a la hora de hablar de árboles en informática: raíz, hijo, ancestro,
descendiente, etc. Aunque unn caso especial de nodo son los nodos atributo. Un nodo puede tener tantos
atributos como desee, y para cada uno se le creará un nodo atributo. No obstante, dichos nodos atributo NO
se consideran como hijos suyos, sino más bien como etiquetas añadidas al nodo elemento.
Una ruta de localización es el tipo de expresión más importante que se puede especificar en la notación
XPath. La sintaxis de una ruta de localización es similar a la usada para las rutas de archivos y directorios.
Por ejemplo, la expresión:
/documento/persona/nombre
hace referencia a todos los elementos <nombre> que cuelguen directamente de cualquier elemento
<persona> que cuelgue del elemento raíz<documento>.
Una ruta de localización siempre tiene un punto de partida llamado nodo contexto. Viene a ser como el
directorio actual si nos referimos a un sistema de ficheros; y de esta forma podemos expresar localizaciones
relativas al nodo que se esté procesando actualmente. Si la expresión XPath comienza con una barra /,
siempre partiremos del nodo raíz del árbol, mientras que si no comienza por barra, el primer elemento
referenciado se entenderá que es hijo del nodo de contexto.
Referencia de los tipos de nodos mediante XPath:
• Nodo raíz. Se identifica por /. No se debe confundir el nodo raíz con el elemento raíz del documento.
• Nodo elemento. Cualquier elemento de un documento XML se convierte en un nodo elemento dentro
del árbol. Cada elemento tiene su nodo padre. El nodo padre de cualquier elemento es, a su vez, un
elemento, excepto el elemento raíz, cuyo padre es el nodo raíz. Podemos localizar cualquier nodo elemento
mediante una expresión XPath. Por ejemplo,
/documento/persona
localiza todos los elementos persana del documento XML.
• Nodo texto. Un nodo texto no tiene hijos, y representa un dato contenido en el documento en un
elemento final. Se representa con text(). Por ejemplo,
/documento/persona/nombre/text()
localiza todos los valores de Modelo del documento XML.
• Nodo atributo. Un atributo forma parte de un elemento y se distingue del nombre de un nodo mediante
el uso del símbolo @. Por ejemplo,
/documento/persona/@id
localiza el valor del atributo id de todos los elementos persona.
• Nodo test. Los nodos test son como funciones que nos van a ayudar a restringir la selección de una
expresión XPath.
- El nodo test * devuelve todos los nodos de tipo principal, pero no nodos de texto, comentarios o
instrucciones de proceso.
- El nodo test node() devuelve todos los nodos de todos los tipos.
- El nodo test text() devuelve cualquier nodo de tipo texto.
- El nodo test comment() devuelve cualquier tipo de comentario.
- El nodo test processing-instruction() devuelve cualquier tipo de instrucción de proceso.
Podemos utilizar predicados en las expresiones XPath para seleccionar determinados nodos y no todos a la
vez. Los predicados se incluyen dentro de la ruta de localización utilizando corchetes; como por ejemplo,
261
/documento/persona[nombre="Juan"]/@id
recupera los id's de personas cuyo nombre sea "Juan". Por su parte, la expresión,
/documento/persona[@id]/*
recupera todos los elementos hijo de aquellos elementos persona que tengan un atributo id.
Podemos combinar varios predicados con el operador and simplemente poniéndolos en secuencia. Por
ejemplo,
/documento/persona [nombre="Juan"] [@id]
o bien
/documento/persona [ (nombre="Juan") and (@id) ]
También se puede hacer uso de los operadores or y not.
Para seleccionar a todos los descendientes (y no sólo a los hijos) se utiliza la doble barra //. Por ejemplo,
/documento//persona
recupera todos los nodos <persona> y <nombre>.
Para seleccionar los ascendientes se utiliza ancestor:: . Por ejemplo,
//ancestor::pesona/ancestor::*
recupera todos los elementos que tienen entre sus descendientes un elemento <persona>.
Para hacer referencia al nodo contexto se utiliza el punto (.). Por ejemplo, para seleccionar todos los
elementos descendientes del nodo contexto se usaría la expresión:
.//*
Para hacer referencia al nodo padre se utilizan dos puntos seguidos (..). Por ejemplo, la expresión,
/documento/persona/nombre/../..
recupera el nodo <documento>.
1.5.2. Funciones XPath.
Además de lo visto podemos aplicar en las expresiones XPath varios tipos de funciones.
Las funciones de nodo permiten restringir el conjunto de nodos devueltos en una expresión XPath o aplicar
una conversión sobre los nodos devueltos:
• position(), selecciona un elemento por posición. Por ejemplo,
/documento/persona [position() = 2]
selecciona el segundo nodo persona.
• last(), selecciona el último elemento. Por ejemplo,
/documento/persona [not (position() = last())]
selecciona todas las personas menos la última. Estas funciones se pueden usar con expresiones
matemáticas, como en el siguiente ejemplo,
/documento/persona [last()-1]
para seleccionar la penúltima persona.
• id(), selecciona elementos con un id dado. Sólo se podrá usar en aquellos ficheros XML que sean
validados por un DTD en el que se especifique que el atributo id es único.
• count(), devuelve el número de elementos seleccionados. Por ejemplo,
/documento [count(persona)>1]
retorna el nodo raíz si tiene más de una persona.
• local-name(), devuelve la parte local del nombre de un nodo cuando se utilizan espacios de nombres.
Por ejemplo,
count(/documento/* [local-name()="persona"]
retorna todos los nodos persona hijos del nodo raíz documento.
• name(), devuelve el nombre completo de un elemento. Por ejemplo,
count(/documento/* [name()="persona"]
retorna todos los nodos persona hijos del nodo raíz documento.
• namespace-uri(), devuelve la URI del espacio de nombres del nodo. Por ejemplo, suponiendo que
usamos un espacio de nombres "namespace1",
count(/documento/* [name()="persona" and namespace-uri()="namespace1"]
retorna todos los nodos persona hijos del nodo raíz documento.
También se incluyen funciones para manipular string en las expresiones de condición.
• concat(arg1, arg2, …), devuelve la concatenación de sus argumentos.

262
• contains(cadena, subcadena), devuelve true si la primera cadena contiene la segunda; false en caso
contrario.
• normalize-space(cadena), quita espacios al comienzo y final de la cadena.
• starts-with(cadena, subcadena), devuelve true si la primera cadena comienza con la segunda, o false
en caso contrario.
• string(arg), convierte cualquier argumento a cadena.
• string-length(cadena), devuelve el número de caracteres de una cadena.
• substring(cadena, posicion_inicial, longitud), devuelve una subcadena.
• substring-after(cadena, subcadena), devuelve la subcadena que sigue tras otra subcadena.
• substring-before(cadena, subcadena), devuelve la subcadena anterior a otra subcadena.
• translate(cadena, subcadena, posicion), reemplaza una cadena por otra.
También se incluyen funciones numéricas en las expresiones de condición:
• ceiling(arg), devuelve el entero más pequeño y no menor que el argumento. Por ejemplo, ceiling(12.3)
devuelve 13.
• floor(arg), devuelve el entero más grande pero no mayor que el argumento. Por ejemplo, floor(12.3)
devuelve 12.
• number(arg), devuelve el argumento como un número. Por ejemplo, number('4') devuelve 4.
• round(arg), devuelve el entero más próximo al argumento. Por ejemplo, round(12.3) devuelve 12.
• sum(expresión), devuelve la suma resultante de sumar el conjunto de nodos que pasamos como
parámetro. Por ejemplo,
/documento/persona [sum(id) < 10]
retorna los nodos persona cuya suma de atributos id sea menor que 10.
También se incluyen funciones lógicas:
• boolean(arg), convierte su argumento a un valor booleano.
• false(), devuelve false.
• true(), devuelve true.
• not(argumento), devuelve true si el argumento es false y viceversa.
Las funciones count() y sum() también se puede utilizar como funciones de agregado. Es decir, permiten
evaluar los resultado de la consulta. Por ejemplo, podemos saber cuántos nodos <persona> existen con:
count(/documento/persona)
1.6. Hojas de estilo de transformación.
XSLT (XSL Transformations), definido por el grupo de trabajo XSL de la W3C, describe un lenguaje para
transformar documentos XML en otros documentos XML o en otros formatos. Para realizar la
transformación, normalmente necesitamos suministrar una hoja de estilo, que está escrita en "XML Stylesheet
Language" (XSL). Un proceso transformador usará como datos de entrada la hoja de estilo y un documento
XML y generará como salida un nuevo documento:
Figura 10

La hoja de estilo XSL específica cómo se mostrarán los datos XML origen. El proceso transformador XSLT
usa las instrucciones de formateo de la hoja de estilo para realizar la transformación basada en los elementos
del fichero XML origen. El documento convertido puede ser otro documento XML o un documento en otro
formato, como HTML.

263
1.6.1. Estructura de una hoja de estilo XSL.
Si creamos un fichero de hoja de estilo con NetBeans obtentremos el siguiente contenido inicial:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<head>
<title></title>
</head>
<body>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Al ser un documento XML comienza por la instrucción de proceso habitual. El nodo raíz es
<xsl:stylesheet>, donde se declara el esquema que valida la sintaxis de las etiquetas XSL. Aquellas etiquetas
prefijadas con xsl serán interpretadas por el proceso transformador como instrucciones.
La instrucción <xsl:output> determina el formato de salida del documento que se generará. En la plantilla
anterior el formato especificado es html, pero también puede ser xml y text.
El esqueleto de una hoja XSL está organizado en "templates" (plantillas), que son partes de la hoja que se
"disparan" cuando encuentran una etiqueta que corresponda a lo que hay en su atributo match. El primer
<xsl:template match="/"> se corresponde al elemento raíz de un documento XML. Si queremos crear
una hoja de estilo para un documento XML de ventas como:
<?xml version="1.0" encoding="UTF-8"?>
<ventas>
<venta id="1">
<fecha>2014-03-12</fecha>
<precio>20</precio>
</venta>
<venta id="2">
<fecha>2014-04-02</fecha>
<precio>25</precio>
</venta>
</ventas>
Podemos dejar match="/", o podemos especificaremos ventas como nodo para que case la plantilla
principal del documento XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="ventas">
…………………………..
</xsl:template>
</xsl:stylesheet>
El cuerpo de este primer elemento template determina el resultado de la transformación. En la plantilla
generada por NetBeans se incluye la estructura de un página HTML simple. Todo lo que no sean
instrucciones con el prefijo xsl será enviado a la salida tal cual.
Se puede crear una plantilla por cada elemento del documento XML. Por ejemplo:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="ventas">
………………………………
</xsl:template>
<xsl:template match="venta">
</xsl:template>
<xsl:template match="fecha">
</xsl:template>

264
<xsl:template match="precio">
</xsl:template>
</xsl:stylesheet>
Pero las demás plantillas no se invocarán automáticamente. Deben ser invocadas desde la primera mediante
instrucciones <xsl:apply-templates />. Este elemento cede el control a los otros templates, es decir, trata
de aplicar todas las demás plantillas que haya en el documento, incluyendo el resultado en el punto de
inserción del elemento. Si no hay definidas más plantillas se aplica una plantilla por defecto para los
subelementos. La plantilla por defecto navega hasta los nodos finales y muestra su contenido.
En vez de la instrucción general<xsl:apply-templates /> podemos especificar que se aplique sólo una
plantilla determinada. Por ejemplo:
<xsl:apply-templates select="venta" />
Habrá ocasiones en que nos interese mostrar el valor de un elemento o atributo determinado, subordinado a
un elemento padre especificado. Para ello se utiliza la instrución<xsl:value-of select='' />, donde el valor
del atributo select debe ser una expresión XPath válida.
Si queremos mostrar el valor de un elemento fecha dentro de la plantilla de venta debemos usar:
<xsl:template match="venta">
<xsl:value-of select="fecha" />
</xsl:template>
También podemos establecer una ruta absoluta al elemento con:
<xsl:value-of select="/ventas/venta/fecha" />
Si lo que queremos mostrar es el valor de un atributos debemos prefijarlo con el símbolo @. Por ejemplo,
para mostrar el atributo id dentro de la plantilla del nodo venta debemos usar:
<xsl:template match="venta">
<xsl:value-of select="@id" />
</xsl:template>
1.6.2. Cómo probar hojas de estilo en NetBeans.
El editor de hojas de estilo de NetBeans ofrece un menú contextual con la opción de ejecutar un
transformador sobre la hoja de estilo. El menú contextual «Transformación XML» abre un cuadro de diálogo
que permite especificar el fichero XML origen y un destino. También podemos seleccionar el navegador por
defecto del sistema o un fichero como destino.

265
Figura 11

En este caso, tras aceptar los datos se mostrará en el navegador los datos de fecha y precio, puesto que las
plantillas por defecto devuelven sólo los nodos de texto hijos.
Figura 12

1.6.3. Referenciar otras hojas de estilo.


Una hoja de estilo puede aprovechar las declaraciones de otras hojas de estilo de dos maneras: usando la
instrucción xsl:import o la instrucción xsl:include. Ambas instrucciones aportan la misma funcionalidad.
En el atributo hrefde ambas instrucciones se asigna la URI que identifica la hoja de estilo a importar o incluir.
Por ejemplo, si tenemos una hoja de estilo llamada ventas_venta.xsl:
FICHERO ventas_venta.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="venta">
nodo venta
</xsl:template>
</xsl:stylesheet>

266
Podemos importar las plantillas y definiciones de esta hoja de estilo en otra:
FICHERO ventas.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:import href="ventas_venta.xsl" />
<xsl:template match="ventas">
<xsl:apply-templates />
</xsl:template>
</xsl:stylesheet>
Como resultado de transformar ventas.xsl se generará un documento de texto con:
nodo venta
nodo venta
1.6.4. Uso de claves para búsquedas.
La instrucción <xsl:key/> permite declarar una clave que posteriormente puede ser usada en la hoja de
estilo mediante la función key(). Por ejemplo, para el documento de ventas podemos definir una clave para
el atributo id del nodo venta:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:key name="id_match" match="venta" use="@id" />
Ahora podemos encontrar la venta de id igual a 1dentro de la plantilla principal:
<xsl:template match="ventas">
<xsl:for-each select= "key('id_match', '1')">
Fecha de venta: <xsl:value-of select="fecha" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Se define una clave llamada id_match que permitirá buscar aquellos elementos venta cuyo atributo id
tengan un valor determinado. La instrucción xsl:for-each permite iterar sobre todos los nodos venta
devuelto por key('id_match', '1'),que serán las ventas con id=1. Dentro de la instrucción xsl:for-each se
define un contexto de consulta de los nodos venta recuperados, por tanto se hace una consulta para recuperar
sus fechas.
1.6.5. Dando formato a números.
La instrucción <xsl:decimal-format /> permite definir los caracteres y símbolos que serán usados cuando
se conviertan números en cadenas con la función format-number(). Por ejemplo, podemos definir:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:decimal-format name= "euro" decimal-separator="," grouping-separator="." />
Ahora podemos mostrar un cantidad monetaria usando este formato:
<xsl:template match="ventas">
<xsl:for-each select="venta">
<xsl:value-of select="format-number(precio, '#.###,00', 'euro')" />
<xsl:text>&#xD;</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
La entidad &#xD; representa al caracter de salto de línea. Y por tanto, el resultado producido será:
20,00
25,00
1.6.6. Uso de parámetros y variables.
La instrucción <xsl:variable /> permite declarar una variable local (dentro de una plantilla) o global (al nivel
de la hoja de estilo). Podemos declarar una variable de dos formas:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

267
<xsl:output method="text"/>
<xsl:variable name="color" select=" 'rojo' " />
<xsl:variable name="nombre">Pedro</xsl:variable>
Y dentro de una plantilla podemos recuperar el valor de las variables de la siguiente forma:
<xsl:template match="ventas">
<xsl:copy-of select= "$color" />, <xsl:copy-of select= "$nombre" />
</xsl:template>
</xsl:stylesheet>
Por su parte, la instrucción <xsl:param />permite declarar un parámetro local (dentro de una plantilla) o
global (al nivel de la hoja de estilo). Los parámetros pueden ser pasados a la hoja de estilo por el proceso
transformador, o pueden ser pasados a una plantilla internamente. Por si acaso es conveniente asignarles un
valor por defecto.
Por ejemplo, podemos crear un parámetro color cuyo valor será pasado externamente a la hoja de estilos:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:param name= "color" select="valor por defecto" />
Dentro de una plantilla podemos recuperar el valor del parámetro de la siguiente forma:
<xsl:template match="ventas">
<xsl:value-of select= "$color" />
</xsl:template>
</xsl:stylesheet>
Si queremos usar parámetros internamente, podemos pasarlos como argumento a una plantilla:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="ventas">
<xsl:call-template name="venta">
<xsl:with-param name="color" />
</xsl:call-template>
</xsl:template>
<xsl:template match="venta">
<xsl:value-of select= "$color" />
</xsl:template>
</xsl:stylesheet>
1.6.7. Instrucciones de programación en las plantillas.
Dentro de una plantilla podemos usar las siguientes intrucciones de programación:
xsl:if, permite evaluar una condición.
xsl:choose, permite evaluar varias condiciones.
xsl:for-each, permite iterar sobre los resultados de una consulta.
xsl:value-of, permite mostrar el resultado de una consulta que devuelve un único valor.
Estas instrucciones XSL son similares a las etiquetas JSTL c:if, c:choose, c:forEach y c:out. Gracias al uso
de estas instrucciones podemos realizar todo el proceso de salida de la hoja de estilo en la plantilla raíz. Por
ejemplo, podemos generar una página HTML con los datos de ventas en formato de tabla:
Figura 13

Para ello se puede crear el siguiente documento XSL:

268
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="ventas">
<html>
<body>
<table border="1">
<tr><th>ID</th><th>FECHA</th><th>PRECIO</th></tr>
<xsl:for-each select="venta">
<tr>
<td><xsl:value-of select="@id" /></td>
<td><xsl:value-of select="fecha" /></td>
<td><xsl:value-of select="precio" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
La particularidad de las instrucciones de programación es que se evalúan sobre una consulta XPath, y dentro
de su cuerpo se crea un contexto de consulta relativo. La instrucción xsl:for-each de este ejemplo itera sobre
los nodos venta. Dentro de su cuerpo podemos recuperar directamente los datos de cada nodo venta
mediante consultas relativas usando la instrucción xsl:value-of.
Dentro de un bucle xsl:for-each podemos provocar la ordenación de los resultados de la consulta mediante
la instrucción xsl:sort. Por ejemplo, podemos iterar sobre las ventas ordenadas por precio en orden
descendente:
<xsl:for-each select="venta">
<xsl:sort select="precio" order="descending" />
<xsl:value-of select="precio" />,
</xsl:for-each>
El atributo order puede tomar el valor "ascending" (por defecto) o "descending". Otro atributo de esta
instrucción es case-order, que puede tomar el valor "upper-first" o "lower-first" para especificar si al
ordenar van primero las letras mayúsculas o las minúsculas.
El siguiente código muestra un ejemplo de uso de la instrucción xsl:choose:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="ventas">
<xsl:for-each select="venta">
<xsl:choose>
<xsl:when test="precio > 20" >
La venta <xsl:value-of select="@id" /> es cara<xsl:text>&#xD;</xsl:text>
</xsl:when>
<xsl:otherwise>
La venta <xsl:value-of select="@id" /> es barata<xsl:text>&#xD;</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Esta hoja de estilo provocaría la salida:
La venta 1 es barata
La venta 2 es cara
1.6.8. Transformación a documentos XML.
A veces la estructura de un documento XML no es la más adecuada para ser procesada por nuestra
aplicación. Algunas tecnologías que procesan documentos XML puede que no reconozcan bien los atributos,
o que incluso sólo reconozcan bien los atributos y no los nodos hijos.

269
En todo caso, las hojas de estilo permiten generar un documento XML a partir de otro. Para ello
proporcionan instrucciones para generar elementos con sus atributos. Estas instrucciones son:
• <xsl:processing-instruction />
Escribe una instrucción de proceso en la salida. Por ejemplo:
<xsl:processing-instruction name="xml-stylesheet">
href="style.css" type="text/css"
</xsl:processing-instruction>
• <xsl:copy />
Crea un copia del nodo actual en la salida. Por ejemplo:
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
Esta instrucción genera en la salida la misma estructura de nodos que la entrada.
• <xsl:copy-of />
Crea un copia del nodo indicado en la salida: Por ejemplo:
<xsl:copy-of select="venta" />
• <xsl:element />
Crea un nodo de tipo elemento en la salida. Por ejemplo:
<xsl:element name="pedido">
Contenido del pedido
</xsl:element>
Esta instrucción genera el elemento: <pedido>Contenido del pedido</pedido>
• <xsl:attribute />
Añade un atributo a un elemento, pudiendo reemplazar los atributos existentes con nombres
equivalentes. Por ejemplo:
<pared>
<xsl:attribute name= "color">rojo</xsl:attribute>
</pared>
Genera la salida: <pared color="rojo"/>
• <xsl:comment />
Permite crear un nodo de comentario en el resultado. Por ejemplo:
<xsl:comment>Esto es un comentario</xsl:comment>
Genera la salida:
<!-- Esto es un comentario -->
• <xsl:text />
Se usa para escribir texto literal en la salida. Por ejemplo:
<xsl:text disable-output-escaping="no">Un texto &lt;literal&gt;&#xD;Seguido de otra línea</xsl:text>
Genera la salida:
Un texto <literal>
Seguido de otra línea
El atributo "disable-output-escaping" cuando se asigna a "yes" indica que caracteres especiales (como
"<") se escriban tal cual, y cuando se asigna a "no" los codifica (como "&lt;").
• <xsl:message />
Escribe un mensaje en la salida. Se utiliza normalmente para informar de errores. Por ejemplo:
<xsl:message terminate="yes">
Se ha producido un error
</xsl:message>
El atributo "terminate" establece que se pare el procesamiento.
• <xsl:number />
Permite determinar la posición del nodo actual en el documento de origen. Por ejemplo:
<xsl:number value="position()" format="1" />
Donde el atributo format admite los formatos "1", "01", "a", "A", "i", "I".
Como ejemplo haremos la siguiente transformación:

270
Figura 14

La hoja de estilo que produce esta transformación es la siguiente:


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml"/>
<xsl:template match="ventas">
<xsl:element name="ventas">
<xsl:for-each select="venta">
<xsl:element name="venta">
<xsl:attribute name="id"><xsl:value-of select="@id" /></xsl:attribute>
<xsl:attribute name="fecha"><xsl:value-of select="fecha" /></xsl:attribute>
<xsl:attribute name="precio"><xsl:value-of select="precio" /></xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

2. JAXP: APIs de Java para XML


Los API de Java para Procesar XML (JAXP) permiten procesar datos XML utilizando instrucciones de Java.
JAXP proporciona un API simple (SAX) para analizar documentos XML, y un Modelo de Objetos de
Documento (DOM) para crear una representación en memoria de los nodos de un documento. JAXP
también soporta el estándar XSLT, que permite aplicar transformaciones a un documento XML. Desde la
versión 1.4 también proporcona soporte para el estándar Streaming para XML (StAX).
2.1. El API SAX.
SAX (Simple API for XML) define un mecanismo para acceder a documentos XML basado en una lectura
secuencial y un proceso basado en eventos. Es el mecanismo más rápido y eficiente para realizar consultas a
un documento, pero no está pensado para realizar cambios.
2.1.1. La interfaz «ContentHandler».
El API SAX proporciona un analizador de documentos XML basado en eventos. Estar "basado en eventos"
significa que el analizador lee un documento XML desde el principio hasta el final, y cada vez que reconoce
una sintaxis de construcción, se lo notifica a la aplicación que lo está ejecutando.
El analizador SAX pasa notificaciones a la aplicación llamando a los métodos de la
interfazorg.xml.sax.ContentHandler. Por ejemplo, cuando el analizador encuentra un símbolo <, llama al
método startElement(); cuando encuentra caracteres de datos, llama al método characters(); y cuando
encuentra un símbolo </, llama al método endElement(). El problema es que entre las llamadas a estos
métodos no se conserva el estado de lectura; es decir, cuando se lee un nodo de texto con characters() no
tenemos la información de quién es su elemento padre, y lo mismo ocurre con la lectura de cualquier otro
elemento del documento. Por tanto, el programador deberá crera una clase que implemente ContentHandler
y proprocionarle mecanismo para conservar el estado entre llamadas.
Para ilustrar este mecanismo, veamos los métodos de la interfaz ContentHandler que son invocados por
cada elemento de un documento XML:

271
startDocument()
<?xml version="1.0"?>
<documento> startElement("documento")
<persona id="10"> startElement("persona","id=10")
<nombre> startElement("nombre")
Fulano Menganez Characters("Fulano Meganez")
</nombre> endElement("nombre")
</persona> endElement("persona")
</documento> endElement("documento")
endDocument()
La lista siguiente hace una breve descripción del propósito de cada uno de los métodos de la interfaz
ContentHandler.
• void setDocumentLocator(Locator locator)
Este método recibe una instancia de la clase Locator que contendrá la información referente a la
posición del documento donde sucede un evento. Un objeto org.xml.sax.Locator proporciona
básicamente un método getLineNumber() para saber la línea que se está analizando y un método
getColumnNumber() para saber la columna del elemento analizado.
• void startDocument()
El evento que se produce al comenzar a procesar un documento XML.
• void endDocument()
El evento que se produce al finalizar el documento XML.
• void processingInstruction(String target, String data)
Este evento se produce cuando se encuentra una instrucción de proceso de XML.
• void startPrefixMapping(String prefix, String uri)
Indica el comienzo de un prefijo de un espacio de nombre (Namespace). Los parámetros de éste método
son el prefijo del espacio de nombres y su dirección URI asociada.
• void endPrefixMapping(String prefix)
Indica cuándo se ha terminado la petición de inicio del prefijo del espacio de nombres.
• void startElement(String namespaceURI, String localName, String rawName, Attributes atts)
Este método se ejecuta cuando empieza un elemento del documento XML. Los argumentos que recibe
son la dirección URI del espacio de nombres asociado al elemento, el nombre del elemento (o etiqueta)
sin el prefijo del espacio de nombres, el nombre del elemento en la versión 1.0 de la especificación de
XML, y los atributos que contiene la etiqueta en forma de una instancia de la clase Attributes.
• void endElement(String namespaceURI, String localName, String rawName)
Se produce al terminar un elemento. El significado de los atributos es el mismo que en startElement().
• void characters(char[] ch, int start, int length)
Este método consigue el valor del último elemento analizado, es decir, el contenido entre las etiquetas de
inicio y final del elemento. Los parámetros que recibe son un array de caracteres, así como la indicación
del inicio y la longitud del elemento. Con estos tres parámetros se puede crear un string:
String contenido = new String(ch, start, length);
• void ignorableWhitespace(char[] ch, int start, int length)
Este método indica los espacios en blanco que pueden ser ignorados en el documento, pero
normalmente solo se utiliza cuando se valida el documento durante el procesamiento del fichero. Los
parámetros tienes el mismo significado que en el método anterior, characters().
• void skippedEntity(String name)
Este método indica que una entidad externa se ha ignorado al procesar el fichero. Recibe como
parámetros el nombre de dicha entidad.
2.1.2. Implementación de «ContentHandler».
Cuando utilizamos SAX para realizar una consulta buscaremos por elementos determinados del documento, y
por tanto no será necesario implementar todos los métodos que ofrece la interfaz ContentHandler. En estos
casos es más cómodo extenser la clase org.xm.sax.DefaultHandler, la cual ofrece una implementación por
defecto de todos los métodos de ContentHandler.
Por ejemplo, si utilizamos como documento XML el siguiente:

272
Fichero «documento.xml»
<?xml version="1.0"?>
<documento>
<persona id="10">
<nombre>Fulano Menganez</nombre>
</persona>
<persona id="12">
<nombre>Mario López</nombre>
</persona>
</documento>
Y queremos contar cuántos nodos <persona> contiene, nos bastará con procesar el método
startElement(). Crearemos por tanto la siguiente implementación:
package xml;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class DocumentoHandler extends DefaultHandler {
……………………
}
Cada implementación de ContentHandler debe procesar una consulta determinada. En este caso, para contar
el número de nodos <persona> usaremos un contador y un método getter que permita retornarlo:
package xml;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class DocumentoHandler extends DefaultHandler {
private int contador;
public int getContador() {
return contador;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (qName.equals("persona"))
contador++;
}
}
2.1.3. Parseando el documento XML.
Los objetos ContentHandler no realizan el proceso de lectura del documento XML, sólo se encargan de
analizarlo. Es la clase SAXParser quien se encarga de leer un documento, parsearlo y utilizar un objeto
ContentHandler para analizarlo.
Las tecnologías JAXP aplican el patrón de diseño Factory, por ello deberemos comenzar creando un
fabricador de objetos SAXParser:
javax.xml.parsers.SAXParserFactory factory = javax.xml.parsers.SAXParserFactory.newInstance();
javax.xml.parsers.SAXParser saxParser = factory.newSAXParser();
La clase SAXParser proporciona el método parse() para realizar todo el proceso de parseado y análisis. Este
método requiere el documento a parsear y una instancia de un ContentHandler:
DocumentoHandler handler = new DocumentoHandler();
saxParser.parse(application.getRealPath("documento.xml"), handler);
System.out.println("Hay " +handler.getContador() + " personas");
El resultado de ejecutar este código será:
Hay 2 personas
Configuración del fabricador.
La clase SAXParseFactory crea parseadores que por defecto no validan el documento XML, pero podemos
activar esta validación. Para que el documento se valide debe estar asociado a un DTD o una esquema XML.
Podemos configurar el fabricador SAXParserFactory para que admita validación de la siguiente manera:
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(true);

273
2.1.4. Manejando errores.
El parserador puede generar tres tipos de errores: un error fatal, un error y un aviso. Cuando ocurre un error
fatal el parseador no puede continuar y genera una excepción que es transmitida a través del método parse().
Los otros tipos de errores no producen una excepción, y si queremos recibir notificaciones sobre estos
errores debemos gestionarlos mediante código. Para ello, la clase DefalultHandler proporciona tres métodos
que podemos reescribir: fatalError(), error() y warning().
Cuando se produce un error fatal se pasa al método fatalError() de la clase DefaultHandler, el cual lo
relanza por defecto.
Un error no fatal se produce cuando un documento XML falla al ser validado. Si el parseador encuentra que
un documento no es válido, entonces genera un evento de error. Este error es pasado al método error() de la
clase DefaultHandler.
Se produce un aviso normalmente asociado a la presencia de un DTD o un esquema. Por ejemplo, si un
elemento es definido dos veces en un DTD se produce un aviso. En este caso el aviso es pasado al método
warning() de la clase DefaultHandler.
A continuación se gestionan todos los tipos de errores en la clase DocumentoHandler. Los errores fatales se
relanzan, y los demás se muestran en la salida estándar de error:
package xml;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class DocumentoHandler extends DefaultHandler {
…………………….
@Override
public void fatalError(SAXParseException e) throws SAXException {
throw e;
}
@Override
public void error(SAXParseException e) throws SAXException {
System.err.println(e);
}
@Override
public void warning(SAXParseException e) throws SAXException {
System.err.println(e);
}
}
2.2. El API DOM.
El API DOM es un conjunto de interfaces para construir una representación de objeto, en forma de árbol, de
un documento XML analizado.
Figura 15

Una vez que hemos construido el DOM, podemos manipularlo con métodos DOM como insert() y
remove(), igual que manipularíamos cualquier otra estructura de datos en forma de árbol. Así, al contrario
que un analizador SAX, un analizador DOM permite acceso aleatorio a piezas de datos particulares de un
documento XML. Otra diferencia es que con un analizador SAX sólo podemos leer un documento XML,

274
mientras que con un analizador DOM podemos construir una representación objeto del documento y
manipularlo en memoria, añadiendo un nuevo elemento o eliminando uno existente.
2.2.1. Cómo convertir un archivo XML en un DOM XML.
En el capítulo anterior usamos un analizador SAX para buscar sólo unos datos concretos en un documento.
Usando un analizador DOM hubiéramos tenido que tener el modelo del objeto del documento completo en
memoria, los que generalmente es menos eficiente para búsquedas que implican unos pocos elementos,
especialmente si el documento es largo. Pero DOM cubre la carencia de SAX para modificar documento.
El primer paso para trabajar con la tecnología DOM es crear un fabricador y a partir de él obtener un objeto
DocumentBuilder:
javax.xml.parsers.DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
javax.xml.parsers.DocumentBuilder builder = factory.newDocumentBuilder();
La clase javax.xml.parsers.DocumentBuilder proporciona métodos parse() para leer y analizar un
documento XML, y retornar un objeto org.w3c.dom.Document que lo representa como un árbol DOM.
org.w3c.dom.Document document = builder.parse("documento.xml");
2.2.2. Manejando errores.
El parseador DOM, al igual que el parseador SAX utiliza un modelo de gestión de errores que dinstingue
entre errores fatales, errores y avisos. Para gestionar estos errores podemos crear una clase que implemente la
interfaz org.xml.sax.ErrorHandler:
package xml;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
public class MiErrorHandler implements ErrorHandler {
@Override
public void warning(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void error(SAXParseException exception) throws SAXException {
System.err.println(exception);
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
System.err.println(exception);
}
}
Y asociar una instancia de esta clase al objeto DocumentBuilder.
javax.xml.parsers.DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true);
javax.xml.parsers.DocumentBuilder builder = factory.newDocumentBuilder();
builder.setErrorHandler(new MiErrorHandler());
Si queremos que se detecten errores de validación debermos habilita esta opción en el fabricador.
2.2.3. La interfaz «Document».
La interfaz org.w3c.dom.Document representa un documento XML o HTML completo en formato de
árbol DOM. Como hemos visto, se puede obtener una instancia mediante el método parse() de un objeto
javax.xml.parsers.DocumentBuilder.
Cada uno de los elementos, nodos de texto, comentarios, instrucciones de proceso, etc. de un documento
debe existir dentro del contexto de un Document. Además esta interfaz contiene los métodos generadores
necesarios para crear estos objetos.
Algunos de los métodos de esta interfaz son:
• DocumentType getDoctype()
Retorna el tipo de documento. Para documentos sin una declaración de tipo de documento retorna null.
• Element getDocumentElement()
Retorna el elemento raíz del documento.
• Element createElement(String tagName)

275
Crea un elemento del tipo especificado. Por ejemplo, podemos crear un nodo persona con la siguiente
instrucción:
org.w3c.dom.Node nodoPersona = document.createElement("persona");
• Text createTextNode(String data)
Crea un nodo de texto con el contenido especificado. Por ejemplo:
org.w3c.dom.Text texto = document.createTextNode("Juan Pérez");
• Comment createComment(String data)
Crea un nodo de comentario con el contenido especificado.Por ejemplo:
org.w3c.dom.Comment coment = document.createComment("Este modelo ... ");
• Attr createAttribute(String name)
Crea un elemento atributo con el nombre especificado. El nuevo atributo puede ser asignado a un
elemento usando el método setAttributeNode(). Por ejemplo, para crear un atributo id, de un elemento
persona:
org.w3c.dom.Attr atributo = document.createAttribute("id");
atributo.setValue("3");
• NodeList getElementsByTagName(String tagname)
Retorna una lista de nodos con todos los elementos del documento que tengan el nombre especificado.
Por ejemplo, para obtener la lista de nodos persona:
org.w3c.dom.NodeList list = document.getElementsByTagName("persona");
• void normalizeDocument()
Este método normaliza el documento, reconstruyendo el árbol DOM.
2.2.4. La interfaz «Node».
La clase org.w3c.dom.Node representa un nodo de un documento DOM, y extiende a
org.w3c.dom.Element. Todas las entidades de un documento XML, incluido Document extiende a Node.
Un nodo puede tener hijos y una colección de atributos. En un documento XML se distinguen los siguientes
tipos de nodo, que pueden ser obtenidos por el método Nodo.getNodeType():
Tipo de nodo Constante Valor
Elemento Node.ELEMENT_NODE 1
Atributo Node.ATTRIBUTE_NODE 2
Nodo de texto Node.TEXT_NODE 3
<![CDATA]> Node.CDATA_SECTION_NODE 4
Referencia de entidad Node.ENTITY_REFERENCE_NODE 5
<!ENTITY > Node.ENTITY_NODE 6
Instrucción XML Node.PROCESSING_INSTRUCTION_NODE 7
Comentario Node.COMMENT_NODE 8
Elemento raíz Node.DOCUMENT_NODE 9
<!DOCTYPE> Node.DOCUMENT_TYPE_NODE 10
Fragmento Node.DOCUMENT_FRAGMENT_NODE 11
Notación Node.NOTATION_NODE 12

El método Node.getNodeName() recupera el nombre del nodo, pero algunos tipos de nodos tienen un
nombre predefinido y para otros depende de su asignación en el documento. El método
Node.getNodeValue() retorna el valor del nodo, pero el valor de un nodo depende de su tipo.
En la siguiente tabla se especifican los nombres y valores de los diversos tipos de nodos:
Tipo de nodo Nombre Valor
Atributo Nombre del atributo El valor del atributo.
Sección CDATA "#cdata-section" Contenido de la sección.
Comentario "#comment" Contenido del comentario
Documento "#document" null
Fragmento "#document-fragment" null
Elemento Nombre del elemento null
<!DOCTYPE> Nombre del tipo null
Entidad Nombre de la entidad null

276
Notation Nombre de la notación null
Instruction XML El identificador Los datos de la instrucción
Nodo de texto "#text" Contenido del nodo
Algunos métodos de la clase Node son:
• Node getParentNode()
Retorna el nodo padre. Todos los nodos, excepto los atributos, documento, fragmento, entidades y
notaciones tienen un padre.
• NodeList getChildNodes()
Retorna una colección con todos los nodos hijos. Si no hay hijos retorna una colección vacía.
• Node getFirstChild()
Retorna el primer nodo hijo. Si no tiene nodos hijos retorna null.
• Node getLastChild()
Retorna el último nodo hijo. Si no tiene nodos hijos retorna null.
• Node getPreviousSibling()
Retorna el nodo inmediatamente precedente en el mismo nivel. Si no hay más nodos retorna null.
• Node getNextSibling()
Retorna el nodo inmediatamente anterior en el mismo nivel. Si no hay más nodos retorna null.
• NamedNodeMap getAttributes()
Retorna una colección con los atributos del nodo, o null si no existen.
• Document getOwnerDocument()
Retorna el objeto documento que contiene al nodo. Si el nodo es un Document o DocumentType
entonces retorna null.
• Node insertBefore(Node newChild, Node refChild)
Inserta un nuevo nodo hijo newChild antes del nodo existente refChild. Si refChild es null, inserta
newChild al final de la lista de hijos.
• Node replaceChild(Node newChild, Node oldChild)
Reemplaza el nodo hijo oldChild con un nuevo nodo newChid en la lista de hijos, y retorna el nuevo
nodo.
• Node removeChild(Node oldChild)
Elimina el nodo hijo indicado y retorna una referencia al mismo o null si no está en la lista.
• Node appendChild(Node newChild)
Añade el nodo newChild al final de la lista de hijos.
• boolean hasChildNodes()
Indica si hay nodos hijos.
• boolean hasAttributes()
Indica si hay atributos. Sólo se aplica sobre elementos.
• String getTextContent()
Retorna el texto contenido en el nodo y sus descendientes.
• void setTextContent(String textContent)
Asigna el contenido del nodo según su tipo.
Si queremos recorrer sólo los nodos hijos que sean elementos deberemos verificar el tipo del nodo hijo. Hay
que tener en cuenta que al construir el árbol DOM, los saltos de línea del documento XML pueden ser
convertidos en nodos de texto.
2.2.5. Cómo recorrer un árbol DOM.
Supongamos que en el fichero documento.xml queremos modificar el id de la persona de nombre "Mario
López" al valor "100":
<?xml version="1.0"?>
<documento>
<persona id="10">
<nombre>Fulano Menganez</nombre>
</persona>

277
<persona id="12">
<nombre>Mario López</nombre>
</persona>
</documento>
Una vez que hemos obtenido el objeto Document que representa el árbol DOM del fichero XML, podemos
empezar a recorrerlo desde el nodo raíz <documento>:
Node nodoRaiz = document.getDocumentElement();
Podemos explorar los nodos hijos del nodo raíz, que serán los nodos <persona>, más aquellos que
representan los nodos de espacios no importantes. Normalmente tendremos que realizar las siguientes
comprobaciones:
NodeList hijos = nodoRaiz.getChildNodes();
for (int i=0; i < hijos.getLength(); i++) {
if (hijos.item(i).getNodeType()==Node.ELEMENT_NODE &&
hijos.item(i).getNodeName().equals("persona")) {
// seguimos procesando
}
}
Siempre que recorramos los nodos hijos de un nodo, lo habitual será evaluar su tipo y/o su nombre para
asegurarnos de acceder a los nodos deseados. Desde los nodos <persona> podemos acceder a los nodos
hijos <nombre> y evaluar su valor hasta encontrar el valor "Mario López". En ese momento habremos
acabado la búsqueda y podremos acceder al nodo padre para modificar su atributo:
Pero antes de continuar desarrollando el código, podemos ver que este modo de recorrer el árbol DOM es
ineficiente y repetitivo. Nos obliga a descender por los nodos hijos, evaluar el tipo de los nodos hijos y
después volver a subir hasta los nodos padre.
Se puede utilizar otro tipo de recorrido más eficiente. El tipo Document proporicona el método
getElementByTagName() que permite recuperar todos los nodos con un nombre de etiqueta determinado.
Como debemos filtrar por el nombre de persona, podemos recuperar todos los nodos <nombre>, iterar
sobre ellos y evaluar el valor de su nodo de texto. Cuando hayamos encontrado el nodo buscado podemos
subir hasta su nodo padre y modificar su atributo id:
NodeList nombres = document.getElementsByTagName("nombre");
for(int i=0; i<nombres.getLength();i++)
if (nombres.item(i).getFirstChild().getNodeValue().equals("Mario López")) {
Node persona= nombres.item(i).getParentNode();
persona.getAttributes().getNamedItem("id").setNodeValue("100");
break;
}
2.2.6. Cómo añadir y eliminar nuevos nodos.
Cada objeto Node proporciona métodos para acceder a, añadir y eliminar nodos hijos. Por ejemplo, vamos a
añadir un elemento <direccion /> al primer nodo <persona> del documento:
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse("documento.xml");
// Se obtiene el primer nodo persona
Node persona = doc.getElementsByTagName("persona").item(0);
// Se crea el nodo de texto con el contenido de la dirección
Text textoDireccion = doc.createTextNode("San Andrés, 34");
// Se crea el nodo direccion y se le añade su contenido
Element direccion = doc.createElement("direccion");
direccion.appendChild(textoDireccion);
// Se añade el nodo direccion al nodo persona
persona.appendChild(direccion);
…………………………
Los nuevos elementos deben crearse siempe usando los método create_() de la clase Document. Y
debemos tener en cuenta que el contenido de un elmento hoja es también un nodo de tipo texto.
Para eliminar un nodo hay que removerlo de la lista de nodos hijos de su padre. Por ejemplo, si queremos
eliminar el primer nodo persona obtenido en el código previo se ejecutaría:
persona.getParentNode().removeChild(persona);

278
2.2.7. Cómo modificar atributos.
Para modificar los atributos de un nodo hay que tener en cuenta que cada objeto Node tiene su pripia lista de
atributos.
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse("documento.xml");
// Se obtiene el primer nodo persona
Node persona = doc.getElementsByTagName("persona").item(0);
// Se crea el atributo y se asigna su valor
Attr atributo = doc.createAttribute("nif");
atributo.setNodeValue("1111111A");
// Se añade el atributo al elemento
persona.getAttributes().setNamedItem(atributo);
Cuando se añade un atributo a un nodo, si el atributo no existía previamente lo crea, si no lo sustituye.
Eliminar un atributo es tan simple como:
persona.getAttributes().removeNamedItem("nif");
2.2.8. Cómo guardar los cambios de un XML DOM.
Mediante la clase DocumentBuilder podemos leer un documento XML y obtener un Document. Podemos
usar el objeto Document para modificar el árbol DOM, pero ni la clase DocumentBuilder ni la clase
Document proporcionan un método que permita guardar los cambios realizados al fichero XML. Ni siquiera
ofrecen un método que obtenga la representación como string del documento.
Se puede utiliza la clase javax.xml.transform.Transformer para convertir un Document en su
representación como string.
Primero obtendremos un Transformer aplicando el patrón Factory:
javax.xml.transform.TransformerFactory.TransformerFactory tf = TransformerFactory.newInstance();
javax.xml.transform.TransformerFactory.Transformer transformer = tf.newTransformer();
La clase Transformer ofrece el método transform() para convertir un documento XML a otro formato. Como
primer argumento solicita un objeto javax.xml.transform.Source; se trata de una interfaz que permite
especificar un origen XML. Esta interfaz está implementada por las clases:
• javax.xml.transform.dom.DOMSource, la cual permite enlazar un objeto Node como origen XML.
• javax.xml.transform.stream.StreamSource, la cual permite enlazar un fichero o un stream como
origen XML.
Como segundo argumento solicita un objeto javax.xml.transform.Result; se trata tambiénde una interfaz
que permite especificar un destino. Esta interfaz está implementada por las clases:
• javax.xml.transform.dom.DOMResult, la cual permite enlazar un objeto Node como destino.
• javax.xml.transform.stream.StreamResult, la cual permite enlazar un fichero o un stream como
destino.
Teniendo en cuenta esto podemos utilizar el siguiente código para tomar como origen un objeto Document y
como destino un string mediante un StringWriter:
DOMSource domOrigen = new DOMSource(document);
StringWriter stringDestino = new StringWriter();
StreamResult result = new StreamResult(stringDestino);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(domOrigen, result);
String documento = stringDestino.toString();
Podemos usar el string generado en este código para escribirlo en un fichero:
try (PrintWriter destino = new PrintWriter("documento.xml") {
destino.print(documento);
}
O podemos enlazar el StreamResult directamente a un fichero para que el método transform() escriba el
texto resultante al fichero:
DOMSource domOrigen = new DOMSource(document);
StringWriter stringDestino = new StringWriter();
StreamResult result = new StreamResult(new File("documento.xml"));
TransformerFactory tf = TransformerFactory.newInstance();

279
Transformer transformer = tf.newTransformer();
transformer.transform(domOrigen, result);
2.3. El API Streaming para XML (StAX)
La técnologia StAX fue creada para poder analizar un documento XML mediante un iterador. Esto permite al
programador preguntar por un evento de lectura del documento XML y almacenar el estado de lectura. StAX
fue creado para suplir las limitaciones de los parseadores SAX y DOM.
Esta tecnología de Streaming hace uso de SAX para leer un documento XML, pero almacenando un
elemento leído en la memoria, de forma que podamos avanzar a través de los elementos realizando lecturas
secuenciales en ambos sentidos. En cada lectura podremos acceder al estado del elemento actual, incluidos
sus atributos.
StAX tiene la ventaja de que consume pocos recursos de memoria, y en ese sentido en algunos escenarios
supera a la tecnología DOM. Aunque el API SAX es má ligera, StAX tiene la ventaja de permitir recorrer y
escribir elementos.
El API StAX ofrece dos técnicas para recorrer un documento XML: usando un cursor o usando un iterador.
2.3.1. El API Cursor.
El API Cursor proporciona un cursor a través del cual podemos recorrer el documento desde el inicio hasta
el final. Este cursor puede apuntar a un único elemento a la vez, y siempre se mueve hacia adelante, nunca
hacia atrás. El API Cursor ofrece dos interfaces: XMLStreamReader y XMLStreamWriter.
La interfaz javax.xml.stream.XMLStreamReader incluye métodos accesores para recuperar toda la
información posible desde el modelo de información XML, incluyendo la codificación del documento,
nombre de los elementos, atributos, espacios de nombre, nodos de texto, comentarios, etc.
Por ejemplo, el siguiente código permtie obtener los id's de todos los elemnetos venta:
XMLInputFactory factory=XMLInputFactory.newFactory();
InputStream documento = FileInputStream("ventas.xml");
XMLStreamReader reader = factory.createXMLStreamReader(documento));
while (reader.hasNext()) {
if (reader.next() == XMLStreamReader.START_ELEMENT) {
if (reader.getLocalName().equals("venta")) {
String id = reader.getAttributeValue(0);
System.out.println("ID = " + id);
}
}
}
reader.close();
Primero se instancia un fabricador para crear objetos XMLStreamReader. Cada objeto XMLStreamReader
se crea asociado a un documento XML. El método createXMLStreamReader() permite conectar un
documento mediante un InputStream, un Reader o un Source.
Para iterar a través del cursor se hace uso del método hasNext(); este método devuelve true si se pueden
seguir leyendo elementos, y false si se ha llegado al final del documento. Para acceder a un elemento se utiliza
el método next(); este método retorna un valor entero que indica el tipo de elemento leído. Posibles valores
de retorno son:
Constante de XMLStreamConstants Valor Significado
START_ELEMENT 1 Es el inicio de un elemento.
END_ELEMENT 2 Es el final de un elemento.
PROCESSING_INSTRUCTION 3 Es una instrucción de proceso.
CHARACTERS 4 Es un elemento de texto.
COMMENT 5 Es un comentario
SPACE 6 Son caracteres de espacios blancos.
START_DOCUMENT 7 Es el inicio del documento.
END_DOCUMENT 8 Es el final del documento.
ENTITY_REFERENCE 9 Es una referencia de entidad.
ATTRIBUTE 10 Es un atributo.
CDATA 12 Es una sección CDATA.
NAMESPACE 13 Es una declaración de espacio de nombres.

280
NOTATION_DECLARATION 14 Es una notación.
ENTITY_DECLARATION 15 Es una declaración de entidad.
Cada lectura con next() puebla el objeto XMLStreamReader con la información del elemento actual, según
su tipo. En este ejemplo se comprueba que el nombre del elemento leído es "venta", y si es así se accede a su
primer atributo.
XMLStreamReader proporciona los siguientes métodos para recuperar información acerca del elemento
leído:
• String getLocalName(), retorna el nombre local de una etiqueta de inicio o de final.
• String getElementText(), retorna el contenido de un elemento de texto.
• String getText(), retorna el contenido de texto de un elemento de texto, o de un comentario, o de una
referencia de entidad, o de una sección CDATA.
XMLStreamReader proporciona los siguientes métodos para recuperar información acerca de los atributos:
• int getAttributeCount(), retorna el número de atributos.
• String getAttributeNamespace(int índice), retorna el espacio de nombres del atributo.
• String getAttributeLocalName(int índice), retonra el nombre local del atributo.
• String getAttributePrefix(int índice), retorna el prefijo de espacio de nombres del atributo.
• String getAttributeType(int índice), retorna el tipo del atributo, como por ejemplo "CDATA" o
"NMTOKEN".
• String getAttributeValue(int índice), retorna el valor del atributo.
• String getAttributeValue(String namespaceUri, String nombreLocal), retorna el valor del atributo.
• boolean isAttributeSpecified(int índice), indica si el atributo fue creado por defecto.
Como StAX es bidireccional, el API Cursor proporciona otra interfaz para escribir XML mediante streaming.
La interfaz javax.xml.stream.XMLStreamWriter permite a las aplicaciones escribir a un canal XML o crear
un nuevo canal desde cero. Proporciona métodos para: escribir un documento XML bien formado, descargar
y cerrar el canal, y escribir nombres cualificados.
El sigueinte código crea un nuevo documento XML de ventas:
XMLOutputFactory factory = XMLOutputFactory.newInstance();
OutputStream destino = new FileOutputStream("nuevo.xml");
XMLStreamWriter writer = factory.createXMLStreamWriter(destino);
writer.writeStartDocument();
writer.writeStartElement("ventas");
writer.writeAttribute("id", "1");
writer.writeEmptyElement("fecha");
writer.writeStartElement("precio");
writer.writeCharacters("30");
writer.writeEndElement();
writer.writeEndElement();
writer.writeEndDocument();
writer.flush();
writer.close();
Como siempre, primero se utiliza un fabricador de objetos XMLStreamWriter, el cual debe quedar asociado
a un documento destino. La clase XMLStreamWriter proporciona métodos write_() para crear los diversos
elementos del documento. El resulado es el siguiente:
<?xml version="1.0" ?><ventas id="1"><fecha/><precio>30</precio></ventas>
El proceso de escritura de elementos debe empezar por writeStartDocument() y finalizar por
writeEndDocument(). A su vez, la creación de un elemento debe comenzar con writeStartElement() y
finalizar con writeEndElement(), excepto cuando creamos un elemento vacío.
2.3.2. El API Iterator.
El API Iterator representa un canal sobre un documento XML como un conjunto de objetos de evento.
Estos eventos son lanzados por la aplicación y proporcionados por el parseador en el orden en que son leídos
desde el documento XML de origen.
La base de el API Iterator es la interfaz javax.xml.stream.events.XMLEvent, y existe una subinterfaz para
cada tipo de evento. La siguiente tabla describe estas sub-interfaces:

281
Interfaz en javax.xml.stream.events. Significado
StartDocument Informa del comienzo de un conjunto de eventos XML.
StartElement Informa del inicio de un elemento, incluyendo a sus atributos y
declaración de espacios de nombres
EndElement Informa del fin de un elemento.
Characters Informa de una sección CDATA o un elemento de texto.
EntityReference Informa de una referencia de entidad.
ProcessingInstruction Informa de la instrucción de proceso.
Comment Retorna el texto de un comentario.
EndDocument Informa del fin del documento.
Attribute Los atributos normalmente forman parte del evento
StartElement. Pero hay veces que puede interesar procesarlos
como eventos.
Para procesar estos eventos primero se debe instanciar un objeto javax.xml.stream.XMLEventReader a
partir de un XMLInputFactory:
XMLInputFactory factory = XMLInputFactory.newInstance();
InputStream origen = new FileInputStream("ventas.xml");
XMLEventReader reader = factory.createXMLEventReader(origen);
La forma de recorrer el documento es parecida a la del API Cursor:
while(reader.hasNext()) {
XMLEvent evento = reader.nextEvent();
if (evento.isStartElement()) {
StartElement elemento = evento.asStartElement();
if (elemento.getName().getLocalPart().equals("venta")) {
String id = elemento.getAttributeByName(new QName("id")).getValue();
System.out.println("ID = " + id);
}
}
}
reader.close();
Para la escritura se utiliza la clase javax.xml.stream.XMLEventWriter, que se obtiene de la siguiente
manera:
XMLOutputFactory factory = XMLOutputFactory.newInstance();
OutputStream destino = new FileOutputStream("nuevo.xml");
XMLEventWriter writer = factory.createXMLEventWriter(destino);
Las instrucciones para escribir elementos en la salida son análogas a las de la clase XMLStreamWriter:
XMLEventFactory evFactory = XMLEventFactory.newFactory();
writer.add(evFactory.createStartDocument("UTF-8"));
writer.add(evFactory.createStartElement("","","ventas"));
writer.add(evFactory.createStartElement("","","venta"));
writer.add(evFactory.createAttribute("id","1"));
writer.add(evFactory.createStartElement("","","fecha"));
writer.add(evFactory.createEndElement("","","fecha"));
writer.add(evFactory.createStartElement("","","precio"));
writer.add(evFactory.createCharacters("30"));
writer.add(evFactory.createEndElement("","","precio"));
writer.add(evFactory.createEndElement("","","venta"));
writer.add(evFactory.createEndElement("","","ventas"));
writer.add(evFactory.createEndDocument());
writer.close();
2.4. Consultas con XPath.
El API JAXP proporciona las siguientes clases e interfaz para leer un documento XML utilizando expresiones
XPath:
• javax.xml.xpath.XPathFactory, una instancia de esta clase permite crear objetos XPath.
• javax.xml.xpath.XPath, esta interfaz proporciona acceso a un entorno de evaluación de expresiones
XPath.

282
• javax.xml.xpath.XPathExpression, esta clase permite evaluar y obtener el resultado de expresiones
XPath.
2.4.1. La interfaz «XPath».
La interfaz javax.xml.xpath.XPath es la base para crear consultas XPtah y evaluarlas sobre un documento
XML. Como con las demás tecnologías empezaremos por un fabricador de tipo XpathFactory para obtener
un objeto XPath.
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
Mediante una instancia de la interfaz XPath podemos seguir dos técnicas para evaluar expresiones:
• Si queremos evaluar inmediatamente una expresión XPath podemos utilizar el método
XPath.evaluate():
String expresion = "/documento/persona [@id=1]";
InputStream docXML = new FileInputStream("documento.xml");
String result = xpath.evaluate( expresion , new InputSource(docXML) );
• Si queremos reutilizar una expresión XPath, podemos compilarla y generar un objeto XPathExpression:
String expresion = "/documento/persona [@id=1]";
XPathExpression expr = xpath.compile(expresion);
A continuación podemos evaluar la expresión sobre un documento XML y obtener el resultado en una
diversidad de formatos. Para ello se utiliza el método evaluate() de la clase XPathExpression.
InputStream docXML = new FileInputStream("documento.xml");
String result = (String) expr.evaluate( new InputSource(docXML) , XPathConstants.STRING);
El método evaluate() de la interfaz XPath y de la clase XPathExpression evalúa una expresión XPath
sobre un origen que contenga un documento XML o una estructura de datos asimilable. En la clase
XPathExpression admite las siguientes sobrecargas:
• Object evaluate(InputSource source, QName returnType) throws XPathExpressionException
Evalúa la expresión compilada en el contexto especificado por el parámetro InputSource y retorna el
resultado el formato especificado por el segundo parámetro.
Un objeto org.xml.sax.InputSource define un origen de datos enlazado al contexto establecido sobre
un documento XML. El constructor de esta clase admite como parámetro un java.io.InputStream o
un java.io.Reader. También admite un string con un identificador del sistema.
Un objeto javax.xml.namespace.QName representa un nombre cualificado tal como se definen en las
especificaciones XML. Como argumento para este método se pueden usar las siguientes constantes de la
clase javax.xml.xpath.XPathConstants: NUMBER, STRING, BOOLEAN, NODESET, y NODE.
• String evaluate(InputSource source) throws XPathExpressionException
Equivale a evaluate(source, XPathConstants.STRING).
• public Object evaluate(Object item, QName returnType) throws XPathExpressionException
Evalúa la expresión compilada a partir del objeto pasado como primer argumento (un objeto Node, por
ejemplo).
• public String evaluate(Object item) throws XPathExpressionException
Equivale a evaluate(item, XPathConstants.STRING).
Los métodos evaluate() de la interfaz XPath son similares a los de la clase XPathExpression. Se
diferencian en que tienen un primer argumento adicional en el que se pasa la expresión XPath.
Importante. Los métodos evaluate(), cuando se enlazan a un InputSource cierran el canal al finalizar
la consulta. Esto impide realizar otra consulta sobre el mismo canal.
2.4.2. Consultas que devuelven un único valor.
Cuando se realiza una consulta que devuelven un único valor, este puede ser de tipo: numérico (Double),
string, booleano, o de tipo Node.
Por ejemplo, si retornamos el id de la primera persona recuparemos un Double:
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = domFactory.newDocumentBuilder();
// Se evalúa una expresión XPath para obtener el primer "id"
XPathFactory factory = XPathFactory.newInstance();

283
XPath xpath = factory.newXPath();
InputSource input = new InputSource(new FileInputStream("documento.xml"));
String consulta = "/documento/persona [position()=1]/@id";
Double result = (Double) expr.evaluate(consulta, input, XPathConstants.NUMBER);
System.out.println(result);
2.4.3. Consultas que devuelven varios valores.
Cuando la consulta XPath devuleve varios resultados el método evaluate() retorna un objeto NodeList. En
esta caso se debe utilizar la constante XPathConstants.NODESET.
El siguiente código de ejemplo muestra cómo obtener todos los nombres de persona.
// Se obtiene el contenido del xml como un documento DOM
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse("documento.xml");
// Se evalúa una expresión XPath para obtener una lista de nodos "nombre"
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
Object result = xpath.evaluate("/documento/persona/nombre/text()", doc, XPathConstants.NODESET);
// Se imprime el resultado
NodeList nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
System.out.println(nodes.item(i).getNodeValue());
}
2.4.4.¿Cómo parsear un documento que referencia espacios de nombres con JAXP?
Si queremos aplicar expresiones XPath con al API JAXP sobre documentos validados sobre uno o varios
esquemas es preciso especificar el espacio de nombres y el prefijo correspondiente a cada esquema.
Por ejemplo, supongamos el siguiente documento XML que se valida contra un esquema:
Archivo de esquema «biblioteca.xsd»
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://xml.netbeans.org/schema/biblioteca"
xmlns:tns="http://xml.netbeans.org/schema/test"
elementFormDefault="qualified">
<xsd:element name=" biblioteca ">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="libro" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
Archivo de documento «biblioteca.xml»
<?xml version="1.0" encoding="UTF-8"?>
<ns1:biblioteca xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:ns1='http://xml.netbeans.org/schema/biblioteca'
xsi:schemaLocation='http://xml.netbeans.org/schema/ biblioteca bibioteca.xsd'>
<ns1:libro>El Quijote</ns1:libro>
<ns1:libro>Pinocho</ns1:libro>
</ns1:biblioteca>

El espacio de nombres usado es "http://xml.netbeans.org/schema/biblioteca", el cual esta asociado al


prefijo "ns1".
El código para obtener el último libro de la biblioteca es el siguiente:
try {
// Enlazo el documento mediante un FileInputStream
InputStream origenXML = new FileInputStream("bibioteca.xml");
// Obtengo una instancia de XPath
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();

284
// Proporciono la información sobre el contexto del espacio de nombres. Se ha simplificado el
// código teniendo en cuenta que trabajaremos con un solo espacio de nombres y un prefijo.
xpath.setNamespaceContext(new NamespaceContext() {
public String getNamespaceURI(String prefix) {
return "http://xml.netbeans.org/schema/biblioteca";
}
public String getPrefix(String namespaceURI) {
return "ns1";
}
public Iterator getPrefixes(String namespaceURI) {
return Arrays.asList("ns1").iterator();
}
});
// Evalúo la expresión e imprimo el resultado
String expresion = "/biblioteca/libro [last()]";
String resultado = xpath.evaluate(expresion, new InputSource(origenXML));
System.out.println(resultado);
origenXML.close();
} catch (Exception ex) {
}
La información del contexto de esquema es pasada como un objeto
javax.xml.namespace.NamespaceContext al método XPath.setNamespaceContext(). La interfaz
NamespaceContext declara los siguientes métodos:
• String getNamespaceURI(String prefix)
Debe retornar la URI del espacio de nombres asociado a un prefijo dado.
• String getPrefix(String namespaceURI)
Debe retornar el prefijo asociado a la URI de un espacio de nombres dado.
• Iterator getPrefixes(String namespaceURI)
Debe retornar un iterador con los prefijos asociados a la URI de un espacio de nombres dado
2.5. El API XSLT.
JAXP soporta XSLT con el paquete javax.xml.transform, que nos permite conectar un transformador
XSLT para realizar las transformaciones a partir de una hoja de estilo. Los subpaquetes tienen APIs de
streams específicos, de SAX, y de DOM, que nos permiten realizar transformaciones directamente desde un
fichero XML, árboles DOM y eventos SAX.
Importante: Algunas clases del paquete javax.xml.transform pueden hacer uso de la libería
serializer.jar (que puede ser descargada como parte del módulo Xalan desde la página oficial de Oracle).
2.5.1. Proceso de transformación.
Para transformar un documento XML se utiliza la clase javax.xml.transform.Transformer, la cual realizará
el proceso de transformación. Las instancias de esta clase requieren de tres cosas:
• Acceso a la hoja de estilo a través de un objeto javax.xml.transform.Source.
• Acceso al documento XML origen a través de un objeto javax.xml.transform.Source.
• Acceso a un documento destino a través de un objeto javax.xml.transform.Result.
El primer paso es crear una instancia de javax.xml.transform.Transformer mediante un fabricador:
TransformerFactory transFactory = TransformerFactory.newInstance();
// Se obtiene el contenido de una hoja de estilo XSL como un objeto Source
Source hojaDeEstilo = new StreamSource(new File(application.getRealPath("/ventas.xsl")));
// Se crea el transformador asociado a la hoja de estilo
Transformer transformer = transFactory.newTransformer( hojaDeEstilo );
Al crear un objeto Transformer se debe pasar como argumento la hoja de estilo, pero mediante un objeto
cuya clase implemente la interfaz javax.xml.transform.Source. Se puede usar una instancia de:
• javax.xml.transform.dom.DOMSource, para enlazar un objeto Document que contenga la hoja de
estilo.
• javax.xml.transform.sax.SAXSource, para que la hoja de estilo sea parseada con eventaos SAX.
• javax.xml.transform.stream.StreamSource, para enlazar un archivo que contenga la hoja de estilo.

285
El sigueinte paso es obtener una referencia al documento XML origen mediante un Source. Igualmente que
con la hoja de estilo, podemos recuperar el documento XML origen mediante un DOMSource, un
SAXSource o un StreamSource.
Si usamos el nodo raíz del árbol DOM, la siguiente línea de código construye un objeto DOMSource como
fuente de la transformación:
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse("ventas.xml");
DOMSource sourceXML = new DOMSource(doc);
El siguiente paso es crear un objeto javax.xml.transform.stream.StreamResulta través del cual
especificaremos el destino de la transformación:
File newXML = new File("newXML.xml");
FileOutputStream os = new FileOutputStream(newXML);
Result result = new StreamResult(os);
Por último se realiza la transformación:
transformer.transform(source, result);
2.5.2. Paso de parámetros a la hoja de estilo.
Tal como ya se ha explicado, las hojas de estilo admiten el uso de parámetros cuyo valor puede ser
proporcionado externamente.
Para ilustra cómo pasar parémetros vamos a partir de la siguiente hoja de estilo:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:param name="id_param" select="0" />
<xsl:template match="ventas">
<xsl:value-of select="venta [@id = $id_param]/fecha" />
</xsl:template>
</xsl:stylesheet>
En esta hoja de estilo se declara un parámetro llamado id_param con el valor por defecto 0. Se utilizará este
parámetro para recuperar la fecha de la venta cuyo id coincida con el parámetro.
Primero creamos un objeto Transformer enlazado a la hoja de estilo y al documento origen. Como destino
enlazaremos un CharArrayWriter.
TransformerFactory transFactory = TransformerFactory.newInstance();
// Se obtiene el contenido de una hoja de estilo XSL como un objeto Source
Source hojaDeEstilo = new StreamSource(new File("ventas.xsl"));
Transformer transformer = transFactory.newTransformer(hojaDeEstilo);
// El documento origen
Source origen = new StreamSource(new File("ventas.xml"));
// El destino de la transformación
CharArrayWriter bufer = new CharArrayWriter();
Result destino = new StreamResult(bufer);
Antes de realizar la transformación pasaremos valor al parámetro:
transformer.setParameter("id_param", "2");
transformer.transform(origen, destino);
System.out.print(bufer.toString());

3. El API JAXB.
JAXB proporciona una forma rápida y conveniente de crear un mapeo de dos vías entre documentos XML y
objetos Java. Dado un DTD o un esquema, el compilador JAXB genera un conjunto de clases Java que
contienen todo el código para analizar documentos XML basados en ese esquema. Un desarrollador usando
las clases generadas puede construir un árbol de objetos Java representando un documento XML, manipular
el contenido del árbol, y regenerar el documento XML desde el árbol.
Para empezar a usar una aplicación JAXB todo lo que necesitamos es un esquema, que según la versión de
JAXB puede ser un DTD o un esquema XML.
Una vez que tenemos el esquema, lo unimos a un conjunto de clases realizando los siguientes pasos:

286
▪ Escribir un esquema de unión, que contiene las instrucciones para unir el esquema a las clases. El esquema
de unión está escrito en un lenguaje de unión basado en XML, que está incluido con JAXB.
▪ Ejecutar el compilador de esquema, que toma el esquema y un esquema de unión y genera las clases a
partir de ellos. Cada clase que genere el compilador tiene un método get y otro método set. Cuando se
crea e inicializa con datos un ejemplar de la clase, podemos usar estos métodos accesores para acceder a los
datos.
La tecnología JAXB viene a ser para documentos XML, lo que es la tecnología Java Persistence para bases de
datos.
3.1. Generar clases de Java usando JAXB.
JAXB es una tecnología Objeto-XML, que se encarga de hacer la transición entre beans de Java y nodos de
un documento XML. Para realizar el mapeado entre los nodos XML y los beans se puede partir de un
esquema XML.
Como ejemplo de generación de clases desde un esquema crearemos el fichero "productos.xsd" usando la
plantilla «Esquema XML» de NetBeans:
Figura 16

Ahora generaremos las clases beans a partir de este esquema.En el menú «Fichero» debemos pulsar sobre la
opción «Nuevo Fichero» para mostrar el cuadro de diálogo de plantillas de NetBeans. Debemos seleccionar
la categoría «XML» y la plantilla «JAXB Binding».

287
Figura 17

Tras dar un nombre al enlace, cargar el fichero de esquema e indicar el paquete donde se generarán las clases
debemos pulsar el botón «Finalizar».
Figura 18

NetBeans creará un nodo «JAXB Bindings» con el nombre del enlace creado. Se puede utilizar este enlace
para cambiar las propiedades y volver a generar las clases. En el nodo «Generated Sources» se generan las
clases que mapean con los elementos XML. En el paquete indicado se habrán creado tres ficheros:
• ObjectFactory.java, incluye una clase fabricadora de las entidades definidas en el fichero
Productos.java. La clase ObjectFactory es utilizada por la API JAXB para realizar el mapeado entre
clases y elementos XML.
• Productos.java, se corresponde con la clase que mapea el elemento raíz. Los demás elementos hijos se
mapearán con clases interns de la clase Producto.

288
• package-info.java, declara el paquete donde están contenidas las clases usadas para mapear los
elementos XML y el espacio de nombres usado en el documento. Si el documento no va a usar espacios de
nombres es conveniente editar este fichero y dejar vacío el atributo namespace:
@javax.xml.bind.annotation.XmlSchema(namespace = "",
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package modelo;
El compilador de esquema JAXB es lo suficientemente poderoso como para hacer asunciones razonables
desde el fichero de esquema y asignar los tipos de las propiedades de las clases mapeadoras.
3.2. Mapeado de clases, campos y propiedades.
Se pueden utilizar las anotaciones JAXB definidas en el paquete javax.xml.bind.annotation para
personalizar el mapeado de las clases Java al esquema del documento XML.
La anotaciones asociadas con un paquete son las siguientes:
• La anotación @XmlSchema mapea un paquete a un espacio de nombres concreto. Las opciones por
defecto son:
@XmlSchema (
xmlns = {},
namespace = "",
elementFormDefault = XmlNsForm.UNSET,
attributeFormDefault = XmlNsForm.UNSET
)
• La anotación @XmlAccessorType controla la serialización por defecto de los campos y propiedades.
Puede tomar los siguientes valores:
@XmlAccessorType(XmlAccessType.FIELD), enlaza automáticamente cada campo a un elemento
XML.
@XmlAccessorType(XmlAccessType.NONE), no enlaza automáticamente los campos y propiedades.
@XmlAccessorType(XmlAccessType.PROPERTY), enlaza automáticamente cada propiedad púbica a
un elemento XML.
@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER), enlaza automáticamente cada campo y
propiedad púbica a un elemento XML.
• La notación @XmlAccessorOrder controla el orden del mapeado de propiedades y campos con los
elementos XML. Puede tomar los siguientes valores:
@XmlAccessorOrder (AccessorOrder.UNDEFINED), para orden inderterminado.
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL), para orden alfabético.
Las anotaciones asociadas con una clase son:
• La anotación @XmlType mapea una clase a un tipo del esquema. Por ejemplo, para el elemento
<productos>, la clase Productos se mapea así:
@XmlType(name = "", propOrder = {"producto"})
@XmlRootElement(name = "productos")
public class Productos {
……………………….
}
• La anotación @XmlRootElement asocia un elemento global del esquema XML a la clase.
Las anotaciones asociadas a un campo o a una propiedad son:
• La notación @XmlElement mapea un campo o propiedad a un elemento XML. Por ejemplo:
public class Productos {
@XmlElement(required = true)
protected List<Productos.Producto> producto;
}
El campo producto de la clase Productos es mapeado con el sub-elemento <producto> del documento.
Al ser det tipo List, JAXB infiere que este elemento se puede repetir dentro del nodo padre <productos>.
• La notación @XmlAttribute mapea un campo o propiedad a un atributo de un elemento XML. Por
ejemplo, vamos a modificar el nombre de la propiedad que mapea la colección de productos:
package modelo;
@XmlAccessorType(XmlAccessType.FIELD)

289
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
@XmlType(name = "", propOrder = {"productos"})
@XmlRootElement(name = "productos")
public class Productos {
@XmlElement( name = "producto", required = true)
protected List<Productos.Producto> productos;
public List<Productos.Producto> getProductos() {
if (productos == null) {
productos = new ArrayList<Productos.Producto>();
}
return this.productos;
}
………………………….
}
• La notación @XmlTransient anula el mapeado sobre el campo o propiedad.
3.3. Lectura de un documento.
Después de generar las clases de mapeado podemos escribir aplicaciones Java que las usen para construir
representaciones del documento XML como objetos. Cada objeto corresponde a un elemento del documento
XML. De forma similar, cada objeto es un ejemplar de una clase del conjunto de clases generadas. Como los
objetos se mapean tanto al documento como a las clases, tenemos dos formas diferentes de construir el árbol
de objetos Java: desempaquetando un documento XML válido, o instanciando objetos desde las clases. De
esta forma, JAXB nos permite procesar documentos XML existentes y crear nuevos datos XML instanciando
las clases generadas.
Supongamos que existe este documento XML:
Archivo «productos.xml»
<?xml version="1.0" encoding="UTF-8"?>
<productosxmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://xml.netbeans.org/schema/productos'
xsi:schemaLocation='http://xml.netbeans.org/schema/productos productos.xsd'>
<producto>
<nombre>Producto 1</nombre>
<precio>30.5</precio>
</producto>
<producto>
<nombre>Producto 2</nombre>
<precio>40</precio>
</producto>
</productos>
La clase más importante del API JAXB es la clase abstacta javax.xml.bind.JAXBContext. A partir de esta
clase podemos obtener transformadores de XML a Java (desempaquetar) y transformadores de Java a XML
(empaquetar).
Para leer el documento productos.xml empezaremos por tanto creando un objeto JAXBContext:
JAXBContext jaxbContext = JAXBContext.newInstance("modelo");
El método fabricador newInstance() solicita la ruta de paquetes que contienen las clases para el mapeado.
Otra sobrecarga de este método permite pasar los tipos de las clases principales de mapeado. Por ejemplo,
podemos también utilizar esta instrucción:
JAXBContext jaxbContext = JAXBContext.newInstance(modelo.Productos.class);
Ahora crearemos un stream para enlazar el documento XML y poder crear un objeto
javax.xml.bind.Unmarshaller. Este objeto se encargará de transformar el esquema de elementos XML en
un objeto de tipo Productos:
Unmarshaller lector = jaxbContext.createUnmarshaller();
InputStream input = new FileInputStream("productos.xml");
modelo.Productos productos = (modelo.Productos) lector.unmarshal(input);
input.close();

290
La variable productos contendrá toda la información del documento XML, y simplemente tenemos que
manipularla como cualquier otra clase de Java. Por ejemplo, podemos obtener los nombre de producto de la
siguiente manera:
for (modelo.Productos.Producto producto : productos.getProductos()) {
System.out.println(producto.getNombre());
}
3.4. Modificación del documento con JAXB.
Una vez que tenemos un árbol de objetos Java como la raíz del árbol, podemos realizar modificaciones sobre
el mismo. Supongamos que queremos crear un nuevo producto:
modelo.Productos.Producto prd = new modelo.Productos.Producto();
prd.setNombre("Producto 3");
prd.setPrecio(100.5);
productos.getProductos().add(prd);
Como vemos, no plantea mayor problema que instanciar un nuevo objeto Producto, asignar sus propieades y
añadirlo a la colección de productos.
De forma análoga podemos realizar cualquier otra operación de actualización con todas las técnicas que se
han visto para manipular objetos y colecciones.
También se puede partir de cero a la hora de crar una representación de un documento XML. En el siguiente
ejemplo ser crea simplemente el nodo raíz del documento y un nodo hijo sin datos:
modelo.Productos prds = new modelo.Productos();
prds.getProductos().add(new modelo.Productos.Producto());
3.5. Escribir documentos XML desde el árbol de objetos.
Siempre que usemos desempaquetamiento o instanciación para construir una representación de objetos,
podemos posteriormente empaquetar los objetos a un documento XML, lo que significa que JAXB también
permite crear nuevos documentos XML que son válidos con respecto al esquema.
Para empaquetar nuestro árbol de objetos modificados en un nuevo documento XML, hay que crear un
objeto javax.xml.bind.Marshaller a partir del JAXBContext:
Marshaller escritor = jaxbContext.createMarshaller();
OutputStream output = new FileOutputStream("productos.xml");
escritor.marshal(productos, output);
output.close();
El método marshal se encarga de tranformar el objeto productos en un documento XML y escribir su
contenido en el destino especificado.
3.6. Diferencias entre JAXP y JAXB.
JAXP y JAXB sirven a muy diferentes propósitos. La arquitectura o el API que elijamos dependen de los
requerimientos de nuestra aplicación. Una ventaja de JAXP es que nos permite analizar y procesar los datos
desde el mismo conjunto de APIs. Si sólo queremos grabar unos pocos datos de un gran documento,
deberíamos usar un analizador SAX porque analiza los datos como un stream, lo que es muy rápido. Si
tenemos un documento que no es muy largo, y queremos añadirle o eliminarle datos, deberíamos usar DOM.
Aunque un árbol DOM puede ocupar una gran cantidad de memoria, el API DOM incluye funciones
comunes para manipulación de árboles.
Si queremos transformar los datos a otro formato, deberíamos usar JAXP, que incluye el API XSLT de
transformaciones. JAXP también nos permite la flexibilidad de validar los datos o no.
Usaremos JAXB cuando queramos:
• Acceder a datos en memoria, pero no necesitemos capacidades de manipulación.
• Procesar sólo datos que son válidos.
• Convertir datos a diferentes tipos.
• Generar clases basadas en un esquema.
• Construir representaciones de objetos de datos XML.
Usaremos JAXP cuando queramos:
• Tener flexibilidad en la forma de acceder a nuestros datos: secuencialmente con SAX o aleatoriamente en
memoria con DOM.
• Usar el mismo código de proceso con documentos basados en diferentes DTDs o esquemas.
• Analizar documentos que no sean necesariamente válidos.
291
• Aplicar transformaciones XSLT.
• Insertar o eliminar objetos desde un árbol de objetos que representa datos XML.

4. Librería JSTL XML.


Todas las tecnologías vistas para procesar documentos XML (JAXP y JAXB) son totalmente válidas en las
aplicaciones web; pero específicamente para páginas JSP, la API JSTL ofrece una librería con etiquetas para
consultar y transformar documentos XML de un forma sencilla.
La librería JSTL XML incluye el siguiente conjunto de acciones:
Etiqueta Descripción
<x:choose> Etiqueta condicional que establece un contexto para operaciones
<x:when select=""></x:when> condicionales exclusivas.
<x:otherwise></x:otherwise>
</x:choose>
<x:forEach select="" var=""> Etiqueta iteradora sobre los resultados de un consulta XPath.
</x:forEach>
<x:if select=""> Etiqueta condicional sobre una expresión XPath.
</x:if>
<x:out select="" /> Expone el resultado de evaluar una expresión XPath establecida
en el atributo select.
<x:parse xml="" var=" "/> Parsea un documento XML y genera un objeto Document.
<x:set select="" var=""/> Guarda el resultado de evaluar una expresión XPath en una
variable.
<x:transform doc="" var="" xslt=""> Realiza una transformación sobre un documento XML utilizando
<x:param name="" value="" /> una hoja de estilo XSL.
</x:transform>

Importante:Según la versión de JSTL, las etiquetas de la librería XML pueden hacer uso de algunas clases
de la librería "xalan.jar" y "serializer.jar", y por tanto éstas debe estar disponible para el proyecto web.
Pueden descargarse estas librerías desde la página oficial de Oracle ("http://www.oracle.es").
4.1. Procesamiento XML.
El primer paso para que las etiqueta JSTL XML trabajen con un documento XML es convertirlo a un objeto
Document. Esto se puede realizar mediante la etiqueta <x:parse /> de dos formas.
Podemos leer el contenido de un fichero dentro de una variable mediante la etiqueta <c:import /> y
después parsear el contenido:
<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<c:import url="ventas.xml" var="reader" />
<x:parse doc="${reader}" var="docx" />
</body>
</html>
La acción <c:import> es utilizada para importar un documento y recuperar su contenido en un atributo,
mientras <x:parse> genera un árbol DOM a partir de él. Y ya se puede usar la variable de ámbito "docx"
para realizar consultas. También se puede crear previamente un objeto Document y dejarlo disponible en
una atriabuto de ámbito.
La segunda forma de usar la etiqueta <x:parse> es escribiendo un documento XML dentro de su cuerpo:
<x:parse var="docx">
<ventas>

292
<venta id="1">
<fecha>2014-03-12</fecha>
<precio>20</precio>
</venta>
<venta id="2">
<fecha>2014-04-02</fecha>
<precio>25</precio>
</venta>
</ventas>
</x:parse>
4.2. Consultas.
Casi todas las etiquetas JSTL XML trabajan sobre el resultado de una consulta XPath. Si la consulta XPath
retorna un único resultado se puede utilizar la etiqueta <x:out> para renderizar su resultado:
<x:out select="$docx/ventas/venta [@id=1]/fecha" />
Esta etiqueta permite recuperar la fecha de la venta de id=1 y mostrarla en la página. Si nos fijamos en su
sintaxis, vemos que en el atributo select se asigna la consuta XPath, pero con un diferencia respecto a las
consultas XPath puras. Aquí comienza con $docx, para indicarle a la etiqueta el documento sobre el que
tiene que realizar la consulta.
Si la consulta XPath retorna más de un resultado podemos utizar la etiqueta <x:forEach> para iterar sobre
los mismos:
<x:forEach var="fecha" select="$docx/ventas/venta/fecha/text()">
${fecha.nodeValue}<br>
</x:forEach>
El código precedente recupera todas las fechas de ventas. El resultado es devuelvo con un NodeList y por
tando en cada iteración la variable de ámbito fecha contendrá un objeto Node. Por eso se accede a su
propiedad nodeValue para renderizar el contenido de las etiquetas <fecha></fecha>.
También podemos aplicar funciones de agregado como count() y sum() en las expresiones XPath de la
siguiente manera:
<x:out select="count($docx/ventas/venta)" />
Esta etiqueta renderiza el número de nodos venta.
4.3. Transformación de documentos.
La etiqueta <x:transform /> permite aplicar una hoja de estilo XSL a un documento XML y generar el
resultado sobre la propia página o dentro de una variable de ámbito.
Para ilustrar un ejemplo definiremos una sencilla hoja de estilo y un documento XML directamente sobre la
página:
<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<c:set var="docx">
<?xml version="1.0" encoding="UTF-8"?>
<ventas>
<venta id="1">
<fecha>2014-03-12</fecha>
<precio>20</precio>
</venta>
<venta id="2">
<fecha>2014-04-02</fecha>
<precio>25</precio>
</venta>

293
</ventas>
</c:set>
<c:set var="hoja">
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="ventas">
Suma de precios = <xsl:value-of select="sum(venta/precio)" />
</xsl:template>
</xsl:stylesheet>
</c:set>
<x:transform xslt="${hoja}" doc="${docx}"/>
</body>
</html>
En este ejemplo, la hoja de transformación calcula la suma de los precios de ventas. Por tanto el resultado que
se mostrará en el navegador será:
Suma de precios = 45
Los atributos xslt y doc del la etiqueta <x:transform> admiten como argumento un String (como se hace
en el ejemplo), un Reader o un Source. No admiten como argumento un Document, y por eso no se aplica
un parseo.
También es posible pasar parámetros a la hoja de estilo. En la siguiente modificación del código previo se
filtran las ventas por un precio pasado por parámetro:
<c:set var="hoja">
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:param name="param" select="0" />
<xsl:template match="ventas">
<xsl:value-of select="sum(venta/precio [text() > $param])" />
</xsl:template>
</xsl:stylesheet>
</c:set>
<x:transform xslt="${hoja}" doc="${docx}">
<x:param name="param" value="20" />
</x:transform>

PRÁCTICA
En una aplicación web se quiere gestionar el contenido de un documento XML con la siguiente estructura:
<stoks>
<stok codigo="1">
<producto>Producto 1</producto>
<cantidad unidades="kg">20</cantidad>
</stok>
<stoks>
El elemento <stoks> es la raíz del documento y pude estar formado por cero o varios elementos <stok>.
Cada elemento <stock> tiene obligatoriamente un atributo código, un único hijo <producto> y un único
hijo <cantidad>. El atributo unidades de <cantidad> es opcional, si no se especifica se interpretarán
kilogramos.

01) Crea un esquema XML que valide los documentos de stock.


02) Crea una hoja de estilo que permita mostrar los stock en una página HTML con un formato de tabla:

CÓDIGO PRODUCTO CANTIDAD UNIDADES


1 Producto 1 20 kg

03) Crea una librería de etiquetas personalizadas que contenga:

294
<stokc:carga url="stocks.xml" />
Debe cargar en memoria un documento especificado en url.
<stock:guarda />
Debe guardar los cambios realizados en el documento cargado con <stock:carga>.
<stock:inserta codigo="2" producto="p" cantidad="20" />
Debe insertar un nuevo stock en el documento de memoria.
<stock:elimina codigo="2" />
Debe eliminar de memoria el stock de código dado.

Utiliza el API DOM para implementar las funcionalidades de estas etiquetas.

295
UNIDAD 18. COMPONENTES JAVA EE: EJB 3.0
1. Tecnología JMS
La tecnología JMS es la solución de Java para implementar un sistema de gestión de mensajes mediante un
Servidor de Aplicaciones. Un sistema de mensajes aporta una solución asíncrona para intercambiar
información entre un cliente y un servidor, o entre dos clientes y un servidor intermediario.
En un sistema de mensajería como JMS, normalmente hablaremos de:
• Cola de mensajes: un sistema servidor que permite recibir, almacenar y distribuir mensajes.
• Productor o publicador: aquella aplicación cliente que envía mensajes a una cola.
• Consumidor: aquella aplicación cliente que recibe o consume los mensajes de una cola.
• Suscriptor: aquella aplicación cliente que se suscribe a una cola y reciben notificaciones de los mensajes
que son introducidos en la misma.
1.1. Arquitectura JMS.
Una aplicación JMS consta de los siguientes elementos:
• Clientes JMS: aplicaciones que envían o reciben mensajes a través de JMS.
• Mensajes: los mensajes que se intercambian.
• Objetos administrados: los objetos JMS a los que se dirigen las comunicaciones dentro del Servidor de
Aplicaciones.
1.1.1. Objetos administrados.
Los objetos administrados son las entidades con las que se comunican los clientes JMS para enviar o recibir
mensajes. Se denominan así porque los crea un administrador en un Servidor de Aplicaciones. Estos objetos
administrados implementan las interfaces JMS y se sitúan en un espacio de nombres JNDI para que los
clientes puedan solicitarlos. Hay dos tipos de objetos administrados en JMS:
• ConnectionFactory: Se usa para crear una conexión al proveedor del sistema de mensajes.
• Destination: Son los destinos de los mensajes que se envían y el recipiente de los mensajes que se
reciben.
1.1.2. Mensajes.
Los mensajes son el corazón del sistema de mensajes. Están formados por tres elementos:
• Una cabecera (header): contiene una serie de campos que le sirven a los clientes y proveedores para
identificar a los mensajes.
• Propiedades (properties): Son propiedades personalizadas para un mensaje en particular. Incluye entre
otros el usuario y la aplicación que envían el mensaje, un número de secuencia, etc.
• El cuerpo (body): Es el mensaje en sí. Hay varios tipos:
-StreamMessage: Contiene una secuencia de datos que se escriben y leen de manera secuencial.
-MapMessage: Contiene pares nombre-valor.
-TextMessage: Contiene un string.
-ObjectMessage: Contiene un objeto que implementa la interfaz Serializable.
-BytesMessage: Contiene una secuencia de bytes.
1.1.3. Clientes JMS.
Los clientes JMS son tanto los que suministran mensajes como los que los reciben. Para crear un cliente, en
general, se debe:
• Conseguir un objeto de la clase ConnectionFactory a través de JNDI.
• Conseguir un destino, mediante un objeto de la clase Destination a través de JNDI.
• Usar el objeto ConnectionFactory para conseguir un objeto Connection.
• Usar el objeto Destination para crear un objeto Session.
1.1.4. Tipos de destinos.
En JMS existen dos tipos de destinos: Queue (colas) y Topic (tópicos).
El modelo de destino Punto-a-PuntoQueue se corresponde con el siguiente esquema:

296
Figura 1

En las Queue existen uno o varios publicadores, y uno o varios consumidores. Los publicadores van dejando
sus mensajes en la cola, y son tomados en orden por un consumidor (sólo un consumidor puede recuperar un
mensaje de cada vez). Si el consumidor no está disponible, la cola va guardando ("encolando") los mensajes,
de manera que el consumidor pueda retomar su procesamiento cuando vuelva a estar en línea.
El modelo de destino Publicador/SubscriptorTopic se corresponde con el siguiente esquema:
Figura 2

Uno o varios consumidores se "suscriben" al Topic y van recibiendo los mensajes que se publican. Cuando se
desconectan dejan de recibir esos mensajes, y los pierden.
En los siguientes aparatados se ilustran ejemplos de estos dos modelos.
1.2. Ejemplo del modelo «Queue».
En este apartado veremos cómo crear un «ConnectionFactory» y un «Destination» sobre un servidor de
aplicaciones (concretamente GlassFish) y cómo debe ser el código de una aplicación productora y
consumidora de los mensajes.
Para ilustrar los ejemplos de los diversos modelos JMS se usará como Servidor de Aplicaciones la versión 3
de GlassFish.
1.2.1. Creación de los objetos administrados.
El primer paso es crear los objetos administrados apropiados mediante la consola de administración de
GlassFish: una fábrica de conexión y un recurso de destino de tipo Queue. Una vez arrancado el servidor de
GlassFish su consola de administración puede ser accedida a través de un navegador web a través del puerto
4848. También podemos acceder a su consola de administración mediante NetBeans, en su panel de
«Services/Prestaciones». Sobre el nodo «Servidores|Servidor GlassFish» debemos desplegar el menú
contextual y pulsar la opción «Ver Consola de Administración del Dominio».

297
Figura 3

Para crear la fábrica de conexión debemos utilizar el menú «Recursos/Recursos JMS/Fábricas de


conexión» en el panel izquierdo de la consola de administración de GlassFish. En el panel derecho debemos
pulsar el botón «Nuevo» para crear una fábrica de conexiones JMS. El siguiente diagrama ilustra la
configuración de una nueva fábrica de conexión:
Figura 4

El nombre JNDI asignado a esta fábrica de ejemplo es "jms/FabricaQueue1", y el tipo de recurso para
crear una Queue debe ser "javax.jms.QueueConnectionFactory". Las demás opciones pueden dejarse por
defecto.
Para crear un el recurso de destino debemos utilizar el menú «Recursos/Recursos JMS/Recursos de
destino» en el panel izquierdo de la consola de administración de GlassFish. El siguiente diagrama ilustra la
configuración de un destino de tipo Queue:

298
Figura 5

El nombre JNDI asignado a este destino de ejemplo es "jms/Cola1", y el tipo de recurso para crear una
Queue debe ser "javax.jms.Queue". La Queue debe ser asociada con un destino físico que almacenará los
mensajes, en el ejemplo se ha denominado "ColaFisica1". Las demás opciones pueden dejarse por defecto.
El destino físico puede crearse utilizando el menú «Configuración/Servicios JMS/Destinos físicos" o
podemos dejar que sea el propio Servidor quien lo cree cuando sea necesario.
Figura 6

1.2.2. Implementación del publicador.


Una aplicación cliente JMS necesitar referenciar las siguientes librerías para invocar JMS sobre GlassFish:gf-
client.jar y javax.jms.jar.
Las propiedades de configuración JNDI para GlassFish son:
Propiedad Valor
java.naming.factory.initial com.sun.enterprise.naming.SerialInitContextFactory
java.naming.factory.url.pkgs com.sun.enterprise.naming
java.naming.factory.state com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
java.naming.provider.url El host del Servidor de GlassFish
Tras conseguir el fabricador y destino mediante JNDI, la aplicación publicadora debe instanciar un objeto de
tipo javax.jms.Session, el cual le permite establecer una sesión de publicación en la cola. Mediante un
objeto de tipo javax.jms.QueueSender se pueden enviar mensajes a la cola.
import java.util.Properties;
import javax.jms.*;
import javax.naming.*;
public class PublicadorQueue {
public static void main(String[] args) {
// Se configuran las propiedades de conexión JNDI a un servidor local de GlassFish
Properties props = new Properties();

299
props.setProperty("java.naming.factory.initial", "com.sun.enterprise.naming.SerialInitContextFactory");
props.setProperty("java.naming.factory.url.pkgs", "com.sun.enterprise.naming");
props p.setProperty("java.naming.factory.state",
"com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
props.setProperty("java.naming.provider.url", "localhost");
try {
// Código de conexión JNDI
Context jndiContext = new InitialContext(p);
QueueConnectionFactory qcf = (QueueConnectionFactory) jndiContext.lookup("jms/FabricaQueue1");
Queue queue = (Queue) jndiContext.lookup("jms/Cola1");
// Código de creación de la sesión
QueueConnection queueCon = qcf.createQueueConnection();
QueueSession session = queueCon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
// Se crea el objeto publicador
QueueSender sender = session.createSender(queue);
// Se crea un mensaje de tipo string con las opciones por defecto
TextMessage message = session.createTextMessage("Primer mensaje");
// Se publica el mensaje
sender.send(message);
// se cierra la conexión a la cola
sender.close();
session.close();
} catch (NamingException ex) {
System.out.println("ERROR DE CONEXIÓN JNDI: " + ex);
} catch (JMSException ex) {
System.out.println("ERROR JMS: " + ex);
}
}
}
Algunos aspectos a comentar sobre este código son los siguientes:
• El método createQueueSession() admite dos parámetros. El primero es un valor booleano que indica
si la sesión debe ser transaccional. El segundo indica si el consumidor reconocerá cualquier mensaje
recibido (lo cual se ignora si la sesión es transaccional). Mediante esta característica un mensaje enviado no
se elimina de la cola hasta que no sea reconocido por el consumidor. Este reconocimiento se realiza
llamando al método acknowledge() del mensaje. Para que sea el cliente el que reconozca los mensajes y
no JMS, hay que crear una sesión con la constante CLIENT_AKNOWLEDGE como parámetro. Los valores
posibles son:
-Session.AUTO_ACKNOWLEDGE, provoca que sea la sesión quien reconozca el mensaje y lo elimine
de la cola automáticamente.
-Session.CLIENT_ACKNOWLEDGE, provoca que sea la aplicación cliente quien deba reconocer el
mensaje.
-Session.DUPS_OK_ACKNOWLEDGE, provoca que la sesión reconozca de forma perezosa la entrega
de mensajes.
• Se pueden enviar mensajes mediante los siguientes métodos de la clase QueueSender:
-void send(Message mensaje), envía un mensaje a la cola con los valores por defecto.
-void send(Destination destino, Message mensaje), envía un mensaje a un destino determinado.
-void send(Queue cola, Message mensaje), envía un mensaje a una cola destino determinada.
-public void send(Destination destino, Message mensaje, int modoEntrega, int prioridad,
long tiempoDeVida), envía un mensaje a un destino.
-public void send(Queue cola, Message mensaje, int modoEntrega, int prioridad, long
tiempoDeVida), envía un mensaje a una cola destino.
• A los mensajes se les puede establecer una duración a partir de la cual dejan de ser válidos. Para esto
podemos usar el método setTimeToLive() de la interfaz MessageProducer o podemos especificarlo en
el método send() o publish() dependiendo del tipo de servicio. Podemos enviar un mensaje con:
sender.send(message,DeliveryMode.NON_PERSISTENT,1,1000);

300
Y de esta manera conseguimos que los mensajes caduquen (y por lo tanto no se distribuyan) si no se
solicitan antes de un segundo. El valor del tiempoDeVida se expresa en milisegundos; un valor igual a 0
indica que el mensaje no caduca nunca.
El valor DeliveryMode.PERSISTENT indica que el proveedor de los servicios JMS asegura que el mensaje
no se perderá en caso de que falle el servicio. El valor DeliveryMode.NON_PERSISTENT no garantizan
esto último.
1.2.3. Implementación del consumidor.
La aplicación consumidora necesita referenciar también las mismas librerías de GlassFish y las propiedades de
configuración JNDI son también las mismas.
Tras conseguir el fabricador y destino mediante JNDI, la aplicación consumidora debe instanciar un objeto de
tipo javax.jms.Session, el cual le permite establecer una sesión de publicación en la cola. Mediante un
objeto de tipo javax.jms.QueueReceiver se pueden recuperar mensajes de la cola.
import java.util.Properties;
import javax.jms.*;
import javax.naming.*;
public class ConsumidorQueue {
public static void main(String[] args) {
// Se configuran las propiedades de conexión JNDI a un servidor local de GlassFish
Properties props = new Properties();
props.setProperty("java.naming.factory.initial", "com.sun.enterprise.naming.SerialInitContextFactory");
props.setProperty("java.naming.factory.url.pkgs", "com.sun.enterprise.naming");
props p.setProperty("java.naming.factory.state",
"com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
props.setProperty("java.naming.provider.url", "localhost");
try {
// Código de conexión JNDI
Context jndiContext = new InitialContext(p);
QueueConnectionFactory qcf = (QueueConnectionFactory) jndiContext.lookup("jms/FabricaQueue1");
Queue queue = (Queue) jndiContext.lookup("jms/Cola1");
// Código de creación de la sesión
QueueConnection queueCon = qcf.createQueueConnection();
QueueSession session = queueCon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
// Se crea el objeto receptor
QueueReceiver receiver = session.createReceiver(queue);
// Se inicia la conexión de entrega de mensajes
queueCon.start();
// Se recuperan los mensajes de la cola.
// Si la cola no tiene mensajes se asigna el valor null pasado un milisegundo.
Message msg;
while ((msg = receiver.receive(1)) != null) {
if (msg instanceof TextMessage) {
TextMessage message = (TextMessage) msg;
System.out.println(message.getText());
}
}
// Se cierra la conexión a la cola
queueCon.close();
} catch (NamingException ex) {
System.out.println("ERROR DE CONEXIÓN JNDI: " + ex);
} catch (JMSException ex) {
System.out.println("ERROR JMS: " + ex);
}
}
}
Algunos aspectos a resaltar de este código son los siguientes:
• Para que comience la recepción de los mensajes en necesario invocar el método start() de la clase
QueueConnection.
• Se pueden recibir mensajes mediante los siguientes métodos de la clase QueueReceiver:

301
-Message receive(), recupera el siguiente mensaje. Este método llama indefinidamente hasta que un
mensaje es producido o hasta que se cierra el consumidor. Si se aplican transacciones, el consumidor
retiene el mensaje hasta que la transacción se confirma.
-Message receive(long tiempo), recupera el siguiente mensaje esperando los milisegundos
especificados a que haya un mensaje. Un tiempo con valor cero nunca expira y la llamada es indefinida
hasta que se cierra el consumidor.
-Message receiveNoWait(), recupera un mensaje si está disponible inmediatamente. Si no hay ninguno
disponible se retorna el valor null.
1.3. Ejemplo del modelo «Topic».
Como el proceso de configurar una cola de tipo Topic en GlassFish es similar a crear una cola Queue, en este
ejemplo se prescinde de imágenes ilustrativas.
1.3.1. Creación de los objetos administrados.
Para crear una fábrica de conexión debemos utilizar el menú «Recursos/Recursos JMS/Fábricas de
conexión» en el panel izquierdo de la consola de administración de GlassFish.
Para este ejemplo asignaremos a esta fábrica el nombre JNDI "jms/FabricaTopic1" , y el tipo de recurso
para crear un Topic debe ser "javax.jms.TopicConnectionFactory". Las demás opciones pueden dejarse
por defecto.
Para crear un el recurso de destino debemos utilizar el menú «Recursos/Recursos JMS/Recursos de
destino» en el panel izquierdo de la consola de administración de GlassFish. Para este ejemplo asignaremos
el nombre JNDI "jms/Topic1", y el tipo de recurso para crear un Topic debe ser "javax.jms.Topic". El
Topic debe ser asociado con un destino físico que almacenará los mensajes, en este ejemplo lo
denominaremos "TopicFisico1". Las demás opciones pueden dejarse por defecto.
1.3.2. Implementación del publicador.
La creación de una aplicación publicadora de un Topic es similar a la publicadora de una Queue. Sólo cambia
el tipo de la cola y las clases que la manejan.
import java.util.Properties;
import javax.jms.*;
import javax.naming.*;
public class PublicadorTopic {
public static void main (String args[]){
// Se configuran las propiedades de conexión JNDI a un servidor local de GlassFish
Properties props = new Properties();
props.setProperty("java.naming.factory.initial", "com.sun.enterprise.naming.SerialInitContextFactory");
props.setProperty("java.naming.factory.url.pkgs", "com.sun.enterprise.naming");
props p.setProperty("java.naming.factory.state",
"com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
props.setProperty("java.naming.provider.url", "localhost");
try {
// Código de conexión JNDI
Context jndiContext = new InitialContext(p);
TopicConnectionFactory tcf = (TopicConnectionFactory) jndiContext.lookup("jms/FabricaTopic1");
Topic topic = (Topic) jndiContext.lookup("jms/Topic1");
// Código de creación de la sesión
TopicConnection topicCon = tcf.createTopicConnection();
TopicSession session = topicCon.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
// Se crea el objeto publicador
TopicPublisher sender = session.createPublisher(topic);
// Se crea un mensaje de tipo string con las opciones por defecto
TextMessage message = session.createTextMessage("Primer mensaje");
// Se publica el mensaje
sender.publish(message);
// se cierra la conexión a la cola
topicCon.close();
} catch (NamingException ex) {
System.out.println("ERROR DE CONEXIÓN JNDI: " + ex);
} catch (JMSException ex) {

302
System.out.println("ERROR JMS: " + ex);
}
}
}
1.3.3. Implementación del consumidor.
En este caso la clase consumidora debe implementar la interfaz javax.jms.MessageListener, que tiene un
método (onMessage) que hay que sobrescribir con el código que queremos que se ejecute al recibir un
mensaje. El resto del código es prácticamente igual, a excepción de que antes de iniciar la descarga de
mensajes con el método start(), debemos indicarle la clase que implementa MessageListener mediante el
método setMessageListener() del objeto TopicSubscriber.
import javax.jms.*;
import javax.naming.*;
public class ConsumidorTopic implements MessageListener {
public static void main (String args[]) {
// Se configuran las propiedades de conexión JNDI a un servidor local de GlassFish
Properties props = new Properties();
props.setProperty("java.naming.factory.initial", "com.sun.enterprise.naming.SerialInitContextFactory");
props.setProperty("java.naming.factory.url.pkgs", "com.sun.enterprise.naming");
props p.setProperty("java.naming.factory.state",
"com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
props.setProperty("java.naming.provider.url", "localhost");
try {
// Código de conexión JNDI
Context jndiContext = new InitialContext(p);
TopicConnectionFactory tcf = (TopicConnectionFactory) jndiContext.lookup("jms/FabricaTopic1");
Topic topic = (Topic) jndiContext.lookup("jms/Topic1");
// Creamos la conexión y la sesión
TopicConnection conexion = tcf.createTopicConnection();
TopicSession sesion = conexion.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
// Creamos una sesión de suscripción
TopicSubscriber suscrito = sesion.createSubscriber(topic);
// Añadimos el oyente para recibir mensajes (la propia clase porque implementa MessageListener)
suscrito.setMessageListener(this);
// Iniciamos la recepción
conexion.start();
} catch (NamingException e){
System.out.println("ERROR DE CONEXIÓN JNDI: " + ex);
} catch (JMSException e) {
System.out.println("ERROR JMS: " + ex);
}
}
public void onMessage(Message mensaje){
// Se recibe un mensaje
try{
// Si es un mensaje de texto, lo mostramos
if (mensaje instanceof TextMessage){
System.out.println("Mensaje recibido: " + ((TextMessage) mensaje).getText());
}
} catch (JMSException e){
System.out.println("ERROR JMS: " + ex);
}
}
}
La invocación del método conexion.start() inicia la escucha de recepción de mensajes. Esta escucha es
indefinida hasta que se cierra la sesión.

303
2. EJB 3.0
Los EJB o Enterprise Java Beans son componentes Java EE que se ejecutan dentro de un contenedor EJB,
un entorno de ejecución dentro de un Servidor de Aplicaciones. Los Enterprise Java Beans son componentes
del lado del servidor que encapsulan la lógica del negocio de una aplicación.
2.1. Fundamentos de EJB 3.0.
Un contenedor EJB contiene y maneja un bean de igual forma que el Servidor Web Java contiene un servlet o
una página JSP. El contenedor EJB controla cada aspecto del bean en tiempo de ejecución incluyendo
accesos remotos al bean, seguridad, persistencia, transacciones, concurrencia, y accesos a un almacén de
recursos.
Cuando una aplicación cliente invoca un método remoto de un EJB, el contenedor primero intercepta la
llamada para asegurar que la persistencia, las transacciones, y la seguridad son aplicadas apropiadamente a
cada operación que el cliente realiza en el bean. El contenedor maneja estos aspectos de forma automática,
por eso el desarrollador no tiene que escribir este tipo de lógica dentro del propio código del bean. El
desarrollador de EJBs puede enfocarse en encapsular las reglas del negocio, mientras el contenedor se ocupa
de todo lo demás.
Deberíamos considerar usar EJBs si nuestra aplicación tiene alguno de los siguientes requerimientos:
• La aplicación debe ser escalable. Para acomodarse a un gran número de usuarios necesitamos distribuir los
componentes a través de varias máquinas. Los EJBs no son los únicos componentes que podemos ejecutar
sobre varias máquinas, pero tienen la ventaja de que su localización será transparente para los clientes.
• Las transacciones deben asegurar la integridad de los datos. Los EJBs soportan transacciones, el
mecanismo que gestiona acceso concurrente a objetos compartidos.
• La aplicación tendrá varios tipos de clientes. Con sólo unas pocas líneas de código, los clientes remotos
podrán localizar fácilmente a los beans.
Existen dos tipos de Enterprise Java Beans:
• Beans de sesión. Realizan alguna tarea para un cliente, y opcionalmente pueden implementar un servicio
web.
• Beans dirigidos por mensajes (Message-driven Bean). Actúan como receptores de un tipo particular de
mensajería, al igual que si fuesen un servicio JMS.
2.1.1. Roles y responsabilidades en EJB 3.0.
En el desarrollo de aplicaciones que cumplan con las especificaciones de la tecnología EJB se distinguen
varios roles y responsabilidades. Cada uno de estos roles se pueden realizar al mismo tiempo o en diferentes
niveles por un individuo o una organización:
• Proveedor de Beans: el proveedor de un bean es un programador EJB (proporciona el bean mediante su
desarrollo), que está familiarizado con la lógica del negocio para una aplicación de empresa. Se encarga de
producir los beans y empaquetarlos.
• Ensamblador de aplicación: en una aplicación EJB, varios beans puede ser creados por los
programadores de los beans. Es rol de un ensamblador de aplicación consiste en reunir todos los beans.
Normalmente se encarga de escribir los ficheros descriptores.
• Implementador: un implementador es aquel que tiene la responsabilidad de desplegar las aplicaciones
EJB en un contenedor EJB determinado. Sus herramientas son los Enterprise beans y los ficheros
descriptores procedentes del Ensamblador de aplicación. Requiere de experiencia sobre las funcionalidades
del contenedor EJB.
• Proveedor de Servidor EJB: proporciona el servidor EJB que aloja al contenedor EJB. La mayoría de los
contenedores EJB se empaquetan con los servidores EJB.
• Proveedor de contenedor EJB: proporciona los recursos para escribir un contenedor EJB y confirma
que el software está conforme con las especificaciones EJB. También debe proporcionar el software y
herramientas necesarias para que el administrador pueda administrar las aplicaciones EJB.
• Proveedor de persistencia: se encarga de proporcionar los recursos para persistir los datos utilizados por
los beans. Su experiencia está en el mapeado objeto/relacional, el procesamiento de consultas, y el
almacenamiento en caché. El enfoque del proveedor de persistencia es el desarrollo de un entorno de
tiempo de ejecución de transacciones escalable con capacidad para la gestión de la persistencia.
• Administrador del sistema: el administrador es responsable de mantener las aplicaciones EJB
desplegados con mucha frecuencia. Tiene que comprobar que los EJBs se ejecuten en un entorno sin

304
ninguna interrupción incluyendo el re-arranque del servidor en caso de caídas. También es responsable de
mantener la seguridad de los usuarios.
2.1.2. Empaquetado y requerimientos de implementación de beans.
Los ficheros ejb-jar son el formato estándar para empaquetar Enterprise beans. Son los ficheros que produce
el Proveedor de Beans, y los que recibe el Ensamblador de aplicación.
Un fichero ejb-jar producido por el Proveedor de Beans contiene uno o más Enterprise beans que
normalmente no contienen instrucciones de ensamblado de la aplicación. Un fichero ejb-jar producido por el
Ensamblador de aplicación contiene uno o más beans, junto con la información de ensamblado de la
aplicación, la cual describe cómo combinar los beans dentro de una unidad de instalación.
Un fichero ejb-jar puede contener un fichero descriptor de despliegue en formato XML. El fichero descriptor
debe almacenarse con el nombre META-INF/ejb-jar.xml dentro del fichero ejb-jar.
Nota: la mayor diferencia entre EJB 2.0 y EJB 3.0 es que éste último no requiere del fichero descriptor.
El fichero ejb-jar debe contener, bien por inclusión o por referencia, los ficheros de clase de cada Enterprise
bean como sigue:
• La clase del bean.
• Las interfaces de negocio del bean, servicios web de punto final, y componentes.
• Clases interceptoras.
• La clave primaria si el bean es un bean de entidad.
2.1.3. Interoperabilidad de EJB 3.0 con componentes anteriores.
EJB 3.0 soportar la migración e interoperabilidad a través de componentes clientes y servidores escritos en
diferentes versiones del API EJB.
Un Enterprise bean escrito con EJB 2.1 puede ser un cliente de componentes escritos con el API EJB 3.0.
Por ello los componentes antiguos no necesitan ser reescritos o recompilados.
Un cliente escritor para el API EJB 3.0 puede ser un cliente para componentes antiguos. Se pueden usar la
anotaciones @EJB (o los elementos ejb-ref y ejb-local-ref del fichero descriptor) para especificar inyección
de instancias de los beans dentro del componentes cliente.
2.2. Beans de Sesión.
Un Bean de Sesión encapsula lógica del negocio que puede ser invocada programáticamente desde un cliente
local, remoto o desde servicios web. Para acceder a una aplicación que esté desplegada sobre un servidor, el
cliente invocará los métodos del Bean de Sesión. Estos Beans realizarán algún trabajo para sus clientes,
ocultando la complejidad de su ejecución dentro del servidor.
Normalmente un cliente invocará un método de un bean de sesión y éste ejecutará las tareas de negocio,
ocultándole al cliente la implementación (ya sea de la lógica del negocio como también de la complejidad de la
comunicación remota).
Figura 7

Un bean de sesión no es compartido, solo puede pertenecer a un único cliente y no es persistente. Cuando el
cliente termina la sesión, el EJB es desasociado del cliente y termina.
2.2.1. Tipos de beans de sesión.
El estado de un objeto consiste en los valores de sus variables de instancia. Existen tres tipos de beans de
sesión según su conservación de estado:
•Stateful Session Beans.

305
Cuando un cliente se conecta a un Bean Stateful se mantiene el estado del bean durante toda la sesión
con el cliente. Si el cliente elimina el bean o lo termina, la sesión termina y el estado desaparece.
Un bean de sesión no es compartido, y sólo un cliente puede conectarse a él. El estado del bean es
retenido durante las llamadas a los métodos del bean. Cuando finaliza la conversación con el cliente ya
no es necesario retener el estado.
•Stateless Session Beans.
Un Stateless Session Bean no mantiene su estado durante la conversación con el cliente. Cuando un
cliente invoca un método en un Bean Stateless, el bean mantiene su estado durante la llamada y la
respuesta, pero cuando el método finaliza, el estado se pierde. Utilizar variables de instancia en un
Session Bean Stateless es un error de diseño.
Este funcionamiento permite que el contenedor de EJBs pueda compartir los beans con varios clientes, o
que durante la sesión de un mismo cliente cada llamada a un método sea atendida por instancias
diferentes del beans. Esto da gran flexibilidad al servidor para gestionar el ciclo de vida de los Stateless
Beans.
Los Stateless Beans soportan varios clientes simultáneamente, y por tanto ofrecen una mejor
escalabilidad para las aplicaciones que requieren un gran número de clientes concurrentes.
Los Stateless Session Beans pueden implementar servicios web, mientras que los otros tipos de beans no.
• Singleton Session Beans.
Se instancia un único Singleton Session Bean para todo el ciclo de vida de una aplicación. Por tanto son
beans compartidos por varios clientes y tienen una misma funcionalidad que un Stateless Bean, y también
pueden implementar servicios web.
Los Singleton Beans mantienen su estado entre invocaciones del cliente, pero todos los clientes
compartirán este estado. No requieren mantener el estado entre arranques y paradas del servidor.
2.2.2. ¿Cuándo usar un bean de sesión?
En general, se debería usar un bean de sesión cuando:
• En un momento determinado, sólo un cliente tiene acceso a la instancia del bean.
• El estado del bean no es persistente, existiendo solamente por un período corto de tiempo (tal vez un par
de horas).
• El bean implementa un servicio web.
Los Stateful Session Beans son apropiados si se cumple alguna de las siguientes condiciones:
• El estado del bean representa la interacción entre el bean y un cliente específico.
• El bean necesita mantener información acerca del cliente a través de varias invocaciones a varios métodos.
• El bean intermedia entre el cliente y otro componente de la aplicación, presentando al cliente una vista
simplificada.
• El bean maneja el flujo de proceso de varias aplicaciones.
Para mejorar el rendimiento, se debe escoger Stateless Session Bean si se cumple alguna de las siguientes
condiciones:
• El estado del bean no tiene datos específicos del cliente.
• En una invocación a un método, el bean ejecuta tareas específicas para todos los clientes.
Los Singleton Session Beans son apropiados para las siguientes circunstancias:
• Se necesita compartir el estado a través de toda la aplicación.
• Se necesita que varios hilos concurrentes accedan a un mismo bean.
• La aplicación necesita un bean que realice tareas cuando arranca y finaliza la aplicación.
• El bean implementa un servicio web.
2.3. Acceso a Beans de Sesión.
Un cliente puede acceder a un bean de sesión mediante una vista del bean o mediante una interfaz de Java.
Una vista del bean expone aquellos métodos públicos que el cliente podrá invocar, y una interfaz contendrá la
declaración de aquellos métodos que el cliente podrá invocar sobre el bean.
Un cliente solo podrá acceder a un bean de sesión a través de los métodos públicos definidos en la vista de
bean o en la interfaz. Todos los demás aspectos del bean estarán ocultos para el cliente.
Debe tenerse en cuenta la simplicidad a la hora de definir las vistas e interfaces de un bean de sesión. Si
mantenemos una interfaz simplificada, facilitaremos el mantenimiento de las aplicaciones Java EE. Una
interfaz o vista sencilla no sólo protege al cliente de la complejidad interna, sino que facilita realizar cambios

306
al EJB sin que éstos vean su interfaz o vista afectada; si bien esta buena práctica se aplica a todo el desarrollo
de software, es de especial interés en el desarrollo de EJB debido a la propiedad distributiva del mismo.
Los beans de sesión pueden tener más de una interfaz de negocio. Cuando se está diseñando una aplicación
Java EE, una de las primeras decisiones que se realizan es si el tipo de cliente que accederá al Enterprise bean
será remoto, local o un servicio web.
2.3.1. Usando EJBs en clientes.
Los clientes de un EJB pueden obtener una referencia a una instancia del bean mediante sintaxis de inyección
de dependencias o mediante una búsqueda JNDI.
La inyección de dependencias es el modo más simple para obtener una referencia a un bean. Pero para ello el
cliente debe ejecutarse sobre el mismo servidor que esté ejecutando el EJB. Para usar inyección de
dependencias se utiliza la anotación javax.ejb.EJB. Por ejemplo, si existe un EJB denominado EjbTest,
bastará con declarar una variable de instancia como la siguiente:
@EJB
EjbTest bean;
El contenedor de aplicaciones se encargará de asignar una instancia del bean a esta variable.
Aquellos clientes que se ejecuten fuera del entorno en el que se ejecuta el EJB deberán utilizar la tecnología de
búsqueda JNDI para obtener una referencia a una instancia del bean.
Se utilizan tres espacios de nombres JNDI para realizar búsquedas de beans de sesión:
•java:global, este espacio de nombres es el modo portable para encontrar EJBs usando búsquedas JNDI.
La dirección JNDI del EJB tendrá la siguiente forma:
java:global[/nombre_aplicación]/nombre_modulo/nombre_EJB[!nombre_cualificado_interfaz]
El nombre de la aplicación y del módulo por defecto se corresponde con la aplicación y módulo sin la
extensión de fichero. El nombre de aplicación sólo es necesario si la aplicación es empaquetada dentro de
un fichero EAR. El nombre cualificado de la interfaz sólo es necesario si el EJB implementa más de una
interfaz.
•java:module, este espacio de nombres se usa para búsquedas locales dentro un mismo módulo.La
dirección JNDI del EJB tendrá la siguiente forma:
java:module/nombre_EJB[!nombre_cualificado_interfaz]
El nombre de interfaz sólo es necesario si el EJB implementa más de una interfaz.
•java:app, se usa este espacio de nombres para búsquedas locales de beans empaquetados en la misma
aplicación. Esto ocurre cuando el bean está empaquetado en un fichero EAR que contiene varios módulos
Java EE. La dirección JNDI del EJB tendrá la siguiente forma:
java:app[/nombre_modulo]/nombre_EJB[/nombre_interfaz]
El nombre del módulo es opcional. El nombre de interfaz sólo es necesario si el EJB implementa más de
una interfaz.
Por ejemplo, si un bean de sesión llamado EjbTest es empaquetado dentro de un fichero de aplicación web
llamado miApp.war, el nombre del módulo es miApp. Y el nombre JNDI portable es
java:module/EjbTest. Un nombre JNDI equivalente usando el espacio de nombres java:global es
java:global/miApp/EjbTest.
2.3.2. Decidiendo entre acceso local o remoto.
Cuando se diseña una aplicación Java EE, una de las primeras decisiones es determinar el tipo de cliente que
accederá a los Enterprise Beans: locales, remotos o servicios web.
Decidir entre clientes locales o remotos depende de los siguientes factores:
• Acoplamiento fuerte o débil.
Si necesitamos un acoplamiento fuerte entre los beans deberíamos usar acceso local. Por ejemplo, un bean
de sesión puede procesar pedidos de venta y otro bean procesa mensajes de correo electrónico de
confirmación a los clientes. El primer bean utiliza los servicios del segundo, y claramente estos dos beans
están fuertemente acoplados, puesto que tras procesar un pedido se debe enviar inmediatamente una
confirmación. Por tanto el primer bean debería llamar localmente al segundo bean.
Si el acoplamiento es débil deberíamos utilizar acceso remoto. Por ejemplo, un EJB gestiona los datos de un
cliente de nuestra empresa y ofrece un método para realizar un pedido usando los datos que gestiona. Este
bean será un cliente del bean que gestiona los pedidos.
• Tipo de cliente.

307
Si un EJB debe ser accedido por aplicaciones cliente, debería permitir acceso remoto. Si los clientes del EJB
son componentes web (como servlets o páginas JSP) el tipo de acceso depende de cómo queramos
distribuir los componentes.
• Distribución de componentes.
Las aplicaciones Java EE son escalables porque sus componentes del lado cliente pueden distribuirse a
través de varias máquinas. En un escenario distribuido los EJBs deberían permitir el acceso remoto.
• Rendimiento.
Teniendo en cuenta factores de latencia y ancho de banda de una red, las llamadas remotas pueden ser más
lentas que las llamadas locales. Sin embargo, distribuir componentes entre diversos servidores de una red
puede mejorar el rendimiento global de la aplicación. En cada caso deberá valorarse el rendimiento general
de la aplicación.
Si no se está seguro del tipo de acceso que debe tener un EJB debería elegirse el acceso remoto. Esta decisión
da más flexibilidad.
Aunque es infrecuente, es posible que un EJB permita tanto el acceso local como el remoto. En este caso
debe duplicarse la interfaz del negocio, una decorada con la anotación @Remote y otra decorada con la
anotación @Local. La misma interfaz no puede ser decorada con las dos anotaciones.
2.3.3. Ejemplo de Bean de Sesión y cliente usando una vista sin interfaz.
Primero veremos cómo crear un Bean se Sesión que será accedido localmente mediante una vista del bean,
sin utilizar una interfaz. Empezaremos creando un proyecto web en NetBeans llamado WebEjbs.
El primer paso es añadir un nuevo Proyecto de tipo «Aplicación Web» de la categoría «Java Web»:
Figura 8

Tras aceptar, en el siguiente paso pondremos WebEjbs como nombre del proyecto y lo ubicaremos en una
localización:

308
Figura 9

Tras aceptar, en el siguiente paso hay que seleccionar el servidor GlassFish. GlassFish es un servidor web que
soporta un contenedor EJB, cosa que no soporta el servidor Tomcat.
Figura 10

Tras finalizar se creará un nuevo proyecto web con la estructura mostrada en el figura previa.
Ahora añadiremos un Bean de Sesión. Para ello tenemos que agregar un «Session Bean» de la categoría
«Enterprise JavaBeans».

309
Figura 11

Tras aceptar, en el siguiente paso pondremos un nombre de bean "EjbTest" y lo ubicaremos en el paquete
"bean". En este caso hemos elegido un bean de tipo Stateful, y no marcaremos ningún tipo de interfaz:
Figura 12

Tras finalizar se crea la clase EjbTest.java con el código que puede verse en la siguiente figura:

310
Figura 13

Modificaremos este código para incluir la anotación @LocalBean y para agregar un método de la lógica del
negocio. Los métodos de la lógica del negocio accesibles desde un cliente deben ser públicos. Se pueden
escribir a mano o se puede pulsar sobre el menú contextual la opción «Insertar código|Agregar Método del
Negocio». En el cuadro del diálogo «Agregar Método del Negocio» podemos crear un método indicando su
nombre, tipo de retorno, parámetro y excepciones que relanza:
Figura 14

Como ejemplo se ha creado un método llamado convertirTexto(), el cual retorna un parámetro de texto
normalizándolo a mayúsculas:
package beans;
import javax.ejb.LocalBean;
import javax.ejb.Stateful;
@Stateful
@LocalBean
public class EjbTest {
public String convertirTexto(String texto) {
return texto.trim().toUpperCase();
}
}

311
Como cliente añadiremos un servlet que invocará el método del Bean de Sesión.
package servlets;
import beans.EjbTest;
import java.io.*;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletClienteTest", urlPatterns = {"/clienteTest"})
public class ServletClienteTest extends HttpServlet {
@EJB
EjbTest ejb;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
String texto = request.getParameter("texto");
texto = texto == null ? "test" : texto;
try (PrintWriter out = response.getWriter()) {
out.println(ejb.convertirTexto(texto));
}
}
}
Este servlet simplemente recibe un parámetro de solicitud denominado texto, y lo utiliza como parámetro del
método convertirTexto(). El resultado de este método es enviado el la respuesta como texto plano.
En este escenario, al ser un acceso local, podemos utilizar la inyección de dependencias para obtener una
instancia del EJB. La variable ejb, al estar anotada con @EJB será asignada por el contenedor EJB de forma
automática cuando se invoque el servlet.
2.3.4. Ejemplo de Beans de Sesión y cliente con interfaz local.
Crearemos un nuevo Bean de Sesión para ilustrar el acceso a los beans mediante una interfaz local. En este
caso también crearemos un proyecto web llamado WebEjbLocal usando el servidor GlassFish.
Al proyecto añadiremos un Bean de Sesión. Para ello tenemos que agregar un «Session Bean» de la categoría
«Enterprise JavaBeans»:

312
Figura 15

En este caso seleccionaremos un bean de tipo «Stateless» y marcaremos la opción de interfaz «Local». Tras
finalizar se crearán los siguientes ficheros en el proyecto:
Figura 16

El fichero BeanLocalTest.java se corresponde con la clase del Bean de Session, y el fichero


BeanLocalTestLocal.java se corresponde con la interfaz local.
Añadiremos un método del negocio al bean. Podemos usar el menú contextual «Insertar código|Agregar
Método del Negocio» en la clase BeanLocalTest:

313
Figura 17

Al marcar la opción «Local» del uso de interfaces se creará la declaración de método en la interfaz y en la
clase. El resultado final debe ser el siguiente:
package beans;
import javax.ejb.Stateless;
@Stateless
public class BeanLocalTest implements BeanLocalTestLocal {
@Override
public String cambiarTexto(String texto) {
return texto.toUpperCase();
}
}
En este caso el Bean de Sesión está decorado con la anotación @Stateless.
package beans;
import javax.ejb.Local;
@Local
public interface BeanLocalTestLocal {
String cambiarTexto(String texto);
}
La interfaz local debe estar decorada con la anotación @Local. Como alternativa podemos decorar la propia
clase del bean con la anotación @Local especificando la interfaz:
package beans;
import javax.ejb.Stateless;
@Stateless
@Local(BeanLocalTestLocal.class)
public class BeanLocalTest implements BeanLocalTestLocal {
………………………………….
}
Un cliente local de un Bean de Sesión debe poseer las siguientes características:
• Debe ejecutarse en la misma Máquina Virtual Java que el bean al que accede.
• Puede ser un componente web u otro Enterprise bean.
• Para el cliente local, la ubicación del bean al que accede no es transparente.
314
Por tanto, ahora crearemos un servlet cliente en la misma aplicación web:
package servlets;
import java.io.*;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletCliente", urlPatterns = {"/cliente"})
public class ServletCliente extends HttpServlet {
@EJB
private beans.BeanLocalTestLocal ejb;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(ejb.cambiarTexto("prueba"));
}
}
}
Como vemos la técnica de acceso al Bean de Sesión permite la inyección de dependencia, pero esta vez
utilizando un variable del tipo de la interfaz local.
También podemos utilizar un acceso mediante búsquedas JNDI:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
String resultado=null;
try {
beans.BeanLocalTestLocal ejb = (beans.BeanLocalTestLocal)
new InitialContext().lookup("java:global/WebEjbLocal/BeanLocalTest");
resultado = ejb.cambiarTexto("prueba");
} catch (NamingException ex) {
resultado = ex.toString();
}
try (PrintWriter out = response.getWriter()) {
out.println(resultado);
}
}
Los posibles nombres JNDI que podemos utilizar para buscar el bean son:
java:module/BeanLocalTest
java:module/BeanLocalTest!beans.BeanLocalTestLocal
java:global/WebEjbLocal/BeanLocalTest
java:global/WebEjbLocal/BeanLocalTest!beans.BeanLocalTestLocal
2.3.5. Ejemplo de Beans de Sesión y cliente con interfaz remota.
Crearemos un nuevo Bean de Sesión para ilustrar el acceso a los beans mediante una interfaz remota. En este
caso no crearemos un proyecto web para desplegar el Bean de Sesión, si no que crearemos directamente un
proyecto de tipo «EJBModule» de la categoría «Java EE».

315
Figura 18

Tras aceptar, en el siguiente paso pondremos un nombre al proyecto, EJBTest, y una ubicación:
Figura 19

Tras aceptar, en el siguiente paso también elegiremos el servidor GlassFish:

316
Figura 20

Tras finalizar se creará un nuevo proyecto llamado «EJBTest». Antes de agregar un Bean de Sesión crearemos
un proyecto de Librería Java para poder utilizar la interfaz remota desde un cliente.
Empezaremos añadiendo un nuevo proyecto de tipo «Librería de clases Java» de la categoría «Java»:
Figura 21

Tras finalizar, tendremos dos proyectos en NetBeans:


Figura 22

317
Ahora vamos a añadir un nuevo Bean de Sesión en el proyecto EJBTest. Para ello añadiremos un «Session
Bean» de la categoría «Enterprise JavaBeans», y lo configuraremos de la siguiente manera:
Figura 23

En este caso se ha elegido una sesión de tipo Stateless y se ha marcado la opción «Remote». Al marcar esta
opción se nos solicita un proyecto de Librería Java, por tanto se selecciona el proyecto creado previamente.
Tras finalizar se agregan los siguientes archivos en los dos proyectos:
Figura 24

En el proyecto EJBTest se añade la clase del nuevo Bean de Sesión. En la librería de Java se añade la interfaz
remota.
Añadiremos un método del negocio a Bean de Sesión. Para ello utilizaremos el menú contextual «Insertar
código|Agregar Método del Negocio» en la clase SessionBeanTest:

318
Figura 25

Al marcarse la opción «Remote» del uso de interfaces se creará la declaración del método en la interfaz y en la
clase. El resultado final debe ser el siguiente:
package beans;
import javax.ejb.Stateless;
@Stateless
public class SessionBeanTest implements SessionBeanTestRemote {
@Override
public String convierteTexto(String texto) {
return texto.toUpperCase();
}
}
En este caso el Bean de Sesión está decorado con la anotación @Stateless.
package beans;
import javax.ejb.Remote;
@Remote
public interface SessionBeanTestRemote {
String convierteTexto(String texto);
}
La interfaz remota debe estar decorada con la anotación @Remote. Cada vez que modifiquemos y
compilemos el proyecto EJBTest también se compilará el proyecto JavaLibraryClienteEJB. Como
alternativa podemos decorar la propia clase del bean con la anotación @Remote especificando la interfaz:
package beans;
import javax.ejb.Stateless;
@Stateless
@Remote(SessionBeanTestRemote.class)
public class SessionBeanTest implements SessionBeanTestRemote {
………………………………….
}

319
Como hemos creado un módulo EJB y no una aplicación de despliegue, debemos desplegar manualmente el
bean de sesión en GlassFish. Podemos realizar esto desde NetBeans con la opción «Deploy» del menú
contextual del nodo de proyecto.
Figura 26

Importante. El servidor GlassFish da acceso a sus beans instalados mediante procesos oyentes que
utilizan el protocolo iiop y el puerto 3700 por defecto.
Un cliente remoto del Bean de Sesión debe poseer las siguientes características:
• Se puede ejecutar sobre una máquina y JVM diferente al del bean de sesión.
• Puede ser un componente web, una aplicación cliente u otro EJB.
• Para un cliente remoto, la localización del bean es transparente.
• El EJB debe implementar un interfaz del negocio. Los clientes remotos deben acceder siempre al bean
mediante esta interfaz.
Para simplificar crearemos como aplicación cliente un proyecto Java de consola.
Figura 27

Si queremos invocar el EJB desde la aplicación cliente se necesita referenciar:


• El archivo JAR con la interfaz remota.
• Los archivos JAR de las clases cliente del Servidor de Aplicaciones. Cada servidor de aplicaciones tiene sus
propias librerías.

320
En el caso del servidor GlassFish basta con incluir el archivo lib/gf-client.jar de la carpeta de instalación
de GlassFish. Esta librería se encarga a su vez de referenciar automáticamente a otras librerías del
subdirectorio modules/.
Importante. Si falla la carga de gf-client.jar, puede que sea necesario referenciar a mano las librerias
referenciadas dentro de su archivo de manifiesto.
La aplicación remota debe acceder al Bean de Sesión usando JNDI. Para ello debemos instanciar un objeto
InitialContext pasando las propiedades de conexión adecuadas:
package main;
import java.util.Hashtable;
import javax.naming.*;
public class Main {
public static void main(String[] args) throws NamingException {
Hashtable props = new Hashtable();
props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.enterprise.naming.SerialInitContextFactory");
props.put(Context.PROVIDER_URL, "iiop://localhost:3700");
Context ctx = new InitialContext(props);
beans.SessionBeanTestRemote ejb =
(beans.SessionBeanTestRemote) ctx.lookup("beans.SessionBeanTestRemote");
System.out.println(ejb.convierteTexto("prueba"));
}
}
Los nombres JNDI que podemos utilizar para realizar la búsqueda del bean son:
java:global/EJBTest/SessionBeanTest
java:global/EJBTest/SessionBeanTest!beans.SessionBeanTestRemote
beans.SessionBeanTestRemote
beans.SessionBeanTestRemote#beans.SessionBeanTestRemote
También podemos asignar un nombre personalizado en el Bean de Sesión:
package beans;
import javax.ejb.Stateless;
@Stateless(mappedName = "bean/test")
public class SessionBeanTest implements SessionBeanTestRemote {
…………………………………..
}
En este caso podremos usar "bean/test" para realizar la búsqueda JNDI. Este nombre anula los nombres no
portables que se generan por defecto, pero seguirán siendo válidos los nombres no portables que comienzan
por java:global.
2.3.6. Clientes remotos en contenedores web.
En una aplicación Web podemos referenciar un Bean de Sesión configurando sus propiedades de acceso en
archivos de configuración.
En el caso del servidor Tomcat debemos usar el archivo server.xml de Tomcat o el archivo context.xml de
la aplicación web para configurar el mapeado del recurso JNDI. Para permitir flexibilidad en el despliegue
deberíamos usar la interfaz remota.
Por el ejemplo, para configurar el acceso al bean SessionBeanTest usando la interfaz remota debemos
escribir el siguiente fragmento de código en el archivo server.xml o context.xml.
<Context>
<Ejb name="BeanTest" type="Session" remote="beans.SessionBeanTestRemote"/>
<ResourceParams name="BeanTest">
<parameter>
<name>factory</name>
<value>com.sun.enterprise.naming.SerialInitContextFactory</value>
</parameter>
<parameter>
<name>java.naming.provider.url</name>
<value>iiop://localhost:3700</value>
</parameter>
<parameter>
<name>jndi-name</name>

321
<value>beans.SessionBeanTestRemote</value>
</parameter>
</ResourceParams>
</Context>
En el archivo descriptorweb.xml de la aplicación web cliente se utiliza el elemento ejb-ref para declarar la
referencia al Enterprise bean:
<web-app>
<ejb-ref>
<ejb-ref-name>BeanTest</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<remote>beans.SessionBeanTestRemote</remote>
</ejb-ref>
</web-app>
Ahora ya se puede utilizar la clase InitialContext para acceder al recurso JNDI localmente:
Context ctx = new InitialContext();
Context envCtx = (Context) ctx.lookup("java:comp/env");
beans.SessionBeanTestRemote bean = (beans.SessionBeanTestRemote) envCtx.lookup("BeanTest");
2.4. Beans dirigidos por mensajes.
Un bean dirigido por mensajes (Message-Driven Bean) es un Enterprise bean que permite a las aplicaciones
Java EE procesar mensajes asincrónicamente mediante la tecnología JMS. Normalmente actúa como un
receptor de mensajes JMS, de forma que el mensaje puede ser enviado por cualquier componente Java EE, o
por una aplicación JMS, o un sistema que no use tecnologías Java EE.
Un cliente no ejecuta directamente este tipo de beans, sino que sólo debe utilizar la API JMS para enviar
mensajes. Por eso mismo un bean dirigido por mensajes no necesita interfaces. El cliente no espera que su
mensaje sea respondido si no que continúa su ejecución una vez enviado.
2.4.1. Diferencias con los beans de sesión.
La mayor de las diferencias entre los beans dirigidos por mensajes y los beans de sesión es que el cliente no
necesita una interfaz para acceder al ben dirigido por mensajes. Los beans dirigidos por mensajes solamente
constan de una clase bean. Sin embargo, un bean dirigido por mensajes a un bean de servicio stateless en que:
•No mantiene estados conversacionales.
• Todas las instancias de un bean dirigido por mensajes son equivalentes.
• Un único bean puede procesar los mensajes de varios clientes.
Las variables de instancia de un bean dirigido por mensajes pueden contener algún estado a través de la
gestión de mensajes de un cliente, como una conexión JMS, una conexión de base datos o una referencia a
otro EJB.
Los componentes clientes no localizan los beans dirigidos por mensajes e invocan sus métodos directamente.
En vez de ello el cliente accede al bean a través de JMS enviando mensajes al destino asignado por el bean
cuando es desplegado sobre el servidor GlassFish.
Los beans dirigidos por mensajes tienen las siguientes características:
• Se ejecutan con la recepción de un mensaje de un cliente.
• Son invocados asincrónicamente
• Su duración es relativamente corta.
•No representan datos compartidos directamente en una base de datos, pero pueden acceder a y actualizar
• Pueden ser conscientes de las transacciones.
• Son sin estado.
Cuando se recibe un mensaje, el contenedor llama el método onMessage()del bean para procesar el
mensaje. El método onMessage() normalmentemoldea el mensaje a alguno de los 5 tipos de mensajes JMS y
procesa el mismo de acuerdo con la lógica del negocio. El método onMessage() puede llamar a métodos de
ayuda o puede invocar un bean de sesión para procesar la información del mensaje o guardarlo en la base de
datos.
Un mensaje puede ser entregado a un bean dirigido por mensajes dentro de un contexto de transacción, de
forma que todas las operaciones dentro del método onMessage() son parte de una única transacción. Si el
proceso del mensaje dar marcha atrás, el mensaje será vuelto a entregar.

322
2.4.2. Cuándo se usan los beans dirigidos por mensajes.
Los beans de sesión permiten enviar mensajes JSM y recibirlos síncronamente pero no asíncronamente. Para
evitar el bloqueo de los recursos del servidor no deberían usarse recepciones síncronas bloqueantes en un
componente del lado servidor; en general, los mensajes JMS no deberían enviar o recibir síncronamente. Para
recibir mensajes asíncronamente debería usarse un bean dirigido por mensajes.
2.4.3. Implementación de un bean dirigido por mensajes.
Debido a que los beans dirigidos por mensajes sólo tienen como misión procesar los mensajes recibidos sin
tener una relación directa con el cliente, no es necesario crear interfaces para acceder al bean, por lo que basta
con crear una clase que implemente la interfazjavax.jms.MessageListener, para satisfacer el proceso de
mensajes.
Para ilustrar con un ejemplo el uso de un bean dirigido por mensajes, primero crearemos una cola de
mensajes en GlassFish. Véanse el capítulo inicial «Tecnología JSM» para ver cómo acceder a la consola de
administración de GlassFish y cómo crear un fabricador de conexiones JMS y una cola destino.
Figura 28

Figura 29

Podemos crear un bean dirigido por mensajes mediante NetBeans de la misma forma que se crea un bean de
sesión. Los pasos a seguir con NetBeans son los siguientes:
1) Se crea un nuevo proyecto «EJB Module» de la categoría «Java EE». Para este ejemplo se ha denominado
"EJBMensajesTest". Se debe seleccionar el servidor GlassFish.

323
2) Una vez creado el proyecto añadiremos un bean dirigido por mensajes. Para eso se agrega un «Message-
Driven Bean» de la categoría «Enterprise JavaBeans».
Figura 30

Tras aceptar, en el siguiente cuadro de diálogo del asistente debemos configurar el beans. Hay que asignarle
un nombre (en este caso MessageBeanTest) y un paquete (en este caso beans). Debemos seleccionar un
destino JMS para el proyecto, que puede ser del tipo Queue o Topic; para ello se debe pulsar el botón
«Agregar», asignar un nombre (en este caso jms/ColaDestinoTest) y el tipo de destino.
Figura 31

Tras aceptar el siguiente cuadro de diálogo nos muestras las propiedades de configuración de activación del
bean.

324
Figura 32

En este caso cambiaremos la suscripción a NON_DURABLE. Las propiedades de configuración de un bean


dirigido por mensajes son:
acknowledgeMode El modo de conocimiento del bean. Hay tres posibles valores:
• Session.AUTO_ACKNOWLEDGE: la sesión reconocerá automáticamente
la recepción de un mensaje del cliente.
• Session.CLIENT_ACKNOWLEDGE: un cliente reconoce un mensaje
llamando al método acknowledge() del mensaje.
• Session.DUPS_OK_ACKNOWLEDGE: esta opción instruye a la sesión de
reconocer perezosamente la entrega de mensajes.
destinationType Puede ser javax.jms.Queue o javax.jms.Topic.
subscriptionDurability Para suscripciones permanentes hay que asignar Durable.
subscriptionName Para suscripciones permanentes, el nombre de la suscripción.
messageSelector Un string que filtra los mensajes. Un ejemplo puede ser:
TipoNoticia = 'Deportes' OR TipoNoticia = '’Opinion'
Este selector filtra mensajes según el valor de una propiedad del mensaje
llamada TipoNoticia.
Por último se pulsa el botón de finalizar. Se crea el fichero MessageBeanTest.java:
package beans;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;
@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "clientId", propertyValue = "jms/ColaDestinoTest"),
@ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "jms/ColaDestinoTest"),
@ActivationConfigProperty(propertyName = "subscriptionName", propertyValue = "jms/ColaDestinoTest"),
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic")
})
public class MessageBeanTest implements MessageListener {
public MessageBeanTest() {
}
@Override

325
public void onMessage(Message message) {
}
}
En el método onMessage() del editor debemos escribir el código que debe ser ejecutado cuando se reciba
un mensaje. Para nuestro ejemplo simplemente se escribirá en la salida estándar el contenido del mensaje si
es de texto:
@Override
public void onMessage(Message message) {
if (message instanceof TextMessage) {
TextMessage m = (TextMessage) message;
try {
System.out.println(m.getText());
} catch (JMSException ex) {
System.out.println(ex);
}
}
}
Tras compilar el proyecto debemos desplegar el EJB en el servidor GlassFish. Para ello debemos usar la
opción «Deploy» del menú contextual sobre el nodo del proyecto.
En el momento en que la cola "jms/ColaDestinoTest" reciba un mensaje de una aplicación cliente ejecutará
automáticamente el método onMessage() del bean MessageBeanTest.
2.4.4. Cliente de un bean dirigido por mensajes.
El cliente del bean dirigido por mensajes debe ser un productor de mensajes. Un cliente para el bean creado
en el ejemplo previo deberá obtener el fabricador de conexiones y la cola de mensajes.
Una vez referenciadas la librería gf-client.jar para conexiones con el servidor GlassFish, una aplicación
cliente de cualquier tipo puede usar el siguiente código para enviar mensajes al bean.
import java.util.Properties;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.naming.*;
------------------------------
Properties props = new Properties();
props.setProperty("java.naming.factory.initial",
"com.sun.enterprise.naming.SerialInitContextFactory");
props.setProperty("java.naming.factory.url.pkgs", "com.sun.enterprise.naming");
props.setProperty("java.naming.factory.state",
"com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
props.setProperty("java.naming.provider.url", "localhost");
Context jndiContext = null;
ConnectionFactory connectionFactory = null;
Topic queue=null;
try {
jndiContext = new InitialContext(props);
connectionFactory = (ConnectionFactory) jndiContext.lookup("jms/ColaTest");
queue = (Topic) jndiContext.lookup("jms/ColaDestinoTest");
} catch (NamingException ex) {
resultado = ex.toString();
}
if (resultado == null) {
try {
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer messageProducer = session.createProducer(queue);
TextMessage message = session.createTextMessage();

326
message.setText("<<<Mensaje de prueba>>>>");
messageProducer.send(message);
resultado = "Mensaje enviado..........";
} catch (JMSException ex) {
resultado = ex.toString();
}
}
}
System.out.println(resultado);
Como resultado se ejecutará el método onMessage() del bean MessageBeanTest.
En un contenedor web como GlassFish y Tomcat podemos utilizar anotaciones para inyectar las instancias
necesarias. Por ejemplo, el siguiente servlet envía un mensaje al bean:
package servlets;
import java.io.*;
import javax.annotation.Resource;
import javax.jms.*;
import javax.servlet.*;
import javax.servlet.http.*;
@WebServlet(name = "ServletCliente", urlPatterns = {"/cliente"})
public class ServletCliente extends HttpServlet {
@Resource(mappedName = "jms/ColaTest")
private ConnectionFactory connectionFactory;
@Resource(mappedName = "jms/ColaDestinoTest")
private Topic queue;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String resultado=null;
try {
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer messageProducer = session.createProducer(queue);
TextMessage message = session.createTextMessage();
message.setText("<<<Mensaje de prueba>>>>");
messageProducer.send(message);
resultado = "Bien............";
} catch (JMSException ex) {
resultado = ex.toString();
}
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(resultado);
}
}
}

3. Ciclo de vida de EJBs e inyección de dependencias.


Un Enterprise Bean transita por varios estados durante su ciclo de vida. Cada tipo de bean (stateful, stateless,
singleton) tiene un ciclo de vida distinto. Durante ese ciclo de vida se pueden producir inyecciones de
dependencias e invocaciones a métodos de devolución de llamada.
3.1. Inyección de dependencias.
Inyección de Contextos y Dependencias (CDI) para la plataforma Java EE es una funcionalidad que ayuda a
instanciar automáticamente objetos del negocio en las capas web y de transacciones de la plataforma Java EE.
CDI es un conjunto de servicios que, usados a la vez, hacen más sencillo desarrollar Enterprise beans.
Importante. Una aplicación que use CDI debe tener un fichero llamado «beans.xml». Este fichero puede
estar completamente vacío, pero debe estar presente. Para una aplicación Web el fichero «beans.xml» debe
estar en el directorio WEB-INF; para módulos EJB modules o ficheros JAR debe estar en el directorio
META-INF.

327
3.1.1. Inyección de dependencias basadas en nombre.
El contenedor de EJBs cumple con el patrón de inyección de dependencias. Mediante el uso de anotaciones
se pueden decorar ciertas clases de servicio para que sean instanciadas automáticamente dentro de un bean.
Las clases o interfaces que deben ser inyectadas deben decorarse con la anotación @Model, y se usa la
anotación @Inject dentro de la clase del bean.
Por ejemplo, supongamos la siguiente clase de servicio:
package beans;
import javax.enterprise.inject.Model;
@Model
public class Servicio {
public String info() {
return "Servicio";
}
}
Se necesita utilizar una instancia de esta clase en un bean singleton. Podemos declarar un variable de este tipo
anotándola con @Inject:
package beans;
import javax.ejb.Singleton;
import javax.inject.Inject;
@Singleton
public class SessionBean {
@Inject
private Servicio servicio;
@Override
public String informa() {
return servicio.info();
}
}
Si aplicamos el patrón de inyección de dependencias mediante el uso de una interfaz, podemos crear una
interfaz que proporcione las funcionalidades del servicio:
package beans;
import javax.enterprise.inject.Model;
@Model
public interface Servicio {
public String info();
}
En esta caso anotamos la propia interfaz con @Model. Y ahora creamos una implementación concreta del
servicio:
package beans;
import javax.enterprise.inject.Model;
import javax.inject.Named;
@Model
@Named("servicio")
public class ServicioImpl implements IServicio {
@Override
public String info() {
return "ServicioImpl";
}
}
En esta caso anotamos la clase con @Model y con @Named. La anotación @Named permite proporcionar
un nombre que nos permitirá referenciar esta implementación concreta cuando la inyectemos en el bean. Este
nombre será utilizado incluso en expresiones EL.
package beans;
import javax.ejb.Singleton;
import javax.inject.Inject;
import javax.inject.Named;
@Singleton
public class SessionBean {
@Inject @Named("servicio")

328
private Servicio servicio;
@Override
public String informa() {
return servicio.info();
}
}
3.1.2. Inyección de dependencias usando cualificadores.
La especificación CDI no recomienda del uso de resolución de dependencias basadas en un nombre:
«...El uso de @Named como un cualificador de inyección no es recomendado, excepto en casos de
integración con código comprobado que usa nombres para identificar beans…»
Supongamos que queremos inyectar una clase de servicio como:
package beans;
import javax.enterprise.inject.Model;
@Model
public class ServicioTest {
public String info() {
return "ServicioTest";
}
}
Esta clase se puede inyectar simplemente con:
@Inject
beans.ServicioTest srv;
De forma implícita la clase ServicioTest es cualifica con la anotación javax.enterprise.inject.Default, y la
inyección aplica la anotación @Default. Si queremos inyectar una subclase de ServicioTest en la variable
srv, podemos crear un cualificador personalizado de la siguiente manera:
package beans;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface TipoSub {
}
Al ser decorada con la anotación @Qualifier, la anotación TipoSub podrá ser aplicada para cualificar una
clase, un método, un campo y un parámetro. Ahora podemos crear una subclase de ServicioTest cualificada
con esta anotación:
package beans;
import javax.enterprise.inject.Model;
@TipoSub
@Model
public class SubServicioTest extends ServicioTest {
@Override
public String info() {
return "SubServicioTest";
}
}
Ahora podemos inyectar esta subclase usando su cualificador:
@Inject @TipoSub
beans.ServicioTest srv;
3.1.3. Uso de ámbitos para inyección de dependencias.
Para que una aplicación web use un bean que inyecte otras clases de beans, debe ser capaz de mantener su
estado durante la interacción del usuario con la aplicación. El modo de definir este estado es dar un ámbito al
bean. En la siguiente tabla se describen los ámbitos posibles:
Alcance Anotación Duración
Request @RequestScoped La interacción se mantiene durante la solicitud HTTP.
329
Session @SessionScoped La interacción se mantiene durante la sesión actual.
Application @ApplicationScoped Se comparte la interacción con todos los usuarios de la aplicación.
Dependent @Dependent Es el ámbito por defecto y no hace falta especificarlo; significa que un
objeto existe para servir a un único cliente y que tiene el mismo ciclo
de vida del cliente.
En un ámbito da a un objeto de ciclo de vida bien definido un contexto. El objeto será automáticamente
creado cuando sea solicitado y automáticamente será destruido cuando el contexto donde fue definido
finalice. Es más, el estado es automáticamente compartido por cualquier cliente que se ejecute en el mismo
contexto.
Los componentes Java EE, como servlets y Enterprise beans, y componentes JavaBeans no tiene un ámbito
de ciclo de vida bien definido y no se les puede aplicar esta anotaciones. Estos componentes son los
siguientes:
• Singleton, como un beans de sesión singleton, tiene un estado compartido por todos los clientes.
• Objetos sin estado, como los servlets y beans de sesión stateless, no tienen un estado visible para el
cliente.
• Objetos que deben ser creados y destruidos explícitamente por los clientes, como los componentes
JavaBeans y beans de sesión stateful, tienen un estado compartido entre clientes.
Sin embargo, si creamos un componente Java EE que sea un bean administrado, se convierte en un objeto de
ámbito el cual existirá en un contexto que controlará su ciclo de vida.
Por ejemplo, si queremos aplicar inyecciones a un bean de servicio en al ámbito de sesión se utiliza:
package beans;
import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.Model;
@Model
@SessionScoped
public class Servicio implements Serializable {
}
Para mantener al bean en el ámbito de sesión se solicita que la clase sea serializable.
3.1.4. Inyección de objetos que no son beans.
Los métodos de producción proporcionan un modo de inyectar objeto que no son beans, objetos cuyo valor
puede modificarse en tiempo de ejecución, y objetos que requieren una inicialización personalizada. Por
ejemplo, podemos incluir en la clase Servicio un método que devuelve un número:
package beans;
import java.io.Serializable;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.Model;
import javax.enterprise.inject.Produces;
import javax.inject.Named;
@Model
public class Servicio implements Serializable {
@Produces @Named("valor")
public int valor() {
return 100;
}
}
La anotación @Produces convierte al método valor() en un método de producción, en esta caso cualificado
con un nombre string.
En la clase de un bean podemos inyectar un objeto de esta clase y definir una variable entera que también sea
inyectado mediante el método Servicio.valor().
@Inject @TipoSub
Servicio srv;
@Inject @Named("valor")
int valor;

330
Cuando se inyecta el objeto Servicio en un bean administrado, el contenedor automáticamente invoca el
método productor para inicializar la variable valor.
3.2. Métodos de devolución de llamada del ciclo de vida de los beans.
Los métodos del negocio anotados con javax.ejb.Remove en los beans de sesión stateful pueden ser
invocados por los clientes para eliminar la instancia del bean. El contenedor eliminará el bean después de que
el método anotado con @Remove se complete, tanto normalmente como anormalmente.
Por ejemplo, un bean de sesión stateful puede necesitar trabajar con un fichero. Abre el fichero cuando el
bean es instanciado y garantiza que se cierre al ser removido.
package beans;
import java.io.*;
import javax.annotation.PostConstruct;
import javax.ejb.Remove;
import javax.ejb.Stateful;
@Stateful
public class SessionBean {
private FileReader fichero;
@PostConstruct
public void creacion() {
try {
fichero = new FileReader("datos.txt");
} catch (FileNotFoundException ex) {
}
}
@Remove
public void destruccion() {
if (fichero != null) {
try {
fichero.close();
} catch (IOException ex) {
}
}
}
……………………..
}
Un método de un EJB puede ser declarado como un método de devolución de llamada del ciclo de vida del
bean si es decorado con las siguientes anotaciones:
• javax.annotation.PostConstruct: Los métodos anotados con @PostConstruct son invocados por el
contenedor EJB cuando se instancia el bean después de que todas las inyecciones de dependencias se han
completado y antes de que sea invocado un método del negocio del bean.
• javax.annotation.PreDestroy: Los métodos anotados con @PreDestroy son invocados después de
cualquier método anotado con @Remove se haya completado y antes de que el contenedor elimine el
bean.
• javax.ejb.PrePassivate: Los métodos anotados con @PrePassivate son invocados por el contenedor
antes de que el bean pase a un estado pasivo. Esto significa que el contenedor elimina el bean
temporalmente del entorno y lo guarda en un almacén secundario.
• javax.ejb.PostActivate: Los métodos anotados con @PostActivate son invocados por el contenedor
después de que el contenedor mueva el bean desde un almacén secundario para activarlo.
Los métodos del ciclo de vida deben retornar void y no tener parámetros.
3.3. Ciclo de vida de los beans.
Un Enterprise bean pasa por diferentes etapas durante su vida útil, o ciclo de vida. Cada tipo de bean (con
estado de sesión, sin estado, singleton o controlado por mensajes) tiene un ciclo de vida diferente.
3.3.1. Ciclo de vida de un Bean de Sesión Stateful:
La siguiente figura muestra los estados que un bean de sesión transita durante su ciclo de vida.

331
Figura 33

El cliente inicia el ciclo de vida obteniendo una referencia del bean. El contenedor EJBinyecta las
dependencias necesarias y luego invoca al método anotado con @PostConstruct, si existe.
Mientras esté en la etapa "funcionando", el contenedor EJB puede decidir pausar el bean, moviéndolo de la
memoria al almacenamiento secundario (usualmente, el disco rígido). El contenedor EJB invoca el método
anotado con @PrePassivate, si existe, inmediatamente antes de pausarlo. Si el cliente invoca un método de
negocio mientras el EJB está en el estado pausado, el contenedor ejecutará el método anotado con
@PostActivate, si existe, y luego mueve al bean al estado "funcionando".
Hay tres maneras de que termine el ciclo de vida de un stateful session bean:
1) Que se llame a un método anotado con @Remove, lo que determina que se destruya el bean.
2) Que se llame al método remove().
3) Que un método de negocios del bean lance una RuntimeException.
Al final del ciclo de vida, el contenedor EJB ejecuta el método anotado con @PreDestroy, si existe. Luego
de esto, el bean está listo para ser recolectado por el recolector de basura y no puede volver a llamarse salvo
que se cree una nueva instancia del mismo.
3.3.2. Ciclo de vida de un Bean de Sesión Stateless.
Debido a que un bean Stateless nunca es pausado, su ciclo de vida tiene solo dos etapas: "no existe" y
"funcionando".
Figura 34

El cliente inicia el ciclo de vida cuando obtiene una referencia al bean Stateless. El contenedor inyecta las
dependencias y luego invoca al método anotado con @PostConstruct, si existe. Al final del ciclo de vida, el
contenedor EJB llama al método anotado con @PreDestroy, si existe. Luego de esto, la instancia del bean
está lista para ser recolectada por el recolector de basura.
3.3.3. Ciclo de vida de un Bean de SesiónSingleton.
Al igual que un bean de sesión stateless, un bean singleton nunca pasa a un estado pasivo y tienen sólo dos
estados, "no existe" y "funcionando" para la invocación de sus métodos del negocio.
El contenedor EJB inicia el ciclo de vida del bean singleton creando una instancia; esto ocurre cuando se
despliega la aplicación si el singleton está anotado con @Startup. El contenedor inyecta cualquier
dependencia e invoca el método anotado con @PostConstruct, si existe. El bean singleton está ahora listo
para responder a las invocaciones de los clientes.
Al final de su ciclo de vida, el contenedor EJB llama al método anotado con @PreDestroy, si existe. Y el
bean está listo para ser recolectado.
3.3.4. El ciclo de vida de un Message-Driven Bean.
La siguiente figura ilustra los estados del ciclo de vida de un message-driven bean.

332
Figura 35

El contenedor EJB normalmente crea un grupo de instancia del message-driven bean. Para cada instancia el
contenedor EJB realiza las siguientes tareas:
• Si el bean usa inyección de dependencias, el contenedor las inyecta antes de crear la instancia.
• El contenedor llama al método @PostConstruct, si existe.
Al igual que los beans de sesión stateless, un message-driven bean no pasa nunca al estado pasivo y solo tiene
dos estados: "no existe" y "funcionando".
Al final de su ciclo de vida, el contenedor llama al método anotado con @PreDestroy, si existe.Y el bean
está listo para ser recolectado.
3.4. Interceptores.
Un interceptor es una clase que se usa para interponerse en la invocación de un método o un evento del ciclo
de vida de una clase bean. El interceptor realiza tareas de registro o auditoria, que son independientes de la
lógica del negocio. Los interceptores también permiten especificar el código de estas tareas en un lugar de
sencillo mantenimiento.
Una clase interceptora a menudo contiene un método anotado con @AroundInvoke, el cual especifica las
tareas que el interceptor provocará cuando intercepte método invocados. También puede contener un
método anotado con @PostConstruct, @PreDestroy, @PrePassivate, o @PostActivate, para especificar
el ciclo de vida de los método de devolución de llamada del interceptor, y un método anotado con
@AroundTimeout, para especificar el límite de tiempo del interceptor. Una clase interceptora puede
contener más de un método interceptor, pero sólo puede tener un método de cada tipo.
Junto con un interceptor, una aplicación define uno o más tipos de enlace de interceptor, que son anotaciones
que asocian un interceptor con beans o métodos destino. Por ejemplo, la siguiente anotación Logged, es un
enlace de interceptor cuyo destino serán métodos o beans:
package beans;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;
@Inherited
@InterceptorBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface Logged {
}
Una declaración de enlace de interceptor debe ser anotada con import
javax.interceptor.InterceptorBinding. También debe tener la anotación
java.lang.annotation.Inherited, para especificar que puede ser heredada desde una superclase.
Una clase interceptora es anotada con el enlace de interceptor mediante la anotación @Interceptor. Por
ejemplo:
package beans;
import java.io.Serializable;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
@Logged
@Interceptor

333
public class LoggedInterceptor implements Serializable {
public LoggedInterceptor() {
}
@AroundInvoke
public Object logMetodo(InvocationContext invocationContext)throws Exception {
System.out.println("Método entrante: "
+ invocationContext.getMethod().getName() + " en la clase "
+ invocationContext.getMethod().getDeclaringClass().getName());
return invocationContext.proceed();
}
}
Cada método @AroundInvoke tiene un argumento de tipo javax.interceptor.InvocationContext,
retorna un java.lang.Object, y lanza un Exception. La clase interceptora debe ser declarada en el fichero
«beans.xml»:
FICHERO «beans.xml»
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<interceptors>
<class>beans.LoggedInterceptor</class>
</interceptors>
</beans>
Una vez definidos el interceptor y el tipo de enlace, podemos anotar beans y métodos individuales con el tipo
de enlace para especificar que el interceptor sea invocado sobre todos los métodos del bean o sobre métodos
específicos. Por ejemplo, podemos anotar una clase con métodos de servicio para un servlet:
package beans;
import java.io.Serializable;
import javax.enterprise.inject.Model;
@Logged
@Model
public class Servicio implements Serializable {
public String info() {
return "Servicio";
}
}
Podemos ahora usar esta clase de servicio en un servlet:
package servlets;
import beans.Servicio;
import java.io.*;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletCliente", urlPatterns = {"/cliente"})
public class ServletCliente extends HttpServlet {
@Inject
Servicio srv;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(srv.info());
}
}
}

334
Si ejecutamos el servlet veremos que se genera un mensaje de log en la salida estándar con el nombre y clase
del método cuya ejecución ha provocado la intercepción.
Información:Método entrante: info en la clase beans.Servicio
En vez de decorar toda la clase se pueden decorar sólo determinados métodos para su intercepción:
package beans;
import java.io.Serializable;
import javax.enterprise.inject.Model;
@Model
public class Servicio implements Serializable {
@Logged
public String info() {
return "Servicio";
}
}

4. Servicio de temporizador en beans


El servicio de temporizador del contenedor de EJBs permite planificar notificaciones para todos los tipos de
Enterprise beans excepto para los beans de sesión stateful. Se puede programar una notificación de acuerdo a
un calendario concreto, a una hora específica, después de transcurrido un tiempo, o a intervalos de tiempo.
Por ejemplo, se puede configurar un temporizador para que ejecute uno de los métodos de un bean a las
10:30 horas del 23 de mayo, o bien para que se ejecute dentro de 30 días, o bien que se ejecute cada 12 horas.
Los temporizadores para EJBs se pueden programar mediante código o automáticamente. Los
temporizadores programáticos son fijados mediante llamadas explícitas a los métodos de creación de la
interfaz TimerService. Los temporizadores automáticos son creados en la implementación del bean
anotando métodos con java.ejb.Schedule o java.ejb.Schedules.
4.1. Creación de un EJB con servicio de temporizador.
Para ilustrar el uso del servicio de temporización usaremos NetBeans para crear un Bean de Sesión que utilice
este servicio. En un proyecto de módulo EJB incluiremos un «Timer Session Bean» de la categoría
«Enterprise JavaBeans»:
Figura 36

Tras aceptar, el siguiente cuadro de diálogo nos solicita la configuración del bean. Para este ejemplo se crea
un bean de sesión de tipo stateless sin interfaces.

335
Figura 37

Tras aceptar y finalizar se genera el siguiente fichero TimerSessionBean.java:


package beans;
import java.util.Date;
import javax.ejb.Schedule;
import javax.ejb.Stateless;
import javax.ejb.LocalBean;
@Stateless
@LocalBean
public class TimerSessionBean {
@Schedule(dayOfWeek = "Mon-Fri", month = "*", hour = "9-17", dayOfMonth = "*",
year = "*", minute = "*", second = "0")
public void myTimer() {
System.out.println("Timer event: " + new Date());
}
// Add business logic below. (Right-click in editor and choose
// "Insert Code > Add Business Method")
}
Mediante la anotación @Schedule se configura la temporización del bean de sesión asignando uno o varios
atributos de calendario. Modificaremos este bean para programar la invocación automática del método
myTimer() cada 6 segundos:
@Schedule(second = "0/6")
public void myTimer() {
System.out.println("Timer event: " + new Date());
}
A continuación veremos cómo configurar los atributos de calendario.

336
4.2. Temporizadores automáticos.
Se crean temporizadores automáticos por el contenedor de EJBs cuando un Enterprise bean que contiene
métodos anotados con @Schedule o @Schedules se instala.
4.2.1. Configuración de los temporizadores automáticos.
Los temporizadores automáticos se pueden configurar mediante anotaciones o mediante el archivo descriptor
de despliegue «ejb-jar.xml».
Se añaden anotaciones @Schedule sobre un método cuya invocación queramos invocar automáticamente.
Como se ha visto la anotación @Schedule tiene atributos para asignar una expresión de calendario. También
tiene un atributo persistent, que es opcional, y que indica si el temporizador debe persistir cuando se reinicie
o caiga el servidor. Por defecto, todos los temporizadores automáticos son persistentes.
En el ejemplo de inicio de este capítulo se ha creado un bean de sesión TimerSessionBean con
temporización automática:
@Stateless
@LocalBean
public class TimerSessionBean {
@Schedule(second = "0/6")
public void myTimer() {
System.out.println("Timer event: " + new Date());
}
}
Para configurar, como alterativa, este bean y su temporizador mediante el archivo descriptor «ejb-jar.xml»,
primero debemos añadirlo al proyecto. En NetBeans se puede utilizar la plantilla «Estándar Deployment
Descriptor» de la categoría «Enterprise JavaBeans». Este fichero se instala en la carpeta WEB-INF de la
aplicación web.
FICHERO «ejb-jar.xml»
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="3.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/ejb-jar_3_2.xsd">
<enterprise-beans>
<session>
<ejb-name>MessageService</ejb-name>
<local-bean/>
<ejb-class>beans.TimerSessionBean</ejb-class>
<session-type>Stateless</session-type>
<timer>
<schedule>
<second>0/6</second>
<minute>*</minute>
<hour>*</hour>
</schedule>
<timeout-method>
<method-name>myTimer</method-name>
</timeout-method>
</timer>
</session>
</enterprise-beans>
</ejb-jar>
4.2.2. Creación de temporizadores basados en calendarios.
Los temporizadores se pueden asignar de acuerdo a un planificador basado en calendario, con una sintaxis
similar a la de la utilidad cron de UNIX. Tanto los temporizadores programáticos como los automáticos
pueden usar expresiones de temporización basadas en un calendario.
La siguiente tabla muestra los atributos de un temporizador basado en calendario:
Atributo Descripción Valores
second Uno o más segundos dentro de un minuto. 0 a 59.
337
Por ejemplo: second="30".
minute Uno o más minutos dentro de una hora. 0 a 59.
Por ejemplo: minute="15".
hour Una o más horas dentro de un día. 0 a 23. Por ejemplo: hour="18".
dayOfWeek Uno o más días dentro de una semana. 0 (domingo) a 7 (lunes).
Por ejemplo: dayOfWeek="3".
dayOfMonth Uno o más días dentro del mes. 0 a 31.
-7 a -1, para días antes del fin del mes.
Last, para el último día del mes.
Por ejemplo: dayOfMonth="3".
month Uno o más meses dentro de un año. 1 a 12.
Por ejemplo: month="6".
year Un año del calendario. Por ejemplo: year="2014".
En algunos de estos atributos se puede especificar el valor "*" para indicar cualquier valor posible.
Especificando una lista y un rango de valores.
Para especificar uno o más valores para un atributo se usa una coma para separar los valores. Se pueden
especificar rangos de valores separando el mínimo y el máximo con un guion.
Por ejemplo, una expresión que asigne el día de la semana a miércoles y jueves sería así:
dayOfWeek="Tue, Thu"
La siguiente expresión representa las 4:00 a.m. horas, cada hora desde las 9:00 a.m. a las 5:00 p.m., usando un
rango, y las 10:00 p.m.:
hour="4,9–17,22"
La siguiente expresión representa un intervalo del viernes al lunes:
dayOfWeek="5–1"
Especificando intervalos.
La barra inclinada (/) restringe un atributo a un punto inicial y a un intervalo, y se usa para especificar cada
segundos, minutos o horas dentro de los minutos, horas o días respectivamente. Una expresión de x/y,
expresa que x es un punto inicial e y representa el intervalo. Se puede usar el caracter comodín (*) en la
posición x y es equivalente a asignar x a cero.
La siguiente expresión representa cada 10 minutos dentro de una hora:
minute="*/10"
Y por tanto es equivalente a:
minute="0,10,20,30,40,50"
La siguiente expresión representa cada 2 horas empezando al anochecer:
hour="12/2"
4.3. Creación de temporizadores programáticamente.
Se crean temporizadores programáticamente para establecer la invocación del método anotado con
@Timeout en un momento dado. Para crear un temporizador, el bean invoca uno de los métodos create()
de la interfaz TimerService. Estos métodos permiten acciones simples, intervalos o temporizadores basados
en calendarios para ser creados.
Se puede instanciar un objeto javax.ejb.TimerService dentro de un bean usando inyección de dependencias
de la siguiente manera:
@Resource
private TimerService timerService;
4.3.1. Método de programación.
Cuando un planificador programático expira, el contenedor llama al método anotado con @Timeout en la
implementación de la clase del bean. El método anotado con @Timeout contendrá la lógica del negocio para
gestionar el evento del planificador. Deben ser métodos void y pueden tomar un parámetro opcional de tipo
javax.ejb.Timer. No pueden lanzar excepciones explícitamente.
El siguiente método puede ser añadido al bean TimerSessionBean para controlar su finalización:
import java.util.Date;
import javax.ejb.Schedule;

338
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
@Stateless
public class TimerSessionBean {
…………………….
@Timeout
public void timeout(Timer timer) {
System.out.println("TimerSessionBean: tiempo de planificación finalizado");
}
}
4.3.2. Programación del temporizador.
Para una acción simple o un intervalo la expiración del tiempo puede ser expresada como una duración o un
tiempo absoluto. La duración es expresada como el número de milisegundos antes de que el evento sea
lanzado. Para especificar un tiempo absoluto se puede crear un objeto java.util.Date y pasarlo al método
TimerService.createSingleActionTimer() o al método TimerService.createTimer().
El siguiente código asigna un temporizador programático que expirará en 1 minuto (6000 milisegundos):
long duracion = 6000;
javax.ejb.Timer timer = timerService.createSingleActionTimer(duracion, new TimerConfig());
El siguiente código asigna un temporizador programático que expirará a las 12:05 p.m. del 1 de mayo del
2014, especificad como un java.util.Date:
SimpleDateFormatter formatter = new SimpleDateFormatter("dd/MM/yyyy HH:mm");
Date date = formatter.parse("01/05/2014 12:05");
Timer timer = timerService.createSingleActionTimer(date, new TimerConfig());
Para temporizadores basados en calendarios, el tiempo de expiración es expresado como un objeto
javax.ejb.ScheduleExpression, pasado como argumento del método
TimerService.createCalendarTimer(). La clase ScheduleExpression representa una expresión de
calendario y tiene métodos que corresponden a los atributos descritos previamente.
El código siguiente crea un temporizador programático usando la clase de ayuda ScheduleExpression:
ScheduleExpression schedule = new ScheduleExpression();
schedule.dayOfWeek("Mon");
schedule.hour("12-17, 23");
Timer timer = timerService.createCalendarTimer(schedule);
4.3.3. Ejemplo de temporalización.
Para ver cómo se unifica todo este código crearemos un bean de sesión singleton que permitirá programar
una notificación cada 6 segundos:
package beans;
import java.util.Date;
import javax.annotation.Resource;
import javax.ejb.Schedule;
import javax.ejb.Singleton;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerService;
@Singleton
public class TimerSessionBean {
@Resource
private TimerService timerService;
// MÉTODO PARA ARRANCAR EL SERVICIO DE PROGRAMADOR
public void arrancaTimer() {
TimerConfig config = new TimerConfig();
timerService.createIntervalTimer(6000, 6000, config);
}
// MÉTODO PARA PARAR EL SERVICIO DE PROGRAMADOR
public void paraTimer() {
for (Timer timer : timerService.getAllTimers()) {
timer.cancel();

339
}
}
// MÉTODO PROGRAMADO
@Timeout
public void timeout(Timer timer) {
System.out.println("TimerSessionBean: tiempo de planificación finalizado");
}
}
Basta con utilizar este bean desde un cliente e invocar su método arracaTimer() para iniciar la invocación
del método timeout() cada 6 segundos. Es conveniente que el cliente invoque el método paraTimer() para
detener la programación.

5. EJBs como contextos de persistencia


Los datos persistentes son información que puede sobrevivir al programa que los crea. La mayoría de los
programas complejos utilizan datos persistentes: las aplicaciones de escritorio la necesitan para almacenar las
preferencias del usuario a través de invocaciones de programas, las aplicaciones web deben hacer seguimiento
de los movimientos de los usuarios y los pedidos realizados en largos períodos de tiempo, etc.
La persistencia ligera es el almacenamiento y recuperación de datos persistentes con poco o ningún trabajo
por parte del desarrollador. La tecnología EJB se integra perfectamente con la tecnología Java Persistence
para crear contextos de persistencia ligeros.
5.1. Beans de sesión para clases de entidad.
Para ilustrar el uso de Java EE integrando beans de sesión y clases de entidad para Java Persistence,
crearemos una base de datos con Java Derby usando el entorno de desarrollo NetBeans:
Figura 38

Comenzaremos creando un proyecto Web usando el servidor GlassFish y añadiendo una clase de entidad
para mapear la tabla CLIENTE de la base de datos.

340
Figura 39

En el siguiente cuadro de diálogo crearemos un DataSource asociado a la base de datos EMPRESA. Hay que
desplegar la lista «Data Source» y pulsar sobre la opción «Nuevo Data Source». En el cuadro de diálogo
«Crear Data Source» hay que darle un nombre y seleccionar la cadena de conexión apropiada:
Figura 40

Tras aceptar el nuevo Data Source "bd/Negocio" en el siguiente paso hay que seleccionar la tabla CLIENTE.

341
Figura 41

En el siguiente paso crearemos las clases de entidad en el paquete ejb, y seleccionaremos la opción para crear
la unidad de persistencia.
Figura 42

Tras finalizar se creará el fichero Cliente.java y el fichero de configuración persistence.xml.

342
Figura 43

Ahora crearemos un bean de sesión para gestionar la clase de entidad Cliente. Para ello hay que elegir
«Session Beans for Entity Classes» en la categoría «Persistence».
Figura 44

En el siguiente paso hay que seleccionar la clase Cliente.

343
Figura 45

En el siguiente paso, para simplificar el ejemplo, crearemos un bean en el paquete ejb sin utilizar interfaces.
También se puede optar por crear una interfaz Local, o una interfaz Remota si el bean debe ser accedido por
clientes remotos.
Figura 46

Al finalizar se crearán dos clases en el paquete ejb:


package ejb;
import java.util.List;
import javax.persistence.EntityManager;
public abstract class AbstractFacade<T> {
private Class<T> entityClass;
public AbstractFacade(Class<T> entityClass) {
this.entityClass = entityClass;
}
protected abstract EntityManager getEntityManager();
public void create(T entity) {
getEntityManager().persist(entity);
}

344
……………………………..
}
La clase AbstractFacade es una clase abstracta genérica que ofrece la funcionalidad básica para realizar
operaciones de creación, borrado, actualización y consultas sobre una clase de entidad.
package ejb;
import entidad.Cliente;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Stateless
public class ClienteFacade extends AbstractFacade<Cliente> {
@PersistenceContext(unitName = "WebEJBPersistencePU")
private EntityManager em;
@Override
protected EntityManager getEntityManager() {
return em;
}
public ClienteFacade() {
super(Cliente.class);
}
}
La clase ClienteFacade es el bean de sesión específico para gestionar entidades Cliente. Al ser una subclase
de AbstractFacade hereda la funcionalidades básicas de gestión, y simplemente tendremos que añadir
nuevos métodos para cumplir con las reglas de negocio de gestión de clientes.
El contenedor Web de GlassFish crea un contexto de persistencia que permite inyectar una instancia de la
unidad de persistencia mediante la anotación javax.persistence.PersistenceContext:
@PersistenceContext(unitName = "WebEJBPersistencePU")
private EntityManager em;
5.2. Transacciones.
Una típica aplicación Java EE accede a y almacena información en una o más bases de datos. Si la
información es crítica para las operaciones del negocio debe ser exacta, actualizada y confiable. La integridad
de los datos se perdería si se permitiese que varios programas actualizasen la misma información al mismo
tiempo o si un sistema que ha fallado al procesar una transacción de negocios fuera a dejar los datos afectados
sólo parcialmente actualizados.
Las transacciones de software garantizan la integridad de los datos y controlan el acceso simultáneo de datos
por varios programas. En caso de un fallo del sistema, las transacciones se aseguran de que después de la
recuperación, los datos estarán en un estado coherente.
5.2.1. Transacciones administradas por el contenedor EJB.
En un Enterprise bean con un contenedor que gestiona transacciones el contenedor gestiona los límites de las
transacciones. Se pueden utilizar transacciones gestionadas por contenedor para cualquier tipo de bean de
sesión y dirigido por mensajes. El contenedor permite el uso de anotaciones para limitar el contexto de la
transacción.
Normalmente el contenedor inicia una transacción inmediatamente antes de que el método de un bean
comience y confirma la transacción justo antes de que el método finalice. Cada método se puede asociarse
con una única transacción; las transacciones anidadas o múltiples no están permitidas dentro de un método.
Las transacciones administradas por el contenedor no requieren de todos los métodos asociados con las
transacciones. Al desarrollar un bean se pueden establecer atributos de transacción para especificar cuáles de
los métodos del bean estarán asociados con las transacciones.
Los beans que utilicen transacciones gestionadas por contenedor no deben utilizar ningún método de gestión
de transacciones que interfieran con los límites de demarcación de las transacciones del contenedor.
5.2.2. Atributos de transacción.
Los atributos de transacción controlan los límites de una transacción. Esto es importante si se da un caso
como el que se ilustra a continuación:

345
Figura 47

Un atributo de transacción controla los límites de una transacción sobre el método metodoA(), pero este
método invoca otro método metodoB() también controlado por un atributo de transacción. Cuando
metodoA() ejecuta metodoB(), ¿se crea una nueva transacción? La respuesta depende del atributo de
transacción aplicado sobre metodoB().
Los atributos de transacción pueden ser alguno de los siguientes:
• Required
Si el cliente se está ejecutando dentro de una transacción e invoca el método del bean, el método de ejecuta
dentro de la transacción del cliente. Si el cliente no está asociado con una transacción, el contenedor inicia
una nueva transacción antes de ejecutar el método.
Es el atributo implícito para todos los métodos de los beans que se ejecutan en la demarcación de una
transacción del contenedor.
• RequiresNew
Si el cliente se ejecuta dentro de una transacción e invoca el método del bean, el contenedor sigue los
siguientes pasos:
- Suspende la transacción del cliente.
- Inicia una nueva transacción.
- Delega la llamada al método.
- Reanuda la transacción del cliente después de que el método se completa.
Si el cliente no está asociado a una transacción el contenedor inicia una nueva transacción antes de ejecutar
el método.
• Mandatory
Si el cliente se ejecuta dentro de una transacción e invoca el método del bean, el método se ejecuta dentro
de la transacción del cliente. Si el cliente no está asociado a una transacción el contenedor lanza una
TransactionRequiredException.
• NotSupported
Si el cliente se ejecuta dentro de una transacción e invoca el método del bean, el contenedor suspende la
transacción del cliente antes de invocar el método. Después de que el método se completa, el contenedor
reanuda la transacción del cliente.
Si el cliente no está asociado a una transacción el contenedor no inicia una nueva transacción antes de
ejecutar el método.
Se usa este atributo para métodos que no necesitan transacciones. Dado que las transacciones involucran
sobrecargas, este atributo aumenta el rendimiento.
• Supports
Si el cliente se ejecuta dentro de una transacción e invoca el método del bean, el método se ejecuta dentro
de la transacción del cliente. Si el cliente no está asociado a ninguna transacción, el contenedor no inicia una
nueva transacción antes de ejecutar el método.
• Never
Si el cliente se ejecuta dentro de una transacción e invoca el método del bean, el contenedor lanza una
RemoteException. Si el cliente no está asociado a ninguna transacción, el contenedor no inicia una nueva
transacción antes de ejecutar el método.
La siguiente tabla resume la funcionalidad de los atributos de transacción:
Atributo Transacción cliente Transacción método
Required No T2
Required T1 T1
RequiresNew No T2
RequiresNew T1 T2

346
Mandatory No Error
Mandatory T1 T1
NotSupported No No
NotSupported T1 No
Supports No No
Supports T1 T1
Never No No
Never T1 Error
Los atributos de transacción son especificados en la anotación javax.ejb.TransactionAttribute y se asignan
con una de las constantes de javax.ejb.TransactionAttributeType.
Si se aplica @TransactionAttribute a la clase, todos los métodos de la clase se ejecutarán con transacciones.
Si se aplica @TransactionAttribute a un método sólo el método será transaccional. Si se aplica tanto a la
clase como a un método, el atributos sobre el método reescribe el atributo sobre la clase.
En siguiente código muestra cómo utilizar la anotación @TransactionAttribute.
@TransactionAttribute(NOT_SUPPORTED)
public abstract class AbstractFacade<T> {
…………………………………
@TransactionAttribute(REQUIRED)
public void create(T entity) {
getEntityManager().persist(entity);
}
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public void remove(T entity) {
getEntityManager().remove(getEntityManager().merge(entity));
}
…………………………………
}
5.2.3. Motivos para dar marcha atrás de la transacción.
Hay dos motivos por los que la transacción gestionada por el contenedor dará marcha atrás aplicando un
rollback. El primero es motivado por un excepción lanzada por el sistema, el segundo es por la invocación del
método setRollbackOnly() de la interfaz EJBContext. Este método instruye al contenedor para dar marcha
atrás a la transacción. Si el bean lanza una excepción, la marcha atrás no será automática pero podemos
iniciarla invocando setRollbackOnly().
Se puede obtener una instancia de EJBContext mediante inyección de dependencias:
@Resource
EJBContext e;
5.2.4. Excepciones de aplicación.
La especificación JPA diferencia entre las excepciones del sistema y excepciones de aplicación. Se produce
una excepción de aplicación cuando hay un error en la lógica del negocio. Se crean excepciones de aplicación
decorándolas con la anotación @ApplicationException, y no pueden ser de tipo RuntimeException o
RemoteException, como en el siguiente ejemplo:
package error;
import javax.ejb.ApplicationException;
@ApplicationException(rollback = true)
public class ExcepcionBean extends Exception {
}
En el contexto de una transacción, las excepciones de aplicación no causan automáticamente una operación
rollback. Esto da la posibilidad al cliente de recuperarse después de que se produce una excepción en la
aplicación.
Las excepciones de aplicación se envían al cliente sin estar encapsuladas en una JBException. Por lo tanto, se
pueden usar para informar de errores de validación o de problemas en lógica de negocio.
Se utiliza @ApplicationException(rollback=true) si se desea que la transacción aplique un rollback
automático, mientras que @ApplicationException(rollback=false) no aplica un rollback automático.
5.2.5. Transacciones JTA.
En la demarcación de transacciones gestionadas por beans, el código en el bean de sesión o dirigido por
mensajes determinar explícitamente los límites de la transacción. Aunque los beans con transacciones
347
gestionadas por contenedor requieren menos código, tienen una limitación: cuando se ejecuta un método,
puede estar asociado a una transacción o a ninguna.
JTA, o Java Transaction API, permite demarcar transacciones de una manera que es independiente de la
implementación del gestor de transacciones. El servidor GlassFish implementa el gestor de transacciones con
Java Transaction Service (JTS). Sin embargo, nuestro código no invoca los métodos de JTS directamente, sino
que invoca los métodos JTA, los cuales realizan llamadas a las rutinas de bajo nivel de JTS.
Una transacción JTA es controlada por el gestor de transacciones de Java EE. Se pueden utilizar
transacciones JTA para realizar actualizaciones sobre varias bases en una misma operación, pero con la
limitación de que no se soportan transacciones anidadas. No se puede empezar una transacción hasta que la
precedente haya finalizado.
Para demarcar una transacción JTA debemos invocar los métodos begin(), commit() y rollbak() de la
interfaz javax.transaction.UserTransaction. Podemos obtener una instancia de esta interfaz de varios
modos:
1) Usando inyección de dependencias.
2) Mediante el método getUserTransaction() de un objeto EJBContext.
3) Mediante el método getUserTransaction() de un objeto SessionContext.
En todo caso, para aplicar transacciones JTA en un Enterprise bean debemos decorarlo con la anotación
@TransactionManagement(BEAN). El siguiente código ilustra sobre el uso de estas anotaciones y de los
métodos de UserTransaction.
package ejb;
import entidad.Cliente;
import javax.annotation.Resource;
import javax.ejb.EJBContext;
import javax.ejb.Stateless;
import javax.ejb.TransactionManagement;
import static javax.ejb.TransactionManagementType.BEAN;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.UserTransaction;
@Stateless
@TransactionManagement(BEAN)
public class ClienteFacade extends AbstractFacade<Cliente> {
@PersistenceContext(unitName = "WebEJBPersistencePU")
private EntityManager em;
@Resource
UserTransaction userTrans;
…………………………………………………
public void transaccion() {
try {
userTrans.begin();
// Se realizan operaciones sobre varias bases de datos
userTrans.commit();
} catch (Exception ex) {
try { userTrans.rollback(); } catch (Exception ex1) { }
}
}
}

6. Seguridad en Java EE.


Las aplicaciones de la capa de Negocio y de la capa Web están formadas por componentes que se
implementan en varios contenedores. Estos componentes se combinan para crear una aplicación empresarial
de varias capas. La seguridad de cada componente es proporcionada por su contenedor. Un contenedor
proporciona dos tipos de seguridad: declarativa y de programación.
• La seguridad declarativa se expresa mediante archivos descriptores de configuración o mediante
anotaciones.

348
• La seguridad programática está incrustada en una aplicación y se utiliza para tomar decisiones de
seguridad.
El comportamiento de seguridad de un entorno Java EE puede entenderse mejor mediante el análisis de lo
que sucede a una aplicación sencilla con un cliente web, una interfaz de usuario, y la lógica de negocio de un
Enterprise bean.
Figura 48

El cliente web solicita la dirección URL de un recurso protegido de la aplicación principal. Si es el primer
acceso se solicita la autentificación del cliente mediante credenciales. Si las credenciales son validadas, se
utilizarán para futuras solicitudes del cliente.
El servidor web consulta la política de seguridad asociada con el recurso solicitado para determinar si el
usuario validado tiene permiso de acceso. La política de seguridad se puede derivar de anotaciones o de la
configuración de un fichero descriptor.
Si el usuario está autorizado, el servidor Web devuelve el resultado de la solicitud original. Si el recurso
necesita hacer llamadas a un método de un Enterprise bean, utiliza las credenciales del usuario para establecer
una asociación de seguridad entre el recurso y el Enterprise bean. La asociación se implementa como dos
contextos de seguridad relacionados: uno en el servidor web y otro en el contenedor EJB.
El contenedor EJB es responsable de hacer cumplir el control de acceso al método del Enterprise bean. El
contenedor consulta la política de seguridad asociada con el Enterprise bean para determinar quién tiene
prohibido el acceso al método. La política de seguridad se deriva de anotaciones o de la configuración en un
fichero descriptor.
6.1. Seguridad en aplicaciones Web.
Muchos de los elementos necesarios para seguridad en aplicaciones web no pueden ser especificados como
anotaciones. Como en una unidad previa se ha visto cómo aplicar seguridad mediante ficheros descriptores,
aquí se intentará usar anotaciones siempre que sea posible.
6.1.1. Configuración de las opciones de autentificación.
Se usa una restricción de seguridad para definir los permisos de acceso a una colección de recursos que
mapean con una URL.
Si nuestra aplicación web usa servlets podemos expresar la información de restricción de seguridad usando las
anotaciones @HttpConstraint y opcionalmente @HttpMethodConstraint dentro de la anotación
@ServletSecurity.
Si la aplicación web no usar servlets se deben especificar los elementos de restricción de seguridad en el
fichero descriptor. El mecanismo de autentificación no pude expresarse usando anotaciones, así que si se usa
un método de autentificación distinto de BASIC (que es el de por defecto) debe utilizarse el fichero
descriptor.
En unidades previas se ha visto cómo configurar seguridad en una aplicación web ejecutada sobre el servidor
Tomcat. Para ilustrar con un ejemplo el uso de anotaciones supongamos creado un proyecto web con
GlassFish. En el fichero descriptor «web.xml» se configura el método de autentificación BASIC y se declaran
dos roles: rol1 y rol2.
Fichero descriptor «web.xml»

349
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>RealmEmpresa</realm-name>
</login-config>
<security-role>
<description/>
<role-name>rol1</role-name>
</security-role>
<security-role>
<description/>
<role-name>rol2</role-name>
</security-role>
</web-app>
Para validar las credenciales en la base de datos EMPRESA crearemos dos tablas como estas:
CREATE TABLE USUARIO (
nombre_usuario VARCHAR(15) NOT NULL,
password_usuario VARCHAR(15) NOT NULL
);
CREATE TABLE USUARIO_ROL (
nombre_usuario VARCHAR(15) NOT NULL,
nombre_rol VARCHAR(15) NOT NULL
);
E insertaremos los siguientes datos:
INSERT INTO USUARIO VALUES ('usuario1', 'usuario1');
INSERT INTO USUARIO VALUES ('usuario2', 'usuario2');
INSERT INTO USUARIO_ROL VALUES ('usuario1', 'rol1');
INSERT INTO USUARIO_ROL VALUES ('usuario2', 'rol2');
6.1.2. Configuración de un dominio de seguridad en GlassFish.
Ahora crearemos un recurso de tipo DataSource. En GlassFish primero debemos crear un Pool y después un
recurso JDBC. Para crear el Pool podemos usar en NetBeans la plantilla «JDBC Connection Pool» de la
categoría «GlassFish».

350
Figura 49

En el siguiente paso debemos dar un nombre al pool: connectionPoolEmpresa, y debemos elegir una
cadena de conexión existente:
Figura 50

En el siguiente paso debemos escribir la clase del controlador. En el caso de Java Derby será
org.apache.derby.jdbc.ClientDriver.

351
Figura 51

Podemos finalizar para que se cree el Pool. Y ahora debemos crear un recurso JDBC, para ello podemos usar
en NetBeans la plantilla «JDBC Resource» de la categoría «GlassFish». En el asistente seleccionaremos el pool
creado previamente y daremos un nombre JNDI al recurso: db/Empresa.

352
Figura 52

En al archivo descriptor «glassfish-resources.xml» del proyecto web se definirán los recursos creados:
Fichero descriptor « glassfish-resources.xml»

353
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC
"-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN"
"http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
<jdbc-resource enabled="true" jndi-name="db/Empresa" object-type="user"
pool-name="connectionPoolEmpresa">
<description/>
</jdbc-resource>
<jdbc-connection-pool allow-non-component-callers="false"
associate-with-thread="false" connection-creation-retry-attempts="0"
connection-creation-retry-interval-in-seconds="10"
connection-leak-reclaim="false" connection-leak-timeout-in-seconds="0"
connection-validation-method="table"
datasource-classname="org.apache.derby.jdbc.ClientDataSource"
fail-all-connections="false" idle-timeout-in-seconds="300"
is-connection-validation-required="false" is-isolation-level-guaranteed="true"
lazy-connection-association="false" lazy-connection-enlistment="false"
match-connections="false" max-connection-usage-count="0"
max-pool-size="32" max-wait-time-in-millis="60000"
name="connectionPoolEmpresa" non-transactional-connections="false"
ping="false" pool-resize-quantity="2" pooling="true"
res-type="javax.sql.DataSource" statement-cache-size="0"
statement-leak-reclaim="false"
statement-leak-timeout-in-seconds="0" statement-timeout-in-seconds="-1"
steady-pool-size="8" validate-atmost-once-period-in-seconds="0"
wrap-jdbc-objects="true">
<property name="URL" value="jdbc:derby://localhost:1527/EMPRESA"/>
<property name="serverName" value="localhost"/>
<property name="PortNumber" value="1527"/>
<property name="DatabaseName" value="EMPRESA"/>
<property name="User" value="APP"/>
<property name="Password" value="app"/>
</jdbc-connection-pool>
</resources>
Hay que desplegar la aplicación para que se creen en GlassFish el Pool y el recurso JDBC. También se puede
abrir la consola de administración de GlassFish y crear el Pool y el recurso JDBC directamente en los nodos
«Resources|JDBC Connection Pools» y «Resources|JDBC Resources» respectivamente.
Importante. No debemos olvidarnos de incluir en el proyecto la librería del controlador específico de la
base de datos utilizada.
Ahora crearemos un dominio de seguridad (un Realm) mediante la consola de administración de GlassFish.
Para ello hay que seleccionar el nodo «Configurations|server-config|Security|Realms» y pulsar en el botón
«New». Debemos rellenar los datos solicitados para crear un nuevo dominio llamado RealmEmpesa:

354
Figura 53

Los datos asignados son los siguientes:


Realm Name RealmEmpresa
Class Name com.sun.enterprise.security.ee.auth.realm.jdbc.JDBCRealm
JAAS Context* jdbcRealm
JNDI* db/Empresa
UserTable* USUARIO
User Name Column* NOMBRE_USUARIO
Password Column* PASSWORD_USUARIO
Group Table* USUARIO_ROL
Group Table User Name Column NOMBRE_USUARIO
Group Name Column* NOMBRE_ROL
Password Encryption Algorithm* none
Assign Groups
Database User
Database Password
Digest Algorithm none
Encoding
Charset
Hay que llamar la atención sobre las opciones Password Encryption Algorithm y Digest Algorithm, que
en este caso se han dejado a none, puesto que no aplicaremos ningún tipo de encriptación en la contraseña
del usuario ni en el algoritmo Digest. Las opciones Group hacen referencia a los roles.
Por último, para acabar con la configuración, debemos mapear los roles creados en el fichero descriptor
«web.xml» con los grupos de la base de datos. Esto se realiza en el fichero descriptor « glassfish-web.xml». Si
este fichero todavía no está incluido en el proyecto se puede añadir usando la plantilla «GlassFish Descriptor»
de la categoría «GlassFish».
Fichero descriptor «WEB-INF/glassfish-web.xml»

355
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC
"-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN"
"http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app error-url="">
<class-loader delegate="true"/>
<jsp-config>
<property name="keepgenerated" value="true">
<description>Keep a copy of the generated servlet class' java code.</description>
</property>
</jsp-config>
<security-role-mapping>
<role-name>rol1</role-name>
<group-name>rol1</group-name>
</security-role-mapping>
<security-role-mapping>
<role-name>rol2</role-name>
<group-name>rol2</group-name>
</security-role-mapping>
</glassfish-web-app>
6.1.3. Configuración de las restricciones de seguridad.
Por último aplicaremos una política de seguridad sobre un servlet llamado ServletConexion:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.HttpConstraint;
import javax.servlet.annotation.ServletSecurity;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@ServletSecurity(@HttpConstraint(rolesAllowed = {"rol1"}))
@WebServlet(name = "ServletConexion", urlPatterns = {"/conexion"})
public class ServletConexion extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("Eres un usuario con permiso.");
}
}
Si un usuario invoca el servlet por primera vez se le solicitarán credenciales. Si se identifica como el usuario1
podrá acceder al servlet. Si se identifica como usuario2 será validado pero no podrá acceder al servlet.
La anotación @ServletSecurity permite aplicar una o varias restricciones de seguridad sobre un servlet.
Cada restricción se configura mediante una anotación @HttpConstraint.
Se utiliza la siguiente sintaxis para aplicar una restricción sobre todos los métodos del servlet:
@ServletSecurity(@HttpConstraint(rolesAllowed={"rol1,rol2"}))
public class ServletConexion extends HttpServlet {
}
En este caso, todos los métodos HTTP estarán protegidos y sólo serán accesibles por los usuarios de los roles
especificados en la anotación @HttpConstraint.
Si queremos aplicar restricciones al nivel de cada método:
@ServletSecurity(
httpMethodConstraints={
@HttpMethodConstraint("GET"),
@HttpMethodConstraint(value="POST", rolesAllowed={"rol1"}),
@HttpMethodConstraint(value="TRACE", emptyRoleSemantic=ServletSecurity.EmptyRoleSemantic.DENY)
})
public class ServletConexion extends HttpServlet {
}

356
Se consigue la siguiente funcionalidad:
• Todos los usuarios pueden acceder al método GET.
• Sólo los usuarios del rol rol1 puede acceder al método POST.
• Nadie puede acceder al método TRACE.
Se puede aplicar una restricción general y establecer algunas excepciones:
@ServletSecurity(
value=@HttpConstraint(rolesAllowed={"rol1"}),
httpMethodConstraints={
@HttpMethodConstraint(value="POST", rolesAllowed={"rol2"}),
@HttpMethodConstraint("TRACE")
})
public class ServletConexion extends HttpServlet {
}
Se consigue la siguiente funcionalidad:
• Sólo los usuarios autentificados con el rol rol2 podrán acceder al método POST.
• Todos podrán acceder al método TRACE.
• Para los demás métodos HTTP sólo los usuarios autentificados con el rol rol1 podrán acceder.
6.2. Seguridad en Enterprises Beans.
6.2.1. Seguridad declarativa.
La seguridad declarativa permite a los desarrolladores de aplicaciones especificar qué usuarios están
autorizados a acceder a los distintos métodos de los EJBs.
Se pueden especificar permisos de acceso a métodos sobre la clase, sobre cada método de negocio de la clase,
o sobre ambos. Los permisos sobre un método sobreescriben los permisos sobre la clase. Las siguientes
anotaciones se utilizan para especificar permisos de acceso a métodos:
• @DeclareRoles: especifica los roles que usará la aplicación, incluyendo roles no nombrados en una
anotación @RolesAllowed. La anotación @DeclareRoles se especifica sobre la clase de un bean; por
ejemplo:
import javax.annotation.security.*;
@DeclareRoles("rol1")
public class Calculador {
.......................
}
Si queremos declarar más de un rol debemos utilizar la siguiente sintaxis:
@DeclareRoles({"rol1", "rol2"})
• @RolesAllowed (list-of-roles): especifica los roles de seguridad permitidos para acceder a métodos de la
aplicación. Esta anotación se puede especificar sobre una clase o sobre uno o varios métodos.
Para especificar que ningún rol está autorizado para acceder a los métodos de la aplicación se usa la
anotación @DenyAll, y para permitir a cualquier rol acceder a la aplicación se usa la anotación @PermitAll.
Cuando se usa en conjunción con la anotación @DeclareRoles, el conjunto combinado de roles es usado
por la aplicación.
El siguiente ejemplo muestra cómo usar esta anotación:
import javax.annotation.security.*;
@DeclareRoles({"rol1", "rol2"})
public class Calculador {
@RolesAllowed("rol1")
public void setNuevaTasa(int tasa) {
.......................
}
}
• @PermitAll: especifica que todos los roles tienen permiso para ejecutar el método o métodos
especificados. No se validará al usuario en su acceso a la aplicación.
Por ejemplo, el siguiente código aplica esta anotación sobre un método concreto, aunque puede aplicarse
también sobre la clase:
import javax.annotation.security.*;
@RolesAllowed("usuario1")
public class Calculador {

357
@RolesAllowed("usuario2")
public void setNuevaTasa(int tasa) {
.......................
}
@PermitAll
public long convertirMoneda(long cantidad) {
.......................
}
}
• @DenyAll: especifica que ningún rol tendrá permiso para ejecutar el método o métodos especificados.
Esto implica que los métodos serán ejecutados en exclusiva por el contenedor Java EE.
El siguiente código muestra cómo usar esta anotación:
import javax.annotation.security.*;
@RolesAllowed("usuario1")
public class Calculador {
@DenyAll
public long convertirMoneda(long cantidad) {
.......................
}
}
Si el llamador del bean no tiene los permisos adecuados se devolverá un código de estado HTTP 403 -
Forbidden.
6.2.2. Seguridad programática.
La seguridad programática permite identificar al usuario actual y tomar decisiones dentro del propio método
invocado.
La interfaz javax.ejb.EJBContext proporciona dos métodos que permiten a los beans recuperar
información sobre el llamador del bean.
El método getCallerPrincipal() retorna un objeto Principal con la información del usuario llamador. Se
puede, por ejemplo, utilizar el nombre del usuario como clave para obtener información desde una base de
datos.
El siguiente código ilustra sobre cómo utilizar este método:
import java.security.Principal;
import javax.annotation.Resource;
import javax.annotation.security.DeclareRoles;
import javax.annotation.security.RolesAllowed;
import javax.ejb.SessionContext;
import javax.enterprise.inject.Model;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Stateles
public class EmpleadosBean implements EmpleadosServicio {
@Resource
SessionContext ctx;
@PersistenceContext
EntityManager em;
public void cambiarNumeroTelefono(String telefono) {
.......................
// Se recupera el Principal del llamador.
Principal llamador = ctx.getCallerPrincipal();
// Se obtiene el nombre del llamador.
String nombre = llamador.getName();
// Se una el nombre del llamador como clave para encontrar un empleado.
Empleado empleado = em.find(Empleado.class, nombre);
// Se actualiza el número de teléfono.
empleado.setTelefono(telefono);
.......................
}
}

358
El método isCallerInRole() permite evaluar si el llamador pertenece a un rol determinado. Un bean puede
utilizar este método para verifica si el llamador actual está asignado a un rol de seguridad.
El siguiente código muestra cómo usar este método:
@Stateless public class PagoPedidoBean implements PagoPedido {
@Resource
SessionContext ctx;
public void actualizaInfoEmpleado(EmpleadoInfo info) {
EmpleadoInfo oldInfo = ....... leerla de la base de datos;
// The salary field can be changed only by callers
// who have the security role "payroll"
if (info.salario != oldInfo.salario && ! ctx.isCallerInRole("empleado")) {
throw new SecurityException();
}
.......................
}
.......................
}
Se debería usar seguridad programática para controlar el acceso dinámico a un método, por ejemplo, cuando
se quiera denegar el acceso durante una franja horaria en el día.

PRÁCTICA
01. Crea con GlassFish un bean de sesión, llamado BeanHoroscopo, que proporcione métodos de negocio
para consultar el Horóscopo.
• String horoscopoDia(String signo), retornará un string con el horóscopo del día actual para un signo
zodiacal dado.
• String horoscopoMes(String signo, int mes), retornará un string con el horóscopo del mes para un
signo zodiacal y un mes dado.
02. El bean se sesión será accedido remotamente, y hará uso de un servicio local llamado
ServicioHoroscopo que gestionará un repositorio con todas las previsiones del horóscopo. El servicio local
debe ser inyectado mediante dependencias en el bean a través de una interfaz que implemente.
El cliente del bean puede ser una aplicación Web de Tomcat o de Glassfish, y constará de una página inicial
donde el usuario podrá introducir su signo zodiacal y solicitar el horóscopo del día o de un mes dado.
03. Crea en el bean BeanHoroscopo un temporizador que llame a un método del ServicioHoroscopo cada
día. Este método debe archivar las previsiones de días previos en un historial.

359
UNIDAD 19. SERVICIOS WEB
1. Fundamentos sobre servicios Web
Los servicios web son aplicaciones clientes y servidoras que son accesible mediante el protocolo HTTP sobre
la red WWW. Los servicios web se caracterizan por su gran capacidad de integración con otras tecnologías,
gracias a que está basado en una comunicación estándar mediante solicitudes HTTP y en descripciones
mediante el formato XML.
Al igual que los EJBs, un servicio se implementa mediante una clase que ofrece métodos que puedan ser
invocados remotamente, pero utiliza protocolos de comunicación mucho más ligeros.
1.1. Tipos de servicio Web.
A un nivel conceptual, un servicio es un componente de software disponible a través de un punto final de la
red. El servicio como consumidor y como proveedor utiliza mensajes para intercambiar información entre las
solicitudes y las respuestas en la forma de documentos semi-contenidos que hacen muy pocas asunciones
sobre las capacidades tecnológicas del receptor.
Figura 1

Desde un punto de vista técnico podemos decir que se trata de componentes autónomos de grano grueso,
con interfaces bien definidas (contratos de servicio) y con una clara separación entre su interface pública y su
implementación privada interna.
A nivel técnico los servicios web pueden ser implementados de varias formas. En esta unidad se describirán
los servicios Web Completos y los servicios web RESTful.
1.1.1. Servicios Web Completos.
Los servicios Web Completos usan mensajes XML que cumplen con el protocolo SOAP (Simple Object
Access Protocol) estándar, para transmitir la información de solicitud y respuesta. Cada sistema que da acceso
a un servicio web normalmente contiene una descripción de las operaciones que ofrece el servicio; estas
descripciones están escritas en WSDL (el Lenguaje de Descripción de Servicios Web) y en XML para definir
la sintaxis de interfaces con los métodos que pueden ser invocados sobre el servicio.
El formato de los mensajes SOAP y el lenguaje de definición de la interfaz WSDL se han vuelto muy
complejos con el tiempo, y muchas herramientas de desarrollo, como NetBeans, simplifican esta complejidad
cuando crean aplicaciones de servicios web.
Un servicio web diseñado con SOAP debe incluir los siguientes elementos:
• Un contrato formal que establezca los servicios que ofrece. Esto se concreta en una interfaz con los
métodos accesibles de la clase del servicio. Se utiliza WSDL para describir los detalles del contrato, lo cual
puede incluir mensajes, operaciones, enlaces y la ubicación del servicio web. También se puede procesar
mensajes SOAP en servicios JAX-WS sin publicarlos con WSDL.
• La arquitectura del servicio debe ser capaz de gestionar requerimientos no funcionales, como
transacciones, seguridad, confianza, etc.
• La arquitectura debe ser capaz de gestionar procesos e invocaciones asíncronas.
1.1.2. Servicios Web RESTful.
Los servicios RESTful exponen datos como recursos de forma que las aplicaciones pueden acceder a ellos
usando una URI.
En Java EE, JAX-RS proporciona la funcionalidad de un servicio Web
RESTful(RepresentationalStateTransfer). REST se ha convertido en un modelo muy popular para
implementar servicios web que necesitan acceder a datos. REST describe recursos y objetos del negocio sin
estado y con un esquema jerárquico a través de la red, de forma que los recursos son accedidos mediante
URIs que identifican los datos a recuperar. Por ello, los servicios RESTful no requieren de mensajes XML ni
definiciones WSDL.
Un diseño RESTful puede ser apropiado cuando se den las siguientes condiciones:
• El servicio web es completamente sin estado.
360
• Se puede implementar en el servicio una infraestructura de cachés. Si los datos que retorna el servicio no
se generan dinámicamente pueden almacenarse en cachés para que sean devueltos de forma más rápida y
eficiente. De da el inconveniente de que las cachés están limitadas a solicitudes HTTP GET en muchos
servidores.
• El servicio productor y el servicio consumidor tienen una mutua comprensión del contexto y contenido
de los datos que se pasan. Muchas aplicaciones comerciales que ofrecen servicios RESTful también
distribuyen herramientas que describen las interfaces de acceso al servicio.
• El ancho de banda en muy importante y está limitado. REST provoca poca sobrecarga de datos en las
comunicaciones.
1.1.3. Consideraciones sobre el tipo de servicio a usar.
Básicamente se utilizarán servicios RESTful por su fácil integración en la web, y se utilizarán servicios web
completos cuando sean necesarios requerimientos más avanzados.
Los servicios creados con JAX-WS son más apropiados en grandes empresas que requieren una gran
computación. Los servicios JAX-WS soportan más fácilmente el conjunto de protocoles WS-*, que
proporcionan estándares de seguridad y fiabilidad, además de otras cosas.
Los servicios creados con JAX-RS hacen más fácil escribir aplicaciones web que siguen el estilo REST. Este
estilo se base en bajo acoplamiento, escalabilidad y simplicidad de arquitectura.
1.2. Servicios Web con JAX-WS.
El API JAX-WS (Java API for XML Web Service, JSR 224) es una tecnología para crear servicios web que se
comunican usando XML. JAX-WS permite a los desarrolladores escribir mensajes y servicios orientados a
llamadas a procedimientos remotos (RPC-Remote Procedure Call).
En JAX-WS una invocación a una operación de un servicio se representa mediante el protocolo SOAP. La
especificación SOAP define la estructura de una invocación, las reglas que la codifican y las convenciones
para representarla usando mensajes en formato XML sobre el protocolo HTTP.
Dado que los mensajes SOAP son complejos, el API JAX-WS oculta esta complejidad en las aplicaciones. En
el lado servidor, el desarrollador debe especificar las operaciones del servicio definiendo métodos en una
interfaz. Se crean una o más clases que implementan los métodos de la interfaz. Los programas cliente deben
crear un proxy (un objeto local que representa al servicio) a través del cual invocar los métodos del servicio.
Con JAX-WS el desarrollador no necesitan generar o pasear mensajes SOAP, el intérprete JAX-WS convierte
las llamadas y repuestas al servicio en mensajes SOAP.
1.2.1 Ejemplo de un servicio web usando JAX-WS.
Crearemos ahora un servicio web y una aplicación cliente del servicio usando NetBeans. Como se utiliza el
protocolo HTTP para acceder a los servicios web primero hay que crear una aplicación web. Para el ejemplo
posterior se supone creada una aplicación web llamada WebServicioJAXWS para el servidor GlassFish.
Figura 2

Nota. Los servidores Tomcat y GlassFihs utilizan por defecto el puerto 8080 para el protolo HTTP.
Como este puerto puede entrar en conflictos con otras aplicaciones servidoras se puede modificar en
ambos casos.
Para modificar el puerto de GlassFish se puede editar el fichero «Ruta
GlassFish/glassfish/domains/domain1/config/domain.xml» y buscar el elemento<network-
listenerprotocol="http-listener-1"port="8080"...>. Simplemente hay que cambiar el atributo port y
reiniciar el servidor.
Para modificar el puerto de Tomcat se puede editar el fichero «RutaTomcat/conf/server.xml» y
buscar el elemento <Connectorport="8080"protocol="HTTP/1.1"...>. También es posible modificar
el puerto que utiliza Tomcat cuando se ejecuta desde NetBeans editanto las propiedades del servidor en el
panel «Services/Prestaciones».

361
El punto de entrada para desarrollar un servicio web JAX-WS es una clase de Java anotada con
javax.jws.WebService. La anotación @WebService define la clase como un servicio web de punto final.
Una interfaz de punto final o una implementación de punto final (SEI) es una interfaz o clase de Java,
respectivamente, que declara los métodos que un cliente podrá invocar sobre el servicio.
En la aplicación web WebServicioJAXWS añadiremos un «Web Service» de la categoría «Web Service».
Figura 3

En el siguiente paso damos nombre al servicio, WebServicioTest, y lo ubicamos en un paquete, web. Es este
primer ejemplo marcaremos la opción «Crear Servicio Web desde cero».
Figura 4

Tras finalizar, en el proyecto web se creará la clase web.WebServicioTest, la cual está decorada con la
anotación @WebService y declara el método hello() como un método de punto final. Esto último viene
dado por la anotación @WebMethod.

362
Figura 5

Este servicio ofrece cómo operación el método hello(). Para probar cómo invocarlo debemos compilar el
proyecto y desplegarlo a través del servidor web GlassFish. Pero antes estableceremos en el fichero descriptor
web.xml como página de bienvenida WebServicioTest:
<welcome-file-list>
<welcome-file>WebServicioTest</welcome-file>
</welcome-file-list>
Con esto conseguiremos que al ejecutar el sitio web se muestre una página con información sobre el servicio.
Figura 6

En esta página se muestra la dirección para invocar al servicio, así como la dirección WSDL con información
de descripción del servicio web.
Importante. Es necesario habilitar NetBeans y el servidor GlassFish para acceder a esquemas externos
para poder leer el fichero WSDL del servicio web. Para habilitar el acceso hay que modificar los ficheros
de configuración de NetBeans y de GlassFish.
Primero hay que abrir el fichero de configuración de NetBeans
«RutaNetBeans8.0/etc/netbeans.conf», y añadir al string asignado en
«netbeans_default_options»elvalor«-J-Djavax.xml.accessExternalSchema=all».
Para configurar GlassFish hay que abrir el fichero
«RutaGlassFish/glassfish/domains/domain1/config/domain.xml» y añadir el siguiente elemento
<jvm-options>-Djavax.xml.accessExternalSchema=all</jvm-options>dentrodel elemento
<java-config>.
Es necesario reiniciar el servidor para que los cambios tengan efecto.

363
GlassFish permite probar los métodos del servicio web usando la siguiente URL:
http://localhost:8080/WebServicioJAXWS/WebServicioTest?Tester
Figura 7

Para probar el método hello() hay que seguir los siguientes pasos:
• Escribir un valor para el parámetro de método.
• Pulsar el botón con el nombre del método.
1.2.2. Requerimientos de un punto final JAX-WS.
Una clase de punto final JAX-WS debe cumplir con los siguientes requerimientos:
•Laclasedebeestaranotadaconjavax.jws.WebServiceo con javax.jws.WebServiceProvider.
• La clase puede referenciar explícitamente una interfaz de punto final en el atributo endpointInteface de
la anotación @WebService, pero no es obligatorio. Si no se especifica será implícitamente definida a partir
de la implementación de la clase.
• La clase no puede ser final ni abstract.
• La clase debe tener un constructor público sin argumentos, y no debe reescribir el método finalize().
• La clase puede usar las anotaciones @PostConstruct o @PreDestroy sobre los métodos del ciclo de
vida.
• Los métodos del negocio deben ser públicos y no pueden ser static o final.
• Los métodos del negocio que deban ser accesibles por los clientes del servicio web deben estar anotados
con javax.jws.WebMethod.
• Los métodos de negocio accesibles deben tener parámetros y un tipo de retorno compatibles con JAXB.
En la anotación @WebService se especifica el nombre público del servicio. Mientras que en la anotación
@WebMethod se puede establecer el nombre público del método, que puede diferir del escrito en código
Java. Con la anotación @WebParam se establece el nombre público de un parámetro.
Los nombres públicos del servicio, método y parámetros serán publicados mediante WSDL y los clientes los
utilizarán para referenciar al servicio y métodos reales.
1.3. Inyecciónderecursos.
JAX-WS soporta la inyección de recursos mediante anotaciones. La anotación @Resource se puede aplicar
sobre una variable de tipo javax.xml.ws.WebServiceContext dentro de la implementación de la clase raíz
del servicio. La interfazWebServiceContext proporciona alguno de los siguientes métodos:
• MessageContextgetMessageContext(), retorna el contexto de mensajes para la solicitud actual.
• PrincipalgetUserPrincipal(), retorna la información del usuario actual autentificado o null si no hay
usuario autentificado.

364
• booleanisUserInRole(Stringrole), indica si el usuario actual pertenece a un rol determinado. Devuelve
siempre false si no hay usuario autentificado.
Por ejemplo, podemos usar WebServiceContext para evaluar si el usuario autentificado tiene permiso para
invocar el código del servicio:
@WebService
publicclassMiServicio {
@Resource
privateWebServiceContextctx;
publicStringecho(Stringinput){
if (ctx.isUserInRole("admin")
return "Aceptado";
else
return "Usuario no validado";
}
}
Por su parte, la anotación @WebServiceRef se utiliza para inyectar una referencia a un proxy o "port" que
referencia un servicio en las aplicaciones cliente. En el siguiente capítulo veremos cómo usar esta anotación.
1.4. Aplicaciones cliente de servicios web.
En este capítulo veremos cómo crear un cliente para un servicio web JAX-WS. Dada la complejidad de los
mensajes SOAP primero veremos cómo generar un proxy en el cliente que simplifique las llamadas al
servicio, pero también veremos cómo trabajar a nivel de mensajes SOAP.
1.4.1. Ejemplo de un cliente de servicio web usando JAX-WS.
Ahora crearemos como cliente una aplicación de escritorio llamada ClienteWebServicioJAXWS, con una
única clase Main. En la aplicación de escritorio debemos agregar un «Web Service Client» de la categoría
«Web Service».
Figura 8

En el siguiente paso debemos asignar la URL del servicio WSDL. Se puede asignar directamente en la opción
«WSDL URL» o se puede seleccionar el proyecto web con el servicio.

365
Figura 9

En el siguiente paso asignaremos el paquete donde se crearán las clases proxy y finalizaremos.
Figura 10

Tras finalizar, NetBeans generará algunos ficheros de configuración y las clases necesarias para implementar
un proxy que haga transparente la invocación de los métodos del servicio.

366
Figura 11

El código que permitirá acceder al método del servicio es el siguiente:


public class Main {
public static void main(String[] args) {
WebServicioTest_Service serv = new WebServicioTest_Service();
WebServicioTest servicio = serv.getWebServicioTestPort();
System.out.println(servicio.hello("Pedro"));
}
}
1.4.2. Código en las aplicaciones cliente.
Cuando una aplicación de escritorio invoca el método remoto sobre el puerto, el cliente realiza los siguientes
pasos:
1) Usa la clase generadora WebServicioTest_Service.
WebServicioTest_Service serv = new WebServicioTest_Service();
2) Obtiene un proxy del servicio, también conocido como "port".
WebServicioTest servicio = serv.getWebServicioTestPort();
El "port" implementa el SEI definido por el servicio.
3) Invoca el método del servicio, pasándole los argumentos necesarios:
System.out.println(servicio.hello("Pedro"));
Pero si usamos como cliente un servidor de aplicaciones, como GlassFish, podemos utilizar inyección de
dependencias para obtener la instancia del servicio.
Supongamos creado un proyecto web para el servidor GlassFish. Generamos un «Web Service Client» de la
categoría «Web Service» y un servlet llamado ServletCliente:

367
Figura 12

Para invocar el método remoto sobre el "puerto", el servlet cliente de GlassFish realiza los siguientes pasos:
1) Usa la clase generada web.WebServicioTest_Service para inyección de dependencias mediante la
anotación @WebServiceRef().
@WebServiceRef(WebServicioTest_Service.class)
private WebServicioTest servicio;
2) Obtiene un proxy del servicio, también conocido como "port".
3) Invoca el método del servicio, pasándole los argumentos necesarios:
out.println(servicio.hello("Pedro"));
1.5. Fundamentos sobre SOAP.
Básicamente SOAP es un paradigma de mensajería de una dirección sin estado, que puede ser utilizado para
formar protocolos más complejos y completos según las necesidades de las aplicaciones que lo implementan.
Es la base para formar y construir la capa base del protocolo de los servicios web. Es un protocolo basado en
XML y se conforma de tres partes:
• Un sobre (envelope), el cual define qué hay en el mensaje y cómo procesarlo.
• Un conjunto de reglas de codificación para expresar instancias de tipos de datos.
• La convención para representar llamadas a procedimientos y respuestas.
1.5.1. Estructura de los mensajes SOAP.
Un mensaje SOAP es un documento XML con una estructura bien definida en la especificación del
protocolo. Dicha estructura la conforman las siguientes partes:
• Envelope (obligatoria): es el nodo raíz de la estructura e identifica al mensaje SOAP como tal. Un
ejemplo simple de un sobre SOAP es el siguiente:
<soap:Envelopexmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>
<soap:Body>
<p:Processxmlns:p='http://example.org/Operations'>
<m:Dataxmlns:m='http://example.org/information'>
Aquí algún dato con el mensaje
</m:Data>
</p:Process>
</soap:Body>
</soap:Envelope>

368
Los sobres (envelope) tienen un elemento raíz <soap:Envelope> que debe contener un único
elemento<soap:Body>,peronocontieneunadeclaracióndetipodedocumento ni instrucción de proceso.
• Header: este nodo es un mecanismo de extensión que permite enviar información relativa a como debe
ser procesado el mensaje. Es una herramienta para que los mensajes puedan ser enviados de la forma más
conveniente para las aplicaciones. El elemento <soap:Header> se compone a su vez de bloques que
delimitan las unidades de información necesarias para la cabecera.
• Body (obligatorio): contiene la información relativa a la llamada y la respuesta.
• Fault: nodo que contiene información relativa a errores que se hayan producido durante el procesado del
mensaje y su envío. Un receptor debe interpretar un mensaje SOAP como un fallo cuando el
<soap:Body> tiene un único hijo <soap:Fault>. Un ejemplo es el siguiente:
<soap:Body>
<soap:Faultxmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>
<faultcode>soap:Client</faultcode>
<faultstring>Formato de mensaje no válido</faultstring>
<faultactor>http://example.org/someactor</faultactor>
<detail>
<m:msgxmlns:m='http://example.org/faults/exceptions'>
Faltan elementos en el mensaje
</m:msg>
<m:Exceptionxmlns:m='http://example.org/faults/exceptions'>
<m:ExceptionType>Severo</m:ExceptionType>
</m:Exception>
</detail>
</soap:Fault>
</soap:Body>
El elemento <soap:Fault> sólo puede tener como elementos hijos <faultcode>, <faultstring>,
<faultactor> y <detail>.
1.5.2. Creación de sobres SOAP para la solicitud y respuesta.
Ahora veremos cómo crear los sobres SOAP para que un cliente pueda invocar los métodos de un servicio
JAX-WS. Usaremos el ejemplo del servicio WebServicioTest creado previamente, el cual ofrece el método
hello().
Primero invocaremos la URL «http://localhost:8088/WebServicioJAXWS/WebServicioTest?wsdl»
para obtener el documento WSDL del servicio.
<definitions …………. name="WebServicioTest">
<types>
<xsd:schema>
<xsd:importnamespace="http://web/"
schemaLocation="http://localhost:8088/WebServicioJAXWS/WebServicioTest?xsd=1"/>
</xsd:schema>
</types>
<messagename="hello">
<partname="parameters"element="tns:hello"/>
</message>
<messagename="helloResponse">
<partname="parameters"element="tns:helloResponse"/>
</message>
<portTypename="WebServicioTest">
<operationname="hello">
<inputwsam:Action="http://web/WebServicioTest/helloRequest"message="tns:hello"/>
<outputwsam:Action="http://web/WebServicioTest/helloResponse"message="tns:helloResponse"/>
</operation>
</portType>
<bindingname="WebServicioTestPortBinding"type="tns:WebServicioTest">
<soap:bindingtransport="http://schemas.xmlsoap.org/soap/http"style="document"/>
<operationname="hello">
<soap:operationsoapAction=""/>
<input>

369
<soap:bodyuse="literal"/>
</input>
<output>
<soap:bodyuse="literal"/>
</output>
</operation>
</binding>
<servicename="WebServicioTest">
<portname="WebServicioTestPort"binding="tns:WebServicioTestPortBinding">
<soap:addresslocation="http://localhost:8088/WebServicioJAXWS/WebServicioTest"/>
</port>
</service>
</definitions>
El formato del sobre de solicitud y respuesta puede obtenerse de la prueba del método hello() en la URL:
http://localhost:8088/WebServicioJAXWS/WebServicioTest?Tester
Si introducimos un dato para el método hello(), como Pedro, y pulsamos el botón con el nombre del
método obtendremos la siguiente información referida a los sobres SOAP:
Figura 13

SOAP Request
<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<S:Body>
<ns2:hello xmlns:ns2="http://web/">
<name>Pedro</name>
</ns2:hello>
</S:Body>
</S:Envelope>
SOAP Response
<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<S:Body>
<ns2:helloResponse xmlns:ns2="http://web/">
<return>Hello Pedro !</return>
</ns2:helloResponse>
</S:Body>
</S:Envelope>
Si analizamos el SOAP Request vemos que su cuerpo incluye la etiqueta <ns2:hello>, cuyo prefijo hace
referencia a un espacio de nombres "http://web" definido en el documento WSDL como el espacio de
nombres asociado al servicio WebServicioTest.
El elemento <ns2:hello> define sus parámetros con elementos <name>. Estos parámetros se
corresponden con los elementos hijos de <input> en el documento WSDL. Simplemente tendremos que
sustituir el contenido del elemento <name> con el dato que queramos enviar al servicio.

370
Un cliente que utilice el sobre SOAP Request puede utilizar el siguiente código para realizar la solicitud.
Primero se crea un string de formato con el contenido del sobre y un indicador %s en la ubicación del
parámetro:
String sobre = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\""
+ " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ " <SOAP-ENV:Header/>"
+ " <S:Body>"
+ " <ns2:hello xmlns:ns2=\"http://web/\">"
+ " <name>%s</name>"
+ " </ns2:hello>"
+ " </S:Body>"
+ "</S:Envelope>";
Ahora utilizaremos un HttpURLConnection para realizar la solicitud:
URL url = new URL("http://localhost:8088/WebServicioJAXWS/WebServicioTest");
HttpURLConnection conexion = (HttpURLConnection) url.openConnection();
// Configuramos los datos de solicitud
conexion.setRequestMethod("GET");
conexion.addRequestProperty("Content-Type", "text/xml");
conexion.setDoInput(true);
conexion.setDoOutput(true);
// Se utiliza el canal de salida de la conexión para enviar el sobre
PrintWriter pw = new PrintWriter(conexion.getOutputStream());
pw.printf(sobre, "Pedro");
pw.flush();
// Se utiliza el canal de entrada para recuperar el sobre de respuesta
BufferedReader br = new BufferedReader(new InputStreamReader(conexion.getInputStream()));
StringBuffer bufer=new StringBuffer();
String linea;
while((linea=br.readLine())!=null) {
bufer.append(linea);
}
br.close();
// Se muestra el contenido del sobre de respuesta
System.out.println(bufer);
El resultado mostrará el sobre de respuesta con su formato XML:
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:helloResponse xmlns:ns2="http://web/">
<return>Hello Pedro !</return>
</ns2:helloResponse>
</S:Body>
</S:Envelope>
Se puede comprobar que coincide con el esperado. Pero ahora la aplicación cliente debería saber aislar el dato
de respuesta del método. Para ello podemos usar la tecnología JAXP para realizar una consulta directamente
sobre el canal de entrada, en vez de leerlo.
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
String resultado = xpath.evaluate("//return/text()", new InputSource(conexion.getInputStream()));
System.out.println(resultado);
Mediante un objeto XPath consultamos por el contenido de cualquier nodo return usando como origen de
datos el canal de entrada del HttpURLConnection. Y efectivamente el resultado es:
Hello Pedro !

2. Servicios web RESTful y JAX-RS.


Los servicios web RESTful son un estilo de arquitectura que intentan estandarizar el acceso a servicios a
través del protocolo HTTP. Eso se refleja en una interfaz de acceso uniforme y un formato de datos ligero.

371
Como consecuencia se consigue mayor rendimiento, escalabilidad y capacidad de modificar el servicio, lo cual
permite a los servicios trabajar mejor sobre la red.
Para la arquitectura REST los datos y funcionalidades son considerados recursos y son accedidos mediante
URIs, normalmente en la forma de enlaces de las páginas. Los recursos son manejados usando un conjunto
simple de operaciones bien definidas. La comunicación con los clientes se realiza mediante interfaces y
protocolos estandarizados.
Los siguientes principios permiten a las aplicaciones RESTful ser simples, ligeras y rápidas:
• Se identifican los recursos mediante URIs: el servicio expone un conjunto de recursos asociados a método
de invocación HTTP y a datos concatenados en la URI de solicitud. La anotación @Path permitirá asociar
un método del servicio con una URI de invocación determinada.
• Interfaz uniforme: los recursos son manipulados usando un conjunto fijo de operaciones de creación,
consulta, actualización y eliminación asociadas a invocaciones PUT, GET, POST y DELETE
respectivamente.
• Mensajes semi-descriptivos: los recursos son desacoplados de su representación, de forma que su
contenido puede ser accedido en varios formatos como HTML, XML, texto plano, PDF, JPG, JSON y
otros.
• Interacciones sin estado a través de enlaces: cada interacción con un recurso es sin estado; los mensajes
solicitados son auto-contenidos. El estado puede ser embebido en los mensajes de respuesta.
2.1. Fundamentos de JAX-RS.
JAX-RS es una API de Java diseñada para hacer más fácil desarrollar aplicaciones que usan la arquitectura
REST.
Jersey es la implementación de referencia de JAX-RS, e incluye soporte para las anotaciones definidas en el
estándar JSR 311, facilitando el desarrollo de servicios web RESTful mediante el lenguaje Java. Se pueden
descargar las librerías de Jersey de la página «webhttps://jersey.java.net/download.html» e incluirlas en
el proyecto de NetBeans.
Para permitir que los servicios web RESTful convivan con otros recursos de la aplicación web que los
sustenta hay que definir en el fichero descriptor de la aplicación web un servlet que capture las URIs
específicas para los servicios web RESTful gestionados por Jersey:
FICHERO«web.xml»
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
…………………………
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
En los primeros ejemplos de servicios RESTful que veremos en los siguientes capítulos aún no usaremos
Jersey, así que no es necesaria esta configuración.
2.1.1. Clase raíz de recursos RESTful.
El punto de entrada de los servicios web RESTful son los recursos representados por una clase raíz. Las
clases raíz de recursos son POJOs que están anotados con @Path o tienen al menos un método anotado con
@Path o un método designado mediante las anotaciones @GET, @PUT, @POST o @DELETE. Un sub-
recurso es uno de los métodos de la clase del recurso que está anotado con un designador.
2.1.2. Anotaciones usando JAX-RS.
El API JAX-RS usa anotaciones para simplificar el desarrollo de los servicios web RESTful. Estas
anotaciones definen recursos y las acciones que pueden realizarse sobre estos recursos.
La siguiente tabla resume las anotaciones definidas por JAX-RS:
Anotac Descripción
ión

372
@Path Elvalordeestaanotaciónestablece
elpatróndelaURIasociadaconelrecurso.EstaURIpuedeembebervariablesparapasardatosalrecurso.Poreje
mplo,laURI/venta/{id}permitiríaconsultarunregistrodeventaconuniddeterminado.
@GET Esta anotación designa a un método que responderá a la solicitud HTTP GET. Habitualmente
designa métodos que devuelven una consulta.
@POST Esta anotación designa a un método que responderá a la solicitud HTTP POST. Habitualmente
designa métodos que realizan una actualización.
@PUT Esta anotación designa a un método que responderá a la solicitud HTTP PUT. Habitualmente
designa métodos que realizan una alta.
@DELE Esta anotación designa a un método que responderá a la solicitud HTTP DELETE. Habitualmente
TE designa métodos que realizan una baja.
@HEAD Esta anotación designa a un método que responderá a la solicitud HTTP HEAD.
@PathP Informa al contenedor de que debe recuperar el valor de un parámetro anotado desde un segmento
aram de la URI. Se utiliza para establecer la correspondencia entre un parámetro de un método de servicio
y la variable definida con el patrón de la ruta.
@Quer Informa al contenedor de que debe recuperar el valor de un parámetro anotado desde un parámetro
yParam de la cadena de consulta de la URI.
@Cons Especifica el tipo MIME de los datos enviados por un cliente hacia un recurso que los consume.
umes
@Produ Especifica el tipo MIME de los datos producidos por un recurso y que son recibidos por el cliente.
ces
@Provi Se usa esta anotación para cualquier cosa que sea de interés para el intérprete JAX-RS.
der
2.2. Ejemplo de servicio web RESTful.
Crearemos ahora un servicio web RESTful usando NetBeans. Como se utiliza el protocolo HTTP para
acceder a los servicios web RESTful primero hay que crear una aplicación web. Para este ejemplo se supone
creada una aplicación web llamada WebServicioREST para el servidor GlassFish.
Añadimos un «RESTful Web Services from Patterns» de la categoría «Web Services»:
Figura 14

Con esta plantilla se creará un servicio web de tipo singleton sin estar asociado a ninguna entidad de datos.
En el paso siguiente hay que seleccionar «Simple Root Source» para crear una clase raíz de recursos RESTful.

373
Figura 15

En el paso siguiente debemos establecer el paquete donde se generarán las clases (en este ejemplo, rest), la
ruta para invocar el servicio (en este caso, generic), el nombre de la clase (GenericTest), y el tipo MIME de
los datos que se enviarán y recibirán del servicio (para simplificar se ha seleccionado text/plain).
Figura 16

Tras finalizar, en el proyecto se generan dos clases: ApplicationConfig y GenericTest.

374
Figura 17

La clase ApplicationConfig está definida de la siguiente forma:


@javax.ws.rs.ApplicationPath("webresources")
public class ApplicationConfig extends Application {
……………..
}
Esta clase es utilizada para es utilizada para registrar todos los servicios web RESTful de la aplicación. La
anotación @ApplicationPath establece la ruta de aplicación que se utiliza de base para las URIs de recursos.
Por tanto, suponiendo que usamos el puerto 8088 y el host local, las URLs para invocar los servicios web
RESTful de esta aplicación comenzarán por:
http://localhost:8088/WebServicioREST/webresources/...
La clase GenericTest es la clase raíz del recurso y su contenido inicial es:
package rest;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PUT;
@Path("generic")
public class GenericTest {
@Context
private UriInfo context;
public GenericTest() {
}
@GET
@Produces("text/plain")
public String getText() {
thrownewUnsupportedOperationException();
}
@PUT
@Consumes("text/plain")
public void putText(String content) {
}
}

375
La anotación @Path establece la ruta relativa para este recurso. Esta ruta se combina con la ruta de la
aplicación definida por @ApplicationPath para establecer la ruta absoluta de acceso a este recurso. Por
tanto, si en un navegador se invoca la URL en su barra de dirección:
http://localhost:8088/WebServicioREST/webresources/generic
Obtendremos como resultado un «HTTPStatus500-InternalServerError» producido por el
UnsupportedOperationException que lanza el método getText(). Un navegador realiza una invocación
de una URL por el método HTTP GET y por tanto el servicio ejecuta el método final anotado con @GET.
Vamos a cambiar el código de esta clase para que sus métodos no lancen excepciones y realicen alguna tarea.
package rest;
import javax.ws.rs.core.*;
import javax.ws.rs.*;
@Path("generic")
public class GenericTest {
@Context
private UriInfo context;
public GenericTest() {
}
@GET
@Produces("text/plain")
public String getText() {
return"Funciona";
}
@PUT
@Consumes("text/plain")
public void putText(String content) {
System.out.println("Se ha recibido: " + content);
}
@DELETE
@Path("{content}")
public void removeText(@PathParam("content") String content) {
System.out.println("Se ha borrado: " + content);
}
}
A continuación se comentan las anotaciones usadas en este ejemplo:
• El valor de la anotación @Path es relativa a la URI base de la aplicación cuando se aplica sobre una clase.
Cuando se aplica sobre un método la URI es relativa a la URI de la clase. El uso de barras '/' iniciales en
estas rutas es ignorado. Se pueden especificar variables de ruta ubicándolas entre llaves; estas variables
ayudan a identificar partes de la URL como datos.
Para este ejemplo se establece que la ruta para invocar el método putText() es:
http://localhost:8088/WebServicioREST/webresources/generic/{content}
Donde {content} puede ser cualquier cosa.
• La anotación @Context permite inyectar un objeto de tipo UriInfo, el cual proporciona métodos para
manipular la ruta que invoca al servicio.
• La anotación @GET es el designador de método, al igual que @PUT, @POST,@DELETE y @HEAD.
• La anotación @Produces especifica el tipo MIME del dato que retornará el método getText(), en este
caso un texto plano.
• La anotación @Consumes especifica el tipo MIME del dato que recibirá el método putText(), en este
caso un texto plano.
• La anotación @PathParam permite asociar el parámetro del método removeText() con la ruta parcial
asignada al método. Si el parámetro no es anotado, por defecto, recupera el valor del parámetro del cuerpo
de la solicitud.
2.3.Laanotación@Path y plantillas de rutas.
La anotación @Path identifica la plantilla de la ruta a la que responderá el recurso, y se puede especificar a
nivel de clase o de método. El valor de la anotación @Path es una ruta parcial relativa a la URI base del
servidor donde es instalado el servicio y a la raíz del contexto de la aplicación.

376
La plantilla de ruta puede llevar variables embebidas. Estas variables son sustituidas en tiempo de ejecución
por el segmento de ruta correspondiente. Las variables se encapsulan entre llaves. Por ejemplo:
@Path("/clientes/{nombre}")
En este ejemplo, un cliente puede invocar esta ruta escribiendo su nombre al final de la misma. El servicio
web podrá recuperar este valor a través de un parámetro y responder de manera personalizada.
Para obtener el valor de la variable de ruta podemos usar la anotación @PathParam sobre el parámetro del
método correspondiente a la solicitud, tal como se muestra en el siguiente código:
@Path("/clientes/{nombre}")
public class ClienteRecurso {
@GET
@Produces("text/xml")
public String getCliente(@PathParam("nombre") String nombre) {
..........................
}
}
Pordefecto,lavariablederutadebecasarlaexpresiónregular"[^/]+?". Pero se puede expresar una expresión
regular diferente después del nombre de variable. Por ejemplo, si el nombre de cliente debe constar sólo de
leteas podemos reescribir la expresión regular de la siguiente manera:
@Path("clinetes/{nombre:[a-zA-Z]+}")
Las rutas de @Path no necesitan comenzar por la barra /, ya que el intérprete JAX-RS parsea la URI de la
misma manera tanto si comienzan por barra como si no. Si se quieren incluir espacios en blanco en la ruta
deben sustituirse por %20.
Se puede incluir más de una variable de ruta e incluso pueden repetirse los nombres de las variables de ruta.
Por ejemplo:
@Path("/clientes/{nombre}/{apellidos}")
public class ClienteRecurso {
@GET
@Produces("text/xml")
public String getCliente(@PathParam("nombre") String nombre,
(@PathParam("apellidos") String apellidos) {
..........................
}
}
De esta manera se puede utilizar la siguiente URI para invocar el método getCliente():
http://mi.dominio.com/clientes/Sergio/López@20Rubén
2.4. Respondiendo a métodos y solicitudes HTTP.
La funcionalidad de un recurso viene determinada por el método HTTP al que responde. Una solicitud GET
debería retornar el resultado de una consulta, una solicitud PUT debería insertar algo, etc.
Los métodos decorados con los designadores @GET, @PUT, ... deben retornar void, un tipo del lenguaje de
programación de Java o un objeto javax.ws.rs.core.Response.
Se utiliza un objeto Response para proporcionar metadatos adicionales con una respuesta. Por ejemplo, el
siguiente método responderá a una solicitud GET redireccionando al cliente a otra URL:
@GET
public Response get() {
return Response.seeOther(URI.create("http://www.google.es")).build();
}
La clase Response posee métodos estáticos que retornan un ResponseBuilder, el cual proporciona un
modo conveniente de crear una instancia de Response usando el patrón "Builder".
Algunos de los otros métodos que podemos usar de esta clase son:
• Response.ok().build(), para retorna un código estado OK.
• Response.ok(Object entidad).build(), para retorna un código estado OK con un objeto de entidad en
el cuerpo de la respuesta.
• Response.serverError().build(), para retorna un código de error.

377
3. Clientes de servicios web RESTful.
Veamos ahora cómo crear aplicaciones clientes que invoquen los recursos y sub-recursos de un servicio web
RESTful. Cualquier tecnología que permita realizar solicitudes HTTP puede ser utilizada para invocar los
métodos del servicio. Aquí veremos algunas de estas tecnologías.
3.1. Un cliente RESTful usando JAX-RS.
Primero crearemos un cliente del servicio web WebServicioREST usando la plantilla que ofrece NetBeans.
Por simplicidad, se usará como soporte una aplicación de consola, pero el proceso de creación de un cliente
RESTful es el mismo para otro tipo de aplicaciones.
En una aplicación de consola llamada ClienteServicioREST hay que agregar un «RESTful Java Client» de la
categoría «Web Services».
Figura 18

En el siguiente paso hay que dar un nombre a la clase cliente (en este ejemplo JerseyCliente) y un paquete
(jersey). Se debe seleccionar el proyecto que contiene al servicio, o bien se puede seleccionar un servicio
RESTful registrado por alguno de los proveedores conocidos de Internet.

378
Figura 19

En el proyecto se crea la clase JerseyCliente:


Figura 20

Como los servicios web RESTful no son publicados, a diferencia de los servicios web con WSDL, la clase
jersey.JerseyCliente debe ser construida a partir del propio proyecto del servicio RESTful. Esta clase
proporciona métodos de conveniencia para realizar las solicitudes y retornar los resultados de los métodos del
servicio.
package jersey;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.WebTarget;
public class JerseyCliente {
private WebTarget webTarget;
private Client client;

379
private static final String BASE_URI = "http://localhost:8088/WebServicioREST/webresources";
public JerseyCliente() {
client = javax.ws.rs.client.ClientBuilder.newClient();
webTarget = client.target(BASE_URI).path("generic");
}
public void removeText(String content) throws ClientErrorException {
webTarget.path(java.text.MessageFormat.format("{0}", new Object[]{content})).request().delete();
}
public String getText() throws ClientErrorException {
WebTarget resource = webTarget;
return resource.request(javax.ws.rs.core.MediaType.TEXT_PLAIN).get(String.class);
}
public void putText(Object requestEntity) throws ClientErrorException {
webTarget.request(
javax.ws.rs.core.MediaType.TEXT_PLAIN)
.put(javax.ws.rs.client.Entity.entity(requestEntity, javax.ws.rs.core.MediaType.TEXT_PLAIN));
}
public void close() {
client.close();
}
}
La forma de usar esta clase es la siguiente:
package main;
import jersey.JerseyCliente;
public class Main {
public static void main(String[] args) throws Exception {
jersey.JerseyCliente cliente = new JerseyCliente();
System.out.println(cliente.getText());
cliente.putText("prueba");
cliente.removeText("prueba");
cliente.close();
}
}
Simplemente debemos obtener una instancia de JerseyCliente y llamar a sus métodos pasando los
argumentos adecuados al formato esperado por los métodos del servicio web RESTful.
3.2. Un cliente RESTful usando «HttpURLConnection».
Como un servicio web RESTful permite ser accedido mediante el protocolo HTTP usando métodos de
solicitud podemos usar varías técnicas que permiten conexiones HTTP. Una de ellas es el uso de objetos
java.net.HttpURLConnection.
En cualquier tipo de proyecto podemos realizar una solicitud GET con el siguiente código:
URL url = new URL("http://localhost:8088/WebServicioREST/webresources/generic");
HttpURLConnection conexion = (HttpURLConnection) url.openConnection();
conexion.setRequestMethod("GET");
conexion.addRequestProperty("Content-Type","text/plain");
conexion.setDoInput(true);
BufferedReader br = new BufferedReader(new InputStreamReader(conexion.getInputStream()));
System.out.println(br.readLine());
br.close();
A partir de una URL podemos obtener un URLConnection, que según el protocolo usado en la URL
podemos moldear al tipo HttpURLConnection. Los pasos a seguir con un HttpURLConnectionson:
1) Asignar el método de solicitud mediante setRequestMethod().
2) Establecer si la solicitud recibirá datos con setDoInput(true) o enviará datos con setDoOutput(true).
3) Usar el canal de salida getOutputStream() para enviar los datos o usar el canal de entrada
getInputStream() para recuperar datos.
4) Si no se envían ni recuperan datos llamar a un método de invocación, como getResponseCode() o
connect().
El siguiente código muestra cómo invocar el método de servicio removeText():

380
URL url = new URL("http://localhost:8088/WebServicioREST/webresources/generic/prueba");
HttpURLConnection conexion = (HttpURLConnection) url.openConnection();
conexion.setRequestMethod("DELETE");
System.out.println(conexion.getResponseCode());
El método getResponseCode() debe retornar el código «204 - Sincontenido», puesto que el método
removeText() no retorna nada.
El siguiente código muestra cómo invocar el método de servicio putText():
URL url = new URL("http://localhost:8088/WebServicioREST/webresources/generic");
HttpURLConnection conexion = (HttpURLConnection) url.openConnection();
conexion.setRequestMethod("PUT");
conexion.addRequestProperty("Content-Type","text/plain");
conexion.setDoOutput(true);
PrintWriter pw = new PrintWriter(conexion.getOutputStream());
pw.write("prueba");
pw.close();
System.out.println(conexion.getResponseCode());
3.3. Un cliente web usando JavaScript.
Podemos invocar fácilmente un servicio web RESTful desde una página HTML usando código JavaScript
mediante un objeto XMLHttpRequest.
El siguiente código muestra una página HTML con un botón que invoca los tres métodos del servicio
WebServicioREST:
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<button onclick="probarREST()">probar servicio</button>
<script type="text/javascript">
function probarREST() {
// INVOCACIÓN DE getText()
var requestGet = new XMLHttpRequest();
requestGet.open("GET", "http://localhost:8088/WebServicioREST/webresources/generic",true);
requestGet.send();
requestGet.onreadystatechange = function () {
if (requestGet.readyState==4)
alert(requestGet.responseText);
}
// INVOCACIÓN DE removeText()
var requestDelete = new XMLHttpRequest();
requestDelete.open("DELETE",
"http://localhost:8088/WebServicioREST/webresources/generic/prueba",true);
requestDelete.send();
// INVOCACIÓN DE putText()
var requestPut = new XMLHttpRequest();
requestPut.open("PUT", "http://localhost:8088/WebServicioREST/webresources/generic",true);
requestPut.send("prueba");
}
</script>
</body>
</html>
3.4.Usando@Produces para personalizar la respuesta.
Se usa la anotación @Produces para especificar el tipo MIME o representación del recurso que produce y
envía el servicio al cliente. Si se aplica @Produces a nivel de clase, todos los métodos podrán producir el tipo

381
MIME especificado por defecto. Si se aplica a nivel de método, la anotación reescribe la aplicada a nivel de
clase.
Si ningún método del recurso es capaz de producir el tipo MIME ente la solicitud cliente, el intérprete JAX-
RS enviarán un error HTTP "406 Not Acceptable".
El valor de la anotación @Produces es un array de strings de tipos MIME. Por ejemplo:
@Produces({"image/jpeg,image/png"})
El siguiente ejemplo muestra cómo aplicar @Produces a nivel de clase y de método:
@Path("/miResource")
@Produces("text/plain")
publicclassAlgunRecurso{
@GET
publicStringdoGetComoTextoPlano(){
.................
}
@GET
@Produces("text/html")
publicStringdoGetComoHtml(){
.................
}
}
El método doGetComoTextoPlano() por defecto retorna el tipo MIME "text/plain", ya que lo hereda del
nivel de clase. El método doGetComoHtml() reescribe la anotación a nivel de clase y retorna el tipo MIME
"text/html".
Nota. Como se ve en el ejemplo previo, en una clase de servicio podemos incluir varios métodos para el
mismo tipo de invocación siempre y cuando se diferencien en el tipo MIME que retornen. Según el tipo
de datos especificado por el cliente se invocará uno u otro método.
Se puede especificar más de un tipo en la misma declaración @Produces. Por ejemplo:
@Produces({"application/xml","application/json"})
publicStringdoGetComoXmlOJson(){
.................
}
El método doGetComoXmlOJson() será invocado tanto si el cliente solicita los datos en formato XML
como JSon.
3.5. Usando@Consumesparapersonalizarlasolicitud.
Se utiliza la anotación @Consumes para especificar el tipo MIME o representación del recurso que aceptará
o se consumirá desde el cliente. Si se aplica @Consumes a nivel de clase, todos los métodos de respuesta
aceptarán el tipo MIME especificado por defecto. Si se aplica a nivel de método, la anotación reescribe la
aplicada a nivel de clase.
Si ningún método del recurso es capaz de consumir el tipo MIME de la solicitud cliente, el intérprete JAX-RS
enviará un error HTTP "415 UnsupportedMediaType".
El valor de la anotación @Consumes es un array de strings de tipos MIME. Por ejemplo:
@Consumes({"text/plain,text/html"})
El siguiente ejemplo muestra cómo aplicar @Consumestanto a nivel de clase como de método:
@Path("/myResource")
@Consumes("multipart/related")
publicclassAlgunRecurso{
@POST
publicStringdoPost(MimeMultipartmimeMultipartData){
.................
}
@POST
@Consumes("application/x-www-form-urlencoded")
publicStringdoPost2(FormURLEncodedPropertiesformData){
.................
}
}

382
El método doPost() acepta por defecto el tipo MIME de la anotación a nivel de clase, mientras que el
método doPost2() reescribe el tipo MIME por defecto para aceptar datos codificados de un formulario.
3.5.1. Consumiendo datos desde la cadena de consulta.
Se pueden extraer los datos del tipo MIME especificado desde la cadena de consulta de la URI. Se puede
especificar un parámetro anotado con javax.ws.rs.QueryParam en el método del servicio.
El siguiente ejemplo, muestra un servicio web RESTful que define métodos de gestión de empleado. El
método deleteCliente() debe eliminar un empleado a partir de su nombre e ID de departamento:
packagerest;
importjavax.ws.rs.Consumes;
importjavax.ws.rs.DELETE;
importjavax.ws.rs.DefaultValue;
importjavax.ws.rs.Path;
importjavax.ws.rs.QueryParam;
@Path("cliente")
publicclassEmpleadosRecurso{
……………………..
@DELETE
@Consumes("text/plain")
publicvoid deleteCliente(
@DefaultValue("1")@QueryParam("idDep")intidDep,
@QueryParam("nombre")Stringnombre
){
System.out.printf("Seha eliminado al empleado %s del ID de departamento %d\n", nombre, idDep);
}
}
Una aplicación cliente podrá invocar el método deleteCliente() con una solicitud como:
DELETE/cliente?idDep=23&nombre=Sergio%20MartinezHTTP/1.0
La anotación @DefaultValue permite especificar un valor por defecto cuando falte un parámetro en la
cadena de consulta. Si en la cadena de consulta no se especifica un parámetro idDep el método tomará por
defecto el valor 1.
La anotación @QueryParam sólo puede ser usada sobre los siguientes tipos de Java:
• Todos los tipos primitivos excepto char.
• Todas las clases encapsuladoras de los tipos primitivos excepto Character.
• Cualquier clase con un constructor con un único parámetro de tipo String.
• List<T>, Set<T> o SortedSet<T>. Algunas veces los parámetros pueden tener uno o varios valores
con el mismo nombre. En estos casos se pueden usar colecciones para obtener todos los valores.
Si no se especifica @DefaultValue con @QueryParam, y no está presente el parámetro, el valor será una
colección vacía, null o el valor por defecto para los tipos primitivos.
3.5.2. Consumiendo datos desde la ruta de la URI.
Los parámetros de ruta son extraídos desde la URI solicitad, y el nombre del parámetro se corresponde con
un nombre de variable especificado en la anotación @Path. Los parámetros de ruta se especifican usando la
anotación javax.ws.rs.PathParam en el parámetro del método. Por ejemplo:
packagerest;
importjavax.ws.rs.Consumes;
importjavax.ws.rs.DELETE;
importjavax.ws.rs.Path;
importjavax.ws.rs.PathParam;
@Path("cliente")
publicclassEmpleadosRecurso{
……………………..
@DELETE
@Consumes("text/plain")
@Path("{idDep}/{nombre}")
publicvoid deleteCliente(
@PathParam("idDep")intidDep,
@PathParam("nombre")Stringnombre

383
){
System.out.printf("Seha eliminado al empleado %s del ID de departamento %d\n", nombre, idDep);
}
}
En este ejemplo, la ruta parcial del método deleteCliente() incluye dos variables de ruta: {idDep} y
{nombre}. Se utilizan las anotaciones @PathParam para asociar estas variables de ruta con el parámetro
correspondiente del método.
Una aplicación cliente podrá invocar el método deleteCliente() con una solicitud como:
DELETE/cliente/23/Sergio%20MartinezHTTP/1.0
Si la plantilla de la URI no puede casar una de las variables de ruta, el intérprete JAX-RS retornará un error
HTTP "400-BadRequest".
La anotación @PathParam sigue las mismas reglas de mapeado con los tipos de Java que @QueryParam.
3.5.3. Consumiendo datos desde cookies.
Se indican parámetros procedentes de cookies decorándolos con la anotaciónjavax.ws.rs.CookieParam. El
valor del parámetro será extraído de la cookie declarada en las cabeceras HTTP que coincida con el nombre
dado.Por ejemplo:
packagerest;
importjavax.ws.rs.Consumes;
importjavax.ws.rs.DELETE;
importjavax.ws.rs.Path;
importjavax.ws.rs.CookieParam;
@Path("cliente")
publicclassEmpleadosRecurso{
……………………..
@DELETE
@Consumes("text/plain")
publicvoid deleteCliente(
@CookieParam("idDep")intidDep,
@CookieParam("nombre")Stringnombre
){
System.out.printf("Seha eliminado al empleado %s del ID de departamento %d\n", nombre, idDep);
}
}
La anotación @CookeParam sigue las mismas reglas de mapeado con los tipos de Java que @QueryParam.
3.5.4. Consumiendo datos desde cabeceras HTTP.
Se indican parámetros procedentes de cabeceras HTTP decorándolos con la
anotaciónjavax.ws.rs.HeaderParam. El valor del parámetro será extraído de la cabecera HTTP que
coincida con el nombre dado.Por ejemplo:
packagerest;
importjavax.ws.rs.Consumes;
importjavax.ws.rs.DELETE;
importjavax.ws.rs.Path;
importjavax.ws.rs.HeaderParam;
@Path("cliente")
publicclassEmpleadosRecurso{
……………………..
@DELETE
@Consumes("text/plain")
publicvoid deleteCliente(
@HeaderParam("idDep")intidDep,
@HeaderParam("nombre")Stringnombre
){
System.out.printf("Seha eliminado al empleado %s del ID de departamento %d\n", nombre, idDep);
}
}
Un ejemplo de código cliente que invoca el método deleteCliente() mediante HttpURLConnection es:
URL url = new URL("http://localhost:8088/WebServicioREST/webresources/cliente");
HttpURLConnection conexion = (HttpURLConnection) url.openConnection();

384
conexion.setRequestMethod("DELETE");
conexion.addRequestProperty("idDep", "345");
conexion.addRequestProperty("nombre", "Sergio Martinez");
System.out.println(conexion.getResponseCode());
La anotación @HeaderParam sigue las mismas reglas de mapeado con los tipos de Java que
@QueryParam.
3.5.5. Consumiendo datos desde formularios.
Se indican parámetros procedentes de formulario decorándolos con la anotaciónjavax.ws.rs.FormParam.
El valor del parámetro será extraído desde una representación del tipo MIME application/x-www-form-
urlencoded, que es el formato con el cual los formularios HTML codifican los datos posteados en el cuerpo
de la solicitud.Por ejemplo:
packagerest;
importjavax.ws.rs.Consumes;
importjavax.ws.rs.POST;
importjavax.ws.rs.Path;
importjavax.ws.rs.FormParam;
@Path("cliente")
publicclassEmpleadosRecurso{
……………………..
@POST
@Consumes("application/x-www-form-urlencoded")
publicvoid postCliente(
@FormParam("id")int id,
@FormParam("nombre")Stringnombre
){
System.out.printf("Seha modificado al empleado de código %d con el nombre %s \n", id, nombre);
}
}
Un cliente del método postCliente() puede ser un formulario HTML que postee al id y nombre del cliente.
También se puede codificar los datos con HttpURLConnection es:
URL url = new URL("http://localhost:8088/WebServicioREST/webresources/cliente");
HttpURLConnection conexion = (HttpURLConnection) url.openConnection();
conexion.setRequestMethod("POST");
conexion.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conexion.setDoOutput(true);
PrintWriter pw = new PrintWriter(conexion.getOutputStream());
pw.write("id=3434&nombre=Sergio+Martinez");
pw.close();
System.out.println(conexion.getResponseCode());
La anotación @FormParam sigue las mismas reglas de mapeado con los tipos de Java que @QueryParam.
3.5.6. Consumiendo datos desde parámetros Matrix.
Los parámetros Matrix son extraídos desde la URI solicitada. Los parámetros Matrix son asignados como
pares nombre=valor dentro de la URI, como por ejemplo:
/libros/2011;autor=Cervantes
Los parámetros Matrix se especifican usando la anotación javax.ws.rs.MatrixParam en el parámetro del
método. Por ejemplo:
packagerest;
importjavax.ws.rs.Consumes;
importjavax.ws.rs.DELETE;
importjavax.ws.rs.Path;
importjavax.ws.rs.MatrixParam;
@Path("cliente")
publicclassEmpleadosRecurso{
……………………..
@DELETE
publicvoid deleteCliente(
@MatrixParam("idDep")intidDep,
@MatrixParam("nombre")Stringnombre

385
){
System.out.printf("Seha eliminado al empleado %s del ID de departamento %d\n", nombre, idDep);
}
}
Una aplicación cliente podrá invocar el método deleteCliente() con una solicitud como:
DELETE/cliente;idDep=23;nombre=Sergio%20MartinezHTTP/1.0
La anotación @MatrixParam sigue las mismas reglas de mapeado con los tipos de Java que @QueryParam.
3.5.7. Obteniendo un mapa general de parámetros.
Para obtener un mapa general de los nombres y valores de parámetros procedentes de la URI podemos
utilizar un objeto UriInfo inyectado en el código. Para obtener las cabeceras y cookies enviadas también
podemos inyectar un objeto HttpHeaders.
El siguiente ejemplo muestra cómo inyectar estos objetos y acceder a sus colecciones de parámetros:
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
importjavax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Cookie;
importjava.util.Map;
@Path("cliente")
publicclassEmpleadosRecurso{
……………………..
@GET
@Produces({"text/plain"})
public String get(@ContextUriInfo context) {
MultivaluedMap<String, String> queryParams = context.getQueryParameters();
MultivaluedMap<String, String> pathParams = context.getPathParameters();
……………………..
}
En general, la anotación @Context permite inyectar objetos del contexto de Java relacionados con las
solicitudes y respuestas.
Para los parámetros de formulario es posible también asociarlos a un único parámetro:
@POST
@Consumes("application/x-www-form-urlencoded")
public void post(MultivaluedMap<String, String> formParams) {
………………………….
}
3.5.8. Usar clases personalizadas como parámetros.
El intérprete JAX-RS es capaz de realizar las conversiones adecuadas entre los datos enviados y los tipos de
los parámetros de los métodos consumidores. Pero habrá escenarios donde queramos personalizar estos
datos para realizar conversiones automáticas.
Por ejemplo, podemos crear una clase que convierta un string con el nombre de un color a un objeto de tipo
Color.
package param;
import java.awt.Color;
import java.util.HashMap;
import java.util.Map;
public class ColorParam extends Color {
public ColorParam(String color) {
super(mapaColores.get(color));
}
private final static Map<String, Integer> mapaColores;
static {
mapaColores = new HashMap<>();
mapaColores.put("blanco", Color.white.getRGB());
mapaColores.put("azul", Color.blue.getRGB());

386
mapaColores.put("rojo", Color.red.getRGB());
mapaColores.put("verde", Color.green.getRGB());
mapaColores.put("negri", Color.black.getRGB());
}
}
Un servicio puede recibir como parámetro un objeto ColorParam:
package rest;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import param.ColorParam;
@Path("generic")
public class UnRecurso {
@PUT
@Path("{color}")
public void putColor(@PathParam("color") ColorParam id) {
………………………..
}
}
Una aplicación cliente podrá invocar el método putColor() con una solicitud como:
PUT/generic/blancoHTTP/1.0

4. Técnicas avanzadas con JAX-RS


4.1. Sub-recursos.
Las clases de recurso raíz pueden delegar en otras clases de sub-recurso la resolución de la parte final de la
URI solicitada.
La clase de recurso raíz puede procesar sólo parte de la URI solicitada, y el método retorna un objeto de una
clase que resuelve el trozo final de la URI. En este caso se dice que el método es un localizador de recurso.
Un método de la clase de recurso que es anotado con @Path puede ser tanto un método de sub-recurso
como un localizador de sub-recurso.
• Se usa un método de sub-recurso para gestionar las solicitudes sobre un sub-recurso del correspondiente
recurso.
• Se usa un localizador de sub-recurso para localizar sub-recursos del correspondiente recurso, delegándolos
en otra clase.
4.1.1. Métodos de sub-recurso.
Un método de sub-recurso gestiona una solicitud HTTP directamente, y en este caso el método debe estar
anotado con un designador como @GET o @POST, además de @Path. El método es invocado por las URIs
solicitadas que casen con la plantilla concatenada de las anotaciones @Path.
Por ejemplo, el siguiente código muestra un método de sub-recurso que puede utilizarse para extraer los
apellidos de un empleado proporcionados en una dirección de correo concatenada en la URI:
@Path("/infoempleado")
publicclassInfoEmpleado{
publicemployeeinfo(){
}
@GET
@Path("/empleados/{nombre}.{apellidos}@{dominio}.com")
@Produces("text/xml")
publicStringgetApellidos(@PathParam("apellidos")String apellidos){
return apellidos;
}
}
El método getApellidos() retornará martinez para la siguiente solicitud GET:
GET /infoempleados/empleados/[email protected]
4.1.2. Localizadores de sub-recursos.
Un localizador de sub-recursos es un método de la clase de recurso que retorna un objeto en respuesta a una
solicitud HTTP. En este caso el método no debe estar anotado con ningún designador.

387
Por ejemplo, supongamos la siguiente clase RepositorioEmpleado que gestiona objetos Empleado, cuya
clase definiremos después:
package servicio;
import java.util.*;
public class RepositorioEmpleado {
private static List<Empleado> empleados=new ArrayList<>();
static {
// Datos de prueba:
empleados.add(new Empleado(1,"Juan", "Perez"));
empleados.add(new Empleado(2,"Maria", "Lopez"));
}
public Empleado getEmpleado(int id) {
return empleados.stream().filter(e->e.getId()==id).findFirst().get();
}
}
El siguiente código muestra una clase de recurso (InfoEmpleado) y una clase de sub-recurso (Empleado)
que permiten recuperar un empleado.
packagerest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import servicio.*;
// LA CLASE RAÍZ DE RECURSO
@Path("/infoempleado")
publicclassInfoEmpleado{
// LOCALIZADOR DE SUBRECURSO: RECUPERA UN RECURSO Empleado
// DESDE LA RUTA /infoempleado/empleados/{idEmpleado}
@Path("/empleados/{idEmpleado}")
public Empleado getEmpleado(@PathParam("idEmpleado")Stringid){
RepositorioEmpleado repositorio=new RepositorioEmpleado();
return repositorio.getEmpleado(id);
}
}

package servicio;
import javax.ws.rs.Path;
import javax.ws.rs.GET;
// CLASE DE SUBRECURSO
public class Empleado{
private int id;
private String nombre;
private String apellidos;
public Empleado(int id, String nombre, String apellidos) {
this.id = id;
this.nombre = nombre;
this.apellidos = apellidos;
}
// ………….. getters y setters ……………..
// MÉTODO DE SUBRECURSO: retorna los apellidos del empleado
@GET
@Path("/apellidos")
publicStringgetApellidos (){
return apellidos;
}
}
En este código, el método InfoEmpleado.getEmpleado() es el localizador de sub-recurso que proporciona
un objeto Empleado. La clase Empleado es una clase de sub-recurso con un método de sub-recurso que
devuelve los apellidos del empleado.

388
Suponiendo que la ruta raíz de la aplicación del servicio es /WebServicioREST/webresources, si desde un
navegador invocamos una ruta como esta:
http://localhost:8088/WebServicioREST/webresources/infoempleado/empleados/3
El servidor devolverá el error HTTP "500-InternalServerError", puesto que el método
InfoEmpleado.getEmpleado() que casa con la URI no es un método designado y no puede resolver la
consulta. Pero si invocamos la ruta:
http://localhost:8088/WebServicioREST/webresources/infoempleado/empleados/1/apellidos
En el navegador se mostrará:
Perez
En tiempo de ejecución se resuelve la ruta a través del método localizador de sub-recursos. El objeto
Empleado por InfoEmpleado.getEmpleado() casa la URI con su método Empleado.getEmpleado() y
retorna el apellido.
4.2. Integración de JAX-WS y JAX-RS con EJBs y CDI
Tanto JAX-WS como JAX-RS pueden integrarse con la tecnología de los Enterprise Beans y la tecnología de
Inyección de Contextos y Dependencias (CDI).
En general se puede anotar la clase de un bean con @Path para convertirlo en una clase raíz de recursos. Se
puede usar @Path con beans de sesión stateless y beans de sesión singleton.
Por ejemplo, el siguiente código muestra un bean de sesión stateless convertido a una clase raíz de recurso:
package beans;
import javax.ejb.Stateless;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@Stateless
@Path("beanstateless")
public class BeanRestful {
@GET
public String get() {
return "bean stateless";
}
}
También se pueden usar beans de sesión para sub-recursos.
JAX-WS, JAX-RS y CDI tienen un modelo de componentes diferentes. Por defecto, las clases raíces de
recursos son gestionadas en el ámbito de solicitud, y
nosenecesitananotacionesparaespecificaresteámbito.LosbeansgestionadosporCDIyqueesténanotadoscon@Re
questScopedo@ApplicationScoped pueden convertirse a clases de recurso JAX-RS o JAX-WS.
Por ejemplo, supongamos una clase que ofrece funcionalidades específicas:
package servicios;
import javax.enterprise.inject.Model;
@Model
public class Procesa {
………………………
public String convierte(String txt) {
return txt.toUpperCase();
}
}
Podemos usar una instancia de esta clase en un servicio inyectándola:
@WebService(serviceName = "WebServicioTest")
@RequestScoped
public class WebServicioTest {
@Inject
private Procesa procesa;
@WebMethod(operationName = "hello")
public String hello(@WebParam(name = "name") String txt) {
return "Hello " + procesa.convierte(txt) + " !";
}
}

389
La clase inyectable no debe ser final y debe tener un constructor sin argumentos.
4.3. Negociación del contenido en tiempo de ejecución.
Las anotaciones @Produces y @Consumes permiten gestionar una negociación estática sobre el contenido
en JAX-RS. Estas anotaciones especifican el contenido preferido por el servidor como una lista de tipos
MIME. Cabeceras HTTP como Accept,Content-Type yAccept-Language permiten negociar desde el lado
cliente el tipo de contenido preferido.
Por ejemplo, suponiendo que EmpleadoInfo sea una clase de entidad anotada, la siguiente clase muestra las
preferencias de contenido del servidor:
@Produces("application/json")
@Path("/empleado")
public class Empleado {
@PersistenceContext
private EntityManager em;
@GET
@Path("{id}")
public EmpleadoInfo getDatosJSon(@PathParam("id")Integerid) {
return em.find(EmpleadoInfo.class, id);
}
@GET
@Produces("text/xml")
@Path("{id}")
public String getDatosXml((@PathParam("id")Integerid) {
return em.find(EmpleadoInfo.class, id);
}
}
El método getDatosJson() es invocado si la solicitud se parece a lo siguiente:
GET /empleado
Accept: application/json
Y puede producir una respuesta parecida a esta:
{"id":"1", "nombre"="Juan", "apellidos": "Perez"}
El método getDatosXml() es invocado si la solicitud se parece a lo siguiente:
GET /empleado
Accept: text/xml
Y puede producir una respuesta parecida a esta:
<empleado><id>1</id><nombre>Juan</nombre><apellidos>Perez</apellidos></empleado>
Con negociaciones de contenido estático podemos definir varios contenidos y tipos de medios para el cliente
y el servidor. Por ejemplo:
@Produces("text/plain", "text/xml")
Además del soporte de negociación de contenido estático, JAX-RS también soporta negociación de
contenido en tiempo de ejecución usando la clase javax.ws.rs.core.Variant y objetos de solicitud. La clase
Variant especifica la representación de la negociación de contenido. Cada instancia de la clase Variant puede
contener un tipo MIME, un lenguaje y una codificación. El objeto Variant define la representación del
recurso que es soportado por el servidor. Se utiliza la claseVariant.VariantListBuilder para crear una lista de
representaciones de variantes.
El siguiente código muestra cómo crear una lista de variantes de representación de recursos.
List<Variant> vs =Variant.mediaTypes(
MediaType.TEXT_XML_TYPE, MediaType.APPLICATION_JSON_TYPE
).languages(Locale.US, Locale.FRANCE).build();
Este código llama al método build() de la clase VariantListBuilder; esta clase es invocada cuando llamamos
a los métodos mediatypes(), languages() y encodings(). El método build() construye una serie de
representaciones de recursos.
En este ejemplo, el tamaño de la lista vs será 4, y el contenido será como sigue:
[["application/xml","en-US"], ["application/json","en-US"],
["application/xml","fr-FR"],["application/json","fr-FR"]]

390
El método javax.ws.rs.core.Request.selectVariant() acepta una lista de objetos Variant y elige los
objetos Variant que casan con la solicitud HTTP. Este método compara su lista de objetos Variant con las
cabecerasAccept,Accept-Encoding,Accept-Language y Accept-Charset de la solicitud HTTP.
El siguiente código muestra cómo usar el método selectVariant() para seleccionar la variante más aceptable
de los valores solicitados por el cliente:
@GET
@Path("{id}")
@Produces({"text/xml", "application/json"})
public Response find(@PathParam("id") Integer id, @Context Request r) {
List<Variant> vs = Variant.mediaTypes(
MediaType.TEXT_XML_TYPE, MediaType.APPLICATION_JSON_TYPE
).languages(Locale.US, Locale.FRANCE).build();
Variant v = r.selectVariant(vs);
if (v == null) {
return Response.notAcceptable(vs).build();
} else {
return Response.ok(em.find(EmpleadoInfo.class, id)).build();
}
}
El método selectVariant() retorna el objeto Variant que casa con la solicitud, o null si no casa ninguno. En
este código, si el método retorna null, se retorna un objeto Response con una respuesta no aceptable, si no
se retorna un respuesta con el estado OK que contiene la entidad solicitada.
4.4. Servicio Web RESTful desde una base de datos.
Hasta el momento hemos visto los principios que rigen el desarrollo de un servicio web RESTful mediante
ejemplos sencillos que procesaban mensajes no conectados a datos. Ahora veremos cómo crear un servicio
web RESTful asociado a una base de datos mediante un contexto de Java Persistence.
4.4.1. Creación del servicio web RESTful asociado a una entidad.
Primero agregaremos las librerías de Jersey a un proyecto web creado para GlassFish (para este ejemplo se
han instalado las librerías del paquete «jersey-archive-1.18»), y configuraremos el archivo descriptor web.xml
para establecer el servlet que canalizará las invocaciones hacia Jersey.

391
Figura 21

Los elementos:
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
Establecen que las URIs de los servicios JAX-RS comenzarán por rest/.
A continuación crearemos una base de datos con Java Derby llamada Empresa con la siguiente tabla:
CREATE TABLE CLIENTE (
ID INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
NOMBRE VARCHAR(50),
TELEFONO VARCHAR(9)
);
INSERTINTOCLIENTEVALUES(1,"Cliente1","11111111");
INSERTINTOCLIENTEVALUES(2,"Cliente2","2222222");
Ahora ya podemos crear el servicio web a partir de la base de datos. Para ello debemos agregar un «RESTful
Web Services fromo DataBase».

392
Figura 22

En el siguiente paso crearemos primero un nuevo Data Source a partir de la cadena de conexión de Java
Derby.

Figura 23

A continuación debemos seleccionar la tabla CLIENTE.

393
Figura 24

En el siguiente paso hay que indicar un paquete en el cual se guardarán las clases de entidad, que en este caso
será la clase servicio.Cliente.
Figura 25

En el siguiente paso se indica el paquete donde se guardarán las clases de los recursos del servicio web.

394
Figura 26

Al finalizar se crean las siguientes clases:


• servicios.Cliente: la clase de entidad.
• servicios.service.ApplicationConfig: una clase de ayuda para configurar los recursos del servicio. No es
necesario modificar esta clase, ya que será utilizada automáticamente por NetBeans.
• servicios.service.AbstractFacade: una clase base que cumple con el patrón Session Façade, ya que
actúa como componente de fachada de sesión para centralizar el acceso a los servicios de la aplicación.
• servicios.service.ClienteFacadeREST: una subclase de servicios.service.AbstractFacade que
implementa las funcionalidades del servicio web RESTful.
La clase ClienteFacadeREST queda por defecto asociada a la subruta "/servicios.cliente", por lo cual su
ruta absoluta será "/rest/servicios.cliente". Por supuesto pueden modificarse estas rutas.
@Stateless
@Path("servicios.cliente")
public class ClienteFacadeREST extends AbstractFacade<Cliente> {
@PersistenceContext(unitName = "WebClientesRestPU")
private EntityManager em;
public ClienteFacadeREST() {
super(Cliente.class);
}
@POST
@Override
@Consumes({"application/xml", "application/json"})
public void create(Cliente entity) {
super.create(entity);
}
………………….
}
Tal como se puede comprobar, los métodos de sub-recursos de la clase ClienteFacadeREST permiten
trabajar con los tipos XML y JSON. En la clase será inyectada una instancia del EntityManagerque gestiona
el contexto de persistencia.
Tras desplegar la aplicación en el servidor GlassFish, podemos probar el servicio web directamente en un
navegador mediante la URI: http://localhost:8088/WebClientesRest/rest/servicios.cliente

395
Figura 27

El navegador realiza un solicitud GET solicitando por defecto contenido XML, la cual es procesada por el
método findAll(), que devuelve todos los registros de la base de datos.
La URI http://localhost:8088/WebClientesRest/rest/servicios.cliente/1 debe retornar sólo el primer
cliente.
La URI http://localhost:8088/WebClientesRest/rest/servicios.cliente/count debe retornar el número
de clientes en formato texto.
Podemos crear un cliente del servicio con las diversas técnicas vistas previamente.
4.4.2. Creación de un cliente que trabaje con datos JSon.
VeremoscómopuedetrabajarunaaplicaciónclientecondatosJSonpararealizaraltas,consultasyactualizaciones.Enes
teejemplocrearemosunapáginaweb HTML con un formulario para solicitar los datos de un cliente, insertar un
cliente y actualizar los datos de un cliente:
<!DOCTYPE html>
<html>
<head>
<title>Cliente servicio</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<fieldset>
ID <input type="number" min="1" id="id" value="1" /><br>
Nombre <input type="text" id="nombre" value="" /><br>
Teléfono <input type="text" id="telefono" value="" /><br>
<input type="submit" value="recuperar" onclick="recuperar()" />
<input type="submit" value="insertar" onclick="insertar()" />
<input type="submit" value="actualizar" onclick="actualizar()" />
</fieldset>
<script>
// LA URL BASE DEL SERVICIO
var url = "http://localhost:8088/WebServicioREST/rest/cliente";
// FUNCIÓN PARA RECUPERAR UN CLIENTE POR ID
Function recuperar() {
var request = new XMLHttpRequest();
var ruta = url + "/" + document.getElementById("id").value;
request.open("GET", ruta, true);
request.setRequestHeader("Accept","application/json");
request.send();
request.onreadystatechange = function() {
if (request.readyState === 4) {
var cliente = JSON.parse(request.responseText);
document.getElementById("nombre").value = cliente.nombre;

396
document.getElementById("telefono").value = cliente.telefono;
}
}
}
// FUNCIÓN PARA INSERTAR UN CLIENTE
Function insertar() {
// Se crea un cliente con los datos de los controles
var cliente = {};
cliente.nombre = document.getElementById("nombre").value;
cliente.telefono = document.getElementById("telefono").value;
// Se realiza la solicitud pasando el objeto cliente
var request = new XMLHttpRequest();
request.open("POST", url, true);
request.setRequestHeader("Content-Type", "application/json");
request.send(JSON.stringify(cliente));
// Se dejan vacíos los controles
document.getElementById("id").value = "";
document.getElementById("nombre").value = "";
document.getElementById("telefono").value = "";
}
// FUNCIÓN PARA ACTUALIZAR UN CLIENTE CON EL ID DADO
Function actualizar() {
// Se crea un cliente con los datos de los controles
var cliente = {};
cliente.id = document.getElementById("id").value;
cliente.nombre = document.getElementById("nombre").value;
cliente.telefono = document.getElementById("telefono").value;
// Se realiza la solicitud pasando el objeto cliente
var request = new XMLHttpRequest();
request.open("PUT", url, true);
request.setRequestHeader("Content-Type", "application/json");
request.send(JSON.stringify(cliente));
// Se dejan vacíos los controles
document.getElementById("id").value = "";
document.getElementById("nombre").value = "";
document.getElementById("telefono").value = "";
}
</script>
</body>
</html>

PRÁCTICA
Debes crear una aplicación empresarial basada en servicios web. Esta aplicación permitirá a los clientes
realizar operaciones sobre un almacén de reservas. Cada reserva constará de:
Un código numérico.
El nombre del cliente.
La fecha de la reserva.
Una descripción.
Las aplicaciones cliente deberán acceder a la aplicación empresarial mediante un servicio web, el cual
proporcionará métodos para crear una reserva, revocar una reserva por el nombre del cliente y la fecha,
consultar las reservas por fechas, consultar las reservas por nombre del cliente y modificar la fecha y
descripción de una reserva dado su código.
1) Crea primero un servicio web JAX-WS, y una aplicación cliente de escritorio.
2) Crea después un servicio web RESTful y una aplicación cliente formada por páginas web.

397
UNIDAD 20. APLICACIONES MVC
1. El marco Spring
Spring en un Framework basado en el patrón de diseño MVC. El patrón MVC se caracteriza por una fuerte
separación de la lógica de negocio, código de acceso a datos y la interfaz de usuario dentro de Modelos,
Controladores y Vistas.
1.1. Fundamentos de MVC.
ElModeloVistaControlador(MVC)esunpatróndearquitecturadesoftwarequeseparalosdatosylalógicadenegociod
eunaaplicacióndelainterfazdeusuarioyelmóduloencargadodegestionarloseventosylascomunicaciones.ParaelloM
VCproponelaconstruccióndetrescomponentesdistintosquesonelmodelo,lavistayelcontrolador;
esdecir,porunladodefinecomponentesparalarepresentacióndelainformación,yporotroladoparalainteraccióndelu
suario.Estepatróndediseñosebasaenlasideasdereutilizacióndecódigoylaseparacióndeconceptos,característicasqu
ebuscanfacilitarlatareadedesarrollodeaplicacionesy su posterior mantenimiento.
El siguiente diagrama ilustra sobre la arquitectura general de este patrón de diseño:
Figura 1

1.1.1 Modelos.
Cada sitio Web presenta información sobre los diversos tipos de objetos a los visitantes del sitio. Por ejemplo,
un sitio web de una editorial puede presentar información sobre los libros y autores. Un libro incluye
propiedades como el título, un resumen, y el número de páginas. Un autor puede tener propiedades tales
como un nombre y una breve biografía. Cada libro está vinculado a uno o varios autores.
Cuando se escribe una página web MVC para un editor, podemos crear un modelo con una clase para libros y
una clase para los autores. Estas clases del modelo incluirían las propiedades descritas y pueden incluir
métodos tales como "comprar este libro" o "contactar al autor". Si los libros y autores se almacenan en una
base de datos, el modelo puede incluir el código de acceso a datos para leer y modificar archivos.
En Spring los modelos son clases java-beans personalizadas.
1.1.2. Vistas.
Cada sitio web debe renderizar páginas HTML para que el navegador pueda mostrarlas. Esta renderización es
completada por Vistas. Por ejemplo, en el sitio de publicación, una vista puede recuperar los datos del
modelo del libro y renderizarlos en una página web para que el usuario pueda ver los detalles completos. En
las aplicaciones MVC, las Vistas crean la interfaz de usuario.
En Spring, normalmente las vistas serán páginas JSP.
1.1.3. Controladores.
Cada sitio web debe interactuar con los usuarios cuando hacen clic en los botones y enlaces. Los
Controladores responden a las acciones del usuario, cargar datos de un modelo y pasarlos a una vista, para
que así se pueda renderizar una página web. Por ejemplo, en el sito de la editorial, cuando el usuario hace
doble clic en un libro, él o ella esperan ver los detalles completos de ese libro. El Controlador Libro recibe la
solicitud del usuario, carga el modelo de libro con el ID del libro, y lo pasa a la Vista Detalles, lo que hace que

398
una página web muestre el libro. Los Controladores implementan la lógica de entrada y atan los Modelos a la
las Vistas correctas.
1.2. Introducción al marco Spring
El marco Spring es una plataforma Java que proporciona una infraestructura comprensiva que soporta el
desarrollo de aplicaciones Java aplicando el patrón de diseño MVC. Spring gestiona toda la infraestructura por
nosotros para que podamos centrarnos en la lógica de la aplicación.
1.2.1. Inyección de dependencias e inversión de control.
Las aplicaciones Java normalmente consisten de objetos que colaboran entre sí para formar la aplicación
propiamente dicha. Por tanto estos objetos tienen dependencias entre ellos.
AunquelaplataformaJavaproporcionaunaampliavariedaddefuncionesdedesarrollodeaplicaciones,carecedelosme
diosnecesariosparaorganizarlosbloquesbásicosdeconstrucciónenuntodocoherente,dejandoesatareaalosdesarroll
adoresyarquitectos. Es cierto que podemos usar patrones de diseño como Factory, Abstract Factory, Builder,
Decorator y Service Locator para organizar clases y objetos. Sin embargo, estos patrones son simplemente
eso: una forma de formalizar buenas prácticas con un nombre y una descripción.
Elcomponentedeinversión de control (IoC) de Spring proporciona un medio de componer varias clases
dispares para que una aplicación esté lista para trabajar.
LosmarcosIoCtambiénsonconocidoscomomarcosdeInyeccióndeDependencias(DI)porqueinyectanclasesquei
mplementanlasinterfacescorrectasenconstructoresdelaclase.
1.2.2. Módulos.
El marco Spring consiste de características organizadas en 20 módulos. Estos módulos se agrupan dentro de
los componentes Core Container, Data Access/Integration, Web, AOP (Aspect Oriented Programming),
Instrumentation, y Test, tal como se muestra en la siguiente figura:
Figura 2

Core Container.
La capa Core Container consta de los módulos que componen el núcleo, los beans, el contexto y el lenguaje
de expresiones.
Los módulos Core y Beans son la parte fundamental del marco, e incluyen las características de inyección de
código y control de dependencias. El módulo BeanFactory es una implementación sofisticada del patrón
Factory, y se encarga de instanciar todos los beans que gestiona Spring, permitiendo desacoplar la
configuración y especificación de dependencias de nuestra lógica del programa. El módulo Context se
construye sobre la base sólida de Core y Beans, permitiéndole acceder a los objetos del marco de manera
similar a los registros JNDI, añadiendo además soporte para localización (por ejemplo, acceso a los archivos

399
de recursos), propagación de eventos, y carga de recursos. El módulo del lenguaje de expresiones proporciona
un lenguaje potente para consultar y manipular objetos en tiempo de ejecución; es una extensión del lenguaje
de expresiones unificado EL de la especificación JSP 2.1. Soporta la asignación y recuperación de valores de
propiedades, invocación de métodos, acceso al contexto de arrays, colecciones, etc.
Data Access/Integration.
La capa Data Access/Integration consiste de los módulos JDBC, ORM, OXM, JMS y Transaction. El
módulo JDBC proporciona una capa de abstracción para el acceso a bases de datos. El módulo ORM
proporciona una capa para mapear APIs objetos-relacionales populares como JPA, JDO, Hibernate e iBatis.
El módulo OXM proporciona una abstracción que soporta el mapeado objeto-XML implementando JAXB,
Castor, XMLBeans, JiBX y XStream. El módulo Java Messaging Service (JMS) contiene características para
producir y consumir mensajes. El módulo Transaction soporta transacciones declarativas y programáticas
para gestionar clases que implementan interfaces especiales y para nuestros POJOs (plain old Java objects).
Web.
La capa Web consta de los módulos Web, Web-Servlet, Web-Struts y Web-Portlet. El módulo Web de Spring
proporciona características de integración básicas orientadas a web, como carga de varios ficheros e
inicialización de contenedores de control de dependencias usando servlets y contextos. El módulo Web-
Servlet contiene la implementación MVC de Spring para aplicaciones web. El módulo Web-Struct contiene el
soporte para clases que integran una web Struct clásica con una aplicación Spring. El módulo Web-Portlet
proporciona la implementación MVC que usa un entorno portlet.
AOP e Instrumentation.
El módulo AOP de Spring proporciona una implementación que permite definir interceptores de métodos y
puntos de corte para desacoplar código.
El módulo Instrumentation proporciona clases para instrumentación y carga de clases.
Test.
El módulo Test soporta la depuración de componentes Spring con pruebas de unidad.
1.3. El despachador de servlets (DispatcherServlet).
El marco MVC web de Spring es como muchos otros marcos MVC, dirigido por solicitudes, y diseñado en
torno a un servlet central. Este servlet despacha las solicitudes hacia controladores, y ofrece otras
funcionalidades que facilitan el desarrollo de aplicaciones web. El despachador de servlets de Spring, sin
embargo, haces más que esto. Está totalmente integrado con el contenedor de control de dependencias y
permite usar otras muchas más funcionalidades de Spring.
El flujo de procesamiento de una solicitud del DispatcherServlet de Spring se ilustra en el siguiente diagrama:

400
Figura 3

Como se infiere del esquema, el DispatcherServlet usa un patron de diseño Front Controller. Actualmente, el
DispatcherServlet es un servlet (hereda de la clase base HttpServlet), y como tal está declarado en el archivo
web.xml de la aplicación web. Es necesario mapear las solicitudes que debe gestionar el DispatcherServlet
usando un mapeado de URL en el mismo fichero web.xml. El siguiente ejemplo muestra cómo se declara y
mapea un DispatcherServlet:
<web-app>
<servlet>
<servlet-name>example</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>example</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
……………………………
</web-app>
En este ejemplo, todas las solicitudes que finalicen con .htm serán gestionadas por el DispatcherServlet. Éste
es siempre el primer paso para configurar Spring Web MVC. A continuación necesitaremos configurar varios
beans usando por el marco Spring Web MVC (o por el DispatcherServlet mismo).
En el marco Web MVC cada DispatcherServlet tiene su propio fichero de configuración de contexto, el
WebApplicationContext. En este fichero se declaran los beans que serán gestionados por Spring. Se declara el
WebApplicationContext en el fichero descriptor web.xml asociado a un parámetro de contexto, tal como se
muestra a continuación:
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
……………………………
</web-app>

401
Una vez inicializado el DispatcherServlet, el marco mira por un fichero de configuración dipatcher-
servlet.xml ubicado en el directorio WEB-INF de la aplicación web y crea los beans definidos en este
fichero, reescribiendo las definiciones de cualquier bean definido con el mismo nombre en el contexto global.
Consideremos la siguiente configuración del fichero web.xml:
<web-app>
<servlet>
<servlet-name>golfing</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>golfing</servlet-name>
<url-pattern>/golfing/*</url-pattern>
</servlet-mapping>
</web-app>
Con esta configuración, será necesario crear un fichero llamado /WEB-INF/golfing-servlet.xml en nuestra
aplicación; este fichero contendrá todos nuestros componentes específicos de Spring Web MVC. Podemos
cambiar la localización exacta de este fichero de configuración mediante un parámetro de inicialización del
servlet (tal como se muestra más adelante).
El DispatcherServlet usa beans especiales para procesar las solicitudes y renderizar las vistas apropiadas.
Estos beans son parte del marco Spring. Podemos configurarlos en el fichero del contexto web, tal como se
configuran otros beans. Sin embargo, hay otros beans que no necesitamos configurar aquí. Estos beans se
describen en la siguiente tabla:
Tipodebean Utilidad
Controladores Son los encargados de la parte de control del MVC.
Gestoresdemapeados Gestionan la ejecución de una lista de pre-procesadores y post-
procesadores y controladores que serán ejecutados si se cumplen
determinados criterios (por ejemplo, si casa un URL específica con el
controlador).
Solucionadoresdevistas Determinan la asociación de un nombre con una vista.
Solucionadoresdelocalidad Son componentes capaces de resolver la localidad que usa un cliente,
para ofrecer así vistas internacionalizadas.
Solucionadoresdetemas Son capaces de asociar los temas que usará la aplicación web.
Solucionadoresdeficheros Contienen funcionalidades para procesar la carga de ficheros desde los
formularios HTML.
Solucionadoresdeexcepciones Contienen funcionalidades para mapear excepciones con vistas.
Una vez que el DispatcherServlet recibe una solicitud específica, empieza el proceso de la solicitud como
sigue:
1. Se busca en el WebApplicationContext por la solicitud y se enlaza con un atributo que el controlador y
otros elementos del proceso pueden usar. Se enlaza por defecto con un atributo de clave
DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE.
2. Un módulo solucionador de localización enlaza la solicitud para determinar el renderizado de las vistas.
3. El solucionador de temas enlaza la solicitud para determinar qué tema usarán las vistas.
4. Si especificamos un solucionador de ficheros, inspecciona la solicitud por las partes del fichero.
5. Se busca el gestionador específico de la solicitud. Si se encuentra, la ejecución encadena todos los
procesos asociados (preprocesadores, postprocesadores y controladores), para preparar un objeto del
modelo o un renderizado.
6. Si se crea un objeto del modelo, se renderiza una vista. Si no hay objeto del modelo no se renderiza una
vista, puesto que la solicitud puede estar ya completada.
Los solucionadores de excepciones declarados en el WebApplicationContext capturan las excepciones
lanzadas durante el procesado de la solicitud. Se pueden definir funcionalidades personalizadas para
direccionar las excepciones.
El DispatcherServlet también soporta el retorno de la fecha de última modificación, tal como se especifica en
el API Servlet.

402
Podemos personalizar el DispatcherServlet añadiendo parámetros de inicialización de servlet en el fichero
web.xml. La siguiente tabla resume los parámetros soportados:
Parámetro Significado
contextClass Clase que implementa el WebApplicationContext, la cual instancia el contexto
usado por el servlet. Por defecto se usa el fichero XML de contexto de la
aplicación Spring.
contextConfigLocaltion String que contiene la ubicación del fichero de contexto. Este string puede
consistir de varios trozos separados por coma para soportar varios contextos.
namespace Espacio de nombres del WebApplicationContext. Por defecto es disptcher-
servlet.
1.4. Proyecto de Spring Web MVC con NetBeans
En esta apartado desarrollaremos un proyecto Spring Web MVC usando el entorno de desarrollo NetBeans.
Utilizaremos este ejemplo para ver cómo aplicar los diversos conceptos del modelo MVC.
1.4.1. Creación del esqueleto de un proyecto SpringWebMVC.
Empezaremos creando un nuevo proyecto de aplicación Web en NetBeans. Para ello se siguen los siguientes
pasos:
1) Seleccionar «NewProject»enelmenú «File».Seleccionarlacategoría «JavaWeb»yeltipodeproyecto
«WebApplication».Acontinuaciónpulsarelbotón «Next».
Figura 4

2) En el nombre de proyecto escribir PruebaSpring, seleccionar la carpeta donde se guardará el proyecto y


pulsar el botón Next.

403
Figura 5

3) En el siguiente paso seleccionar el servidor GlassFish (o Tomcat) y pulsar el botón «Next».


Figura 6

4) En el siguiente pasomarcarelmarco
«SpringWebMVC»yseleccionarlaúltimaversióndeSpring.Sepuedemarcarlaopción
«IncludeJSTL»paraqueseincluyanlaslibreríasJSTLenelproyecto.Porúltimopulsarelbotón «Finish».

404
Figura 7

Se puede observar, al pulsar lapestaña


«Configuration»,quesedefineundespachadordeservletspordefectodenominado
«dispatcher»quegestionarátodaslassolicitudes que finalicen con la extensión htm.
Al finalizar, se crea un proyecto web con la siguiente estructura:
Figura 8

1.4.2. Configuración del fichero descriptor.


El proyecto Spring crea el siguiente fichero web.xml:
FICHERO«web.xml»
<?xmlversion="1.0"encoding="UTF-8"?>
<web-app ……… >
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

405
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>redirect.jsp</welcome-file>
</welcome-file-list>
</web-app>
Lo primero que destacaremos es la declaración de redirect.jsp como la página inicial del sitio web:
<welcome-file-list>
<welcome-file>redirect.jsp</welcome-file>
</welcome-file-list>
La página redirect.jsp contiene simplemente una redirección a index.htm, la cual será capturada por el
despachador de servlets (puesto que captura todas las solicitudes a páginas *.htm):
<!-- Fichero redirect.jsp -->
<%@pagecontentType="text/html"pageEncoding="UTF-8"%>
<%response.sendRedirect("index.htm");%>
Podemos modificar esta configuración (tanto en web.xml, como en redirect.jsp) para establecer otra página
inicial.
El despachador de servlets se define con la siguiente configuración:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
La clase org.springframework.web.servlet.DispatcherServlet se define en las librerías de Spring y es
instanciada automáticamente para definir el despachador de servlets. Su configuración de mapeado queda
establecida en el fichero dispatcher-servlet.xml.
1.4.3. Configuración del fichero de contexto de aplicación Spring.
En el fichero descriptor web.xml se declara un parámetro de aplicación que determina la ubicación del
fichero WebApplicationContext:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
Inicialmente este fichero no contiene ninguna configuración:
FICHERO«applicationContext.xml»
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"…………>

</beans>
Pero aquí podremos configurar los servicios que usaran nuestros modelos, y soporte para tecnologías como
DataSources, Hibernate, JPA, etc. De momento incluiremos configuraciones para poder utilizar anotaciones:
FICHERO«applicationContext.xml»
<?xmlversion='1.0'encoding='UTF-8'?>

406
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
</beans>
En el elemento <beans> se declaran varios espacios de nombres utilizados por Spring. Cada espacio de
nombres se mapea contra el definido en las librerías de Spring. Al realizar este ejemplo se ha utilizado el
mapeado con la versión 3.0.
El elemento <context:annotation-config/> habilita a Spring para el reconocimiento de anotaciones sobre
controladores y recursos. Spring mirará por beans anotados en el mismo contexto de aplicación.
1.4.4. El fichero de mapeado del DispatcherServlet.
El despachador de servlets captura las solicitudes según la configuración encontrada en el fichero
dispatcher-servlet.xml. Inicialmente este fichero contiene las siguientes configuraciones.
FICHERO«dispatcher-servlet.xml»
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"…………………>
<beanclass="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<propertyname="mappings">
<props>
<propkey="index.htm">indexController</prop>
</props>
</property>
</bean>
<beanid="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp"/>
<beanname="indexController"
class="org.springframework.web.servlet.mvc.ParameterizableViewController"
p:viewName="index"/>
</beans>
Aquí se establece el tipo de solucionador de vistas como un InternalResourceViewResolver. Este tipo de
solucionador gestionara el mapeado de nombres lógicos de vistas a páginas JSP ubicada en el directorio
/WEB-INF/jsp.
Se define un controlador IndexController del tipo ParameterizableViewController:
<beanname="indexController"
class="org.springframework.web.servlet.mvc.ParameterizableViewController"
p:viewName="index"/>
El atributo p:viewName determina que la vista que renderizará tiene como nombre lógico index. El
solucionador de vistas determinará que este nombre se corresponde con la página /WEB-INF/jsp/index.jsp.
Si eliminamos el atributo p:viewName, un controlador de tipo ParameterizableViewController
simplemente responderá con una vista que se corresponda con el nombre de URI solicitado, según los
parámetros de resolución de vistas.
Podremos declarar más controladores mediante elementos <bean/>
Este controlador se mapeará con la URI index.htm:
<beanid="urlMapping"class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

407
<propertyname="mappings">
<props>
<propkey="index.htm">indexController</prop>
</props>
</property>
</bean>
De esta forma, cuando el despachador de servlets reciba una solicitud sobre index.htm redirigirá la gestión al
controlador indexController.
Aquí podremos añadir más mapeados de controladores a URIs.
Si ejecutamos el proyecto Web, el navegador invocará la página inicial redirect.jsp, la cual a su vez redirigirá
a la URI index.htm, la cual será gestionada por el despachador de servlets. El DisptcherServlet redirigirá la
gestión al controlador indexController, que en este caso responderá con la vista /WEB-INF/jsp/index.jsp,
y se mostrará su contenido en el navegador.
Antes de continuar configuraremos este fichero para que acepte anotaciones:
FICHERO«dispatcher-servlet.xml»
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/p
http://www.springframework.org/schema/spring-p-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- Busca en el classpath por benas anotados con @Component o sub-anotaciones -->
<context:component-scanbase-package="controladores"/>
<context:component-scanbase-package="servicios"/>
<!-- Configura el modelo de programación para anotaciones @Controller-->
<mvc:annotation-driven/>

<beanid="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<propertyname="mappings">
<props>
<propkey="index.htm">indexController</prop>
</props>
</property>
</bean>
<beanid="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp"/>
<beanname="indexController"
class="org.springframework.web.servlet.mvc.ParameterizableViewController"
p:viewName="index"/>
</beans>
Con los elementos <context:component-scan/> especificamos rutas de paquetes donde Spring debe
buscar clases anotadas como controladores y como componentes de servicio. En general, cualquier clase bean
utilizada en el desarrollo del proyecto que tenga las anotaciones apropiadas podrá ser reconocida si se
especifica su paquete en un elemento <context:component-scan/>.
Para este ejemplo crearemos clases controladoras en el paquete controladores, y clases de servicio auxiliares
en el paquete servicios.

408
El elemento <mvc:annotation-driven/> configura el modelo de programación de controladores creados
mediante anotaciones.
1.4.5. Añadir una nueva página al proyecto con redirección implícita.
Podemos agregar nuevas páginas JSP al proyecto en la carpeta /WEB-INF/jsp. Como esta carpeta es privada
para el sitio web sólo se podrá accederá a las páginas ubicadas aquí mediante una redirección interna.
Podemos utilizar el controlador indexController para ser redirigidos a las nuevas páginas si no requieren
ningún tipo de procesamiento previo.
Primero editaremos el fichero dispatcher-servlet.xml para que el controlador indexController no tenga
una vista predeterminada:
<beanname="indexController"
class="org.springframework.web.servlet.mvc.ParameterizableViewController"/>
Ahora añadiremos una página llamada saludo.jsp al directorio /WEB-INF/jsp. Esta página simplemente
contendrá:
<!--Fichero WEB-INF/jsp/saludo.jsp-->
<%@pagecontentType="text/html"pageEncoding="UTF-8"%>
<!DOCTYPEhtml>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=UTF-8">
<title>Páginadesaludo</title>
</head>
<body>
<h1>¡HolaMundo!</h1>
</body>
</html>
Debemos añadir un nuevo mapeado a la clase de mapeados del fichero dispatcher-servlet.xml:
<beanid="urlMapping"class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<propertyname="mappings">
<props>
<propkey="index.htm">indexController</prop>
<propkey="saludo.htm">indexController</prop>
</props>
</property>
</bean>
Ahora, simplemente cuando el navegador solicite la página saludo.htm será redirigido automáticamente a la
página /WEB-INF/jsp/saludo.jsp.
1.5. Creación de controladores mediante anotaciones.
Ahora crearemos un controlador específico para redireccionar una página personalizada. Esta redirección
explícita usando un controlador permitirá inyectar datos en una vista mediante atributos de contexto. Para el
ejemplo crearemos una página llamada infoUsuario.jsp y un controlador InfoUsuarioController.
Empezaremos creando la clase controladora. Para eso añadiremos una clase de Java en el paquete
controladores:
packagecontroladores;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.springframework.stereotype.Controller;
importorg.springframework.ui.Model;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping({"/infoUsuario.htm"})
publicclassInfoUsuarioController{
@RequestMapping(method={RequestMethod.GET})
publicStringget(HttpServletRequestrequest,HttpServletResponsereponse,Modelmodel){
Stringnombre=request.getRemoteUser();
model.addAttribute("usuario",nombre==null?"anónimo":nombre);
return"infoUsuario";

409
}
}
La anotación @Controller convierte a la clase InfoUsuarioController en un controlador, mientras que la
anotación @RequestMapping asocia este controlador con la invocación del recurso "infoUsuario.htm".
Un controlador puede gestionar solicitudes para cada acción HTTP; en este caso se crea un método de acción
llamado get() anotado con @RequestMapping(method={RequestMethod.GET}), para que sea el
encargado de procesar las solicitudes realizadas por el método HTTP GET. Para este ejemplo el método
get() recibe un objeto de tipo Model como parámetro, donde podemos guardar cualquier objeto que
queramos inyectar en la vista devuelta, en esta caso el nombre del usuario autentificado. El propio método
retorna el nombre de la vista. El proceso resolvedor de vistas utilizará este nombre para redirigir al clienta a la
página infoUsuario.jsp, donde recuperaremos el atributo "usuario":
<!--Fichero WEB-INF/jsp/infoUsuario.jsp-->
<%@pagecontentType="text/html"pageEncoding="UTF-8"%>
<!DOCTYPEhtml>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=UTF-8">
<title>Informacióndelusuarioactual</title>
</head>
<body>
<h1>Usuarioactual:${usuario}</h1>
</body>
</html>
Para probar este código basta con que el navegador solicite la página infoUsuario.htm.
Figura 9

1.5.1. Implementando controladores.


Los controladores proporcionan acceso a las funcionalidades de la aplicación que normalmente definimos a
través de una interfaz de servicio. Los controladores interpretan los datos de usuario y los convierten en
objetos del modelo que son presentados a través de las vistas. Sprint implementa un controlador de una
forma muy abstracta, lo cual permite crear una gran variedad de controladores.
Un controlador es una clase que implementa la interfaz org.springframework.web.servlet.
mvc.Controller. La siguiente clase es un ejemplo de controlador:
packagecontroller;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.springframework.web.servlet.ModelAndView;
importorg.springframework.web.servlet.mvc.Controller;
publicclassTestControllerimplementsController{
@Override
publicModelAndViewhandleRequest(HttpServletRequesthsr,HttpServletResponsehsr1)
throwsException{
returnnewModelAndView("respuesta");
}
}
Un controlador creado de esta manera debe registrarse en el fichero de configuración del despachador.

410
FICHERO«dispatcher-servlet.xml»
<?xmlversion="1.0"encoding="UTF-8"?>
<beans……………>
<beanclass="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
<beanid="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<propertyname="mappings">
<props>
<propkey="index.htm">indexController</prop>
<propkey="test.htm">test</prop>
</props>
</property>
</bean>
<beanid="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp"/>
<beanname="indexController"
class="org.springframework.web.servlet.mvc.ParameterizableViewController"
p:viewName="index"/>
<beanname="test"class="controller.TestController"/>
</beans>
En este ejemplo la clase controladora TestController gestionará las solicitudes sobre test.htm.
Pero en Spring 2.5 se introdujo un modelo de programación basado en anotaciones para los controladores,
usando anotaciones como @RequestMapping, @RequestParam, @ModelAttribute y otras. Los
controladores implementados con anotaciones no heredan de una clase base o implementan una interfaz
determinada. La claseInfoUsuarioControlleresunejemplodeestemodelodeprogramación.
1.5.1. Definiendo un controlador con @Controller.
La anotación @Controller indica que una clase cumple con el rol de un controlador, de forma que no será
necesario configurar el controlador en el fichero de configuración. El despachador inspeccionará la clase
anotada con @Controller para detectar métodos anotados con @RequestMapping.
1.5.2. Mapeado de solicitudes con @RequestMapping.
Se usan anotaciones @RequestMapping para asociar URIs a una clase o a un método concreto.
Normalmente la anotación a nivel de clase especifica la ruta o patrón de la solicitud que gestionará el
controlador, mientras que las anotaciones a nivel de método especificarán el método HTTP de la solicitud
(GET o POST), o especificarán un parámetro de solicitud HTTP.
El siguiente ejemplo muestra un controlador que usa esta anotación:
@Controller
@RequestMapping("/citas.htm")
publicclassCitasController{
privatefinalAgendaCitasagendaCitas;
publicCitasController(AgendaCitasagendaCitas){
this.agendaCitas= new AgendaCitas();
}
@RequestMapping(method=RequestMethod.GET)
publicMap<String,Cita>get(){
returnagendaCitas.getCitasDeHoy();
}
@RequestMapping(value="/{dia}.htm",method=RequestMethod.GET)
publicMap<String,Cita>getParaHoy(@PathVariable@DateTimeFormat(iso=ISO.DATE)Datedia,
Modelmodelo){
returnagendaCitas.getCitasDeHoy(dia);
}
@RequestMapping(value="/nueva.htm",method=RequestMethod.GET)
publicCitaFormgetNuevoForm(){
returnnewCitaForm();
}
@RequestMapping(method=RequestMethod.POST)

411
publicStringadd(CitaFormcita,BindingResultresult){
if(result.hasErrors()){
return"citas/nueva";
}
agendaCitas.addCita(cita);
return"redirect:/citas";
}
}
En ese ejemplo, la anotación @RequestMapping se usa en varios sitios. Se puede comparar el uso de la
anotación @RequestMapping en controladores con el uso de la anotación @Path en servicios web
RESTful, puesto que su uso es similar.
El primer uso es a nivel de la clase, e indica que este controlador gestionará las solicitudes sobre la ruta
/citas.htm. El método get() está anotado para indicar que aceptará las solicitudes de tipo GET HTTP. El
método add() sólo aceptará las solicitudes POST HTTP. El método getNuevoForm() combina solicitudes
GET con una subruta, de forma que este método aceptará las solicitudes para /citas.htm/nueva.htm. El
método getParaHoy() muestra otro uso de esta anotación con patrones URI que incluyen variables de ruta.
No es necesaria una anotación @RequestMapping a nivel de clase. Sin ella, todas las rutas son absolutas, y
no relativas. El siguiente ejemplo muestra controladores multi-acción usando @RequestMapping:
@Controller
publicclassClinicaController{
@RequestMapping("/clinica.htm")
publicvoid getHandler(){
}
@RequestMapping("/clinica/vets.htm")
publicModelMap getVetsHandler(){
returnnewModelMap(this.clinica.getVets());
}
}
En este ejemplo, las anotaciones sobre los métodos establecen las rutas absolutas que gestionarán cada
método.
Nota. Si queremos crear rutas más compresibles sin tener que finalizar la URI con htm, basta con
cambiar el patrón de ruta asociado con el servlet despachador en el fichero descriptor web.xml. Por
ejemplo, si asignamos el patrón /mvc/* al servlet despachador, todas las solicitudes que comiencen con
mvc serán procesadas por el despachador, y las demás solicitudes serán atendidas por el contenedor web.
1.5.3. Patrones URI para mapeado de rutas.
Para acceder a partes de una URI solicitada en los métodos de acción se usan patrones URI en el parámetro
value de la anotación @RequestMapping.
Un patrón URI es un string que contiene uno o más nombres de variables de ruta. Su propósito es definir una
URI parametrizada. Por ejemplo, si el despachador captura el patrón /mvc/*, la plantilla URI
http://www.ejemplo.com/mvc/usuarios/{idsuario}
contienen la variable idusuario. Si queremos asignar a esta variable el valor pedro, la solicitud será con la
siguiente URI:
http://www.ejemplo.com/mvc/usuarios/pedro.htm
Durante el proceso de la solicitud, la URI se comparará con el patrón URI esperado para extraer su colección
de variables de ruta.
Se usa la anotación @PathVariable sobre un parámetro del método de acción para indicar que el parámetro
se debe enlazar con el valor de la variable del patrón. El siguiente código muestra cómo usar esta anotación:
@RequestMapping(value="/usuarios/{idusuario}.htm",method=RequestMethod.GET)
publicStringfindUsuario(@PathVariable("idusuario")String idsuario,Modelmodel){
model.addAttribute("usuario", usuario);
return "muestraUsuario";
}

412
El patrón URI "/usuarios/{idusuario}" especifica la variable de ruta llamada idusuario. Cuando el
controlador asocia esta solicitud, el valor de idusuario es asignado al parámetro anotado con el mismo
nombre.
Se pueden usar varias anotaciones @PathVariable para enlazar varias variables de la plantilla URI. Los
parámetros de métodos decorados con la anotación @PathVariable pueden ser de cualquier tipo simple
como int, long, Date, etc. Spring automáticamente los convertirá al tipo apropiado, y lanzará una
TypeMismatchException si el tipo no es correcto.
1.5.4. Filtrado de rutas con @RequestMapping.
Además de los patrones URI, la anotación @RequestMapping también soporta patrones al estilo Ant (por
ejemplo, /miRuta/*.do). Así, podemos combinar ambos tipos de patrones (por ejemplo,
/usuarios/*/petcicion/{idPeticion}).
Podemos asociar URIs que posean un determinado parámetro de solicitud. Por ejemplo, podemos forzar que
sólo se gestione una URI que contenga el parámetro de solicitud "tipo=A":
@Controller
@RequestMapping("/usuarios/{idUsuario}")
publicclassRutasRelativasController{
@RequestMapping(value = "/peticion/{idPeticion}", params="tipo=A")
publicvoidfindPeticion(@PathVariableString idUsuario,@PathVariableString idPeticion,Modelmodel){
..............................
}
}
Por ejemplo, el método de acción findPeticion() podría ser invocado por la URI:
http://localhost:8088/usuarios/4/peticion/5?tipo=A
Peronopor la URI:
http://localhost:8088/usaurios/4/peticion/5
Podemos simplemente evaluar que exista el parámetro de solicitud "tipo" independientemente de su valor. O
que no exista dicho parámetro de solicitud con "!tipo". De forma similar podemos mapear la existencia de
cabeceras con la solicitud:
@Controller
@RequestMapping("/usuarios/{idUsuario}")
publicclassRutasRelativasController{
@RequestMapping(value = "/peticion",method=RequestMethod.POST,headers="content-type=text/*")
publicvoidaddPeticion(Peticion peticion,@PathVariableString idUsuario){
..............................
}
}
En este ejemplo, el método addPeticion() solo será invocado si el tipo de contenido MIME casa con el
patrón text/*; por ejemplo, text/xml.
1.5.5. Soporte de varios tipos de argumentos y tipos de retorno.
Los métodos que están decorados con @RequestMapping pueden tener firmas muy flexibles. Se admiten
los siguientes tipos de parámetros en los métodos de acción:
• Objetos request o response (Servlet API). Se puede elegir un tipo específico, como ServletRequest o
HttpServletRequest.
• Objetos de sesión (Servlet API) de tipo HttpSession. Este argumento nunca debe ser nulo.
• Objetos de tipo WebRequest o NativeWebRequest (del paquete
org.springframework.web.context.request). Permiten acceso a parámetros de solicitud así como a
atributos de solicitud o sesión.
• Objetos java.util.Locale, para la localización de la solicitud.
• Objetos java.io.InputStream/java.io.Reader, para poder acceder al contenido de la solicitud.
• Objetos java.io.OutputStream/java.io.Writer, para poder generar el contenido de la respuesta.
• Objetos java.security.Principal, el cual contiene al usuario autentificado.
• Parámetros anotados con @PathVariable, para acceder a las variable de patrón URI.
• Parámetros anotados con @RequestParam, para acceder a un parámetros de solicitud específico.

413
• Parámetros anotados con @RequestHeader, para acceder a una cabecera HTTP específica.
• Parámetros anotados con @RequestBody, para acceder al cuerpo de la solicitud HTTP. Los parámetros
son convertidos al tipo del argumento usando conversores HttpMessageConverters.
• Objetos de tipo HttpEntity<?>, para acceder a cabeceras de la solicitud.
• Objetos java.util.Map/org.springframework.ui.Model/org.springframework.ui.ModelMap, para
inyectar atributos al modelo implícito que se expone en las vistas.
• Comandos u objetos de formulario para enlazar parámetros: se corresponden con el objeto del modelo
que contendrá los datos del formulario procesado por la solicitud.
• Objetos org.springframework.validation.Errors/org.springframework.validation.BindingResult,
para validar los resultados del objeto de formulario. Estos parámetros deben ir a continuación de un
parámetro correspondiente a un objeto de formulario.
• Objetos de estado org.springframework.web.bind.support.SessionStatus, que permiten la limpieza
de atributos de sesión.
Los siguientes tipos de retorno son soportados por los métodos de acción:
• Un objeto ModelAndView, con el modelo implícito enriquecido con el objeto de comando y los
resultados de los métodos accesores del dato referenciados con la anotación @ModelAttribute.
• Un objeto Model, con el nombre de la vista implícito determinado mediante un
RequestToViewNameTranslator y el modelo implícito enriquecido con el objeto de comando y los
resultados de las anotaciones @ModelAttribute.
• Un objeto Map para exponer un modelo, con el nombre implícito de la vista determinado por un
RequestToViewNameTranslator y el modelo implícito enriquecido con el objeto de comando y los
resultados de las anotaciones @ModelAttribute.
• Un objeto View, con el modelo implícito determinado mediante el objeto de comando y las anotaciones
@ModelAttribute. El método de acción puede también enriquecer programáticamente el modelo
declarando un argumento Model.
• Un valor string que es interpretado como el nombre lógico de la vista, con el modelo implícito
determinado a través de objetos de comando y anotaciones @ModelAttribute. El método de acción puede
también enriquecer programáticamente el modelo declarando un argumento Model.
• Si se retorna void el método gestiona la respuesta por sí mismo (escribiendo el contenido de respuesta
directamente, mediante un objeto ServletResponse/HttpServletResponse declarado como parámetro),
o si el nombre de la vista está implícitamente determinado por un RequestToViewNameTranslator (sin
declarar un argumento response en la firma del método).
• Si el método es anotado con @ResponseBody, el tipo retornado es escrito al cuerpo de la respuesta
HTTP. El valor retornado será convertido al tipo de argumento declarado usando
HttpMessageConverters.
• Un objeto HttpEntity<?> o ResponseEntity<?>, para proporcionar acceso a las cabeceras de
respuesta del servlet y a su contenido. El cuerpo será convertido para la respuesta usando
HttpMessageConverters.
• Cualquier otro tipo es considerado como un atributo del modelo simple que será expuesto en la vista,
usando el atributo especificado mediante @ModelAttribute al nivel del método (o el nombre del atributo
por defecto basado en el nombre de la clase del tipo retornado). El modelo es implícitamente enriquecido
con objetos de comando y el resultado de anotaciones @ModelAttribute.
1.5.6. Enlace de parámetros en los métodos de acción.
Se usa la anotación @RequestParam para enlazar parámetros de la solicitud (parámetros de la cadena de
consulta o parámetros de formulario) a parámetros de métodos del controlador. El siguiente código muestra
cómo usar esta anotación:
@Controller
@RequestMapping("/usuarios")
@SessionAttributes("pet")
publicclass UsuariosController {
@RequestMapping(method=RequestMethod.GET)

414
publicString getParam(@RequestParam("idUsuario") int idUsuario, Writer writer)
throwsIOException{
writer.write("ID = " + idUaurio);
}
}
Los parámetros que usan esta anotación son requeridos por defecto, pero podemos especificar que es
opcional asignando el atributo required a false. Por ejemplo,
@RequestParam(value="idUsuario",required=false).Sielparámetronoesespecificadoseasignaráelvalornu
lo al parámetro, por tanto si el parámetro está declarado con un tipo primitivo se producirá un error.
La anotación @RequestHeader indica que un parámetro del método de acción debería ser enlazado al valor
de una cabecera de la solicitud HTTP. Por ejemplo:
@RequestMapping(value="/cabecera",method=RequestMethod.GET)
publicvoidgetHeader(@RequestHeader("User-Agent") String header, Writer writer)
throwsIOException{
writer.write(header);
}
La anotación @CookieValue permite asociar un parámetro del método de acción con el valor de una cookie
HTTP. Si consideramos la siguiente cookie recibida con la solicitud:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
El siguiente código muestra cómo obtener este valor:
@RequestMapping("/cookie")
publicvoid getCookie(@CookieValue("JSESSIONID")Stringcookie, Writer writer)
throwsIOException{
writer.write(cookie);
}
La anotación @RequestBody indica que un parámetro del método de acción debería ser enlazado al valor
del cuerpo de la solicitud HTTP. Por ejemplo:
@RequestMapping(value="/cuerpo",method=RequestMethod.PUT)
publicvoid getBody(@RequestBodyStringbody,Writerwriter)throwsIOException{
writer.write(body);
}
La anotación @ResponseBody es similar a @RequestBody, pero se utiliza sobre el método de acción para
indicar que el tipo retornado debe ser escrito al cuerpo de la respuesta HTTP (y no puesto en el modelo, o
interpretado como el nombre de la vista). Por ejemplo:
@RequestMapping(value="/saludo",method=RequestMethod.PUT)
@ResponseBody
public String hola(){
return "Hola Mundo";
}
Este ejemplo enviará el texto "Hola Mundo" al flujo de respuesta HTTP. Con esta anotación no es necesario
inyectar un parámetro de tipo Writer.
El tipo HttpEntity<?> es similar a @RequestBody y @ResponseBody. Además dar acceso al cuerpo de
la solicitud y respuesta, HttpEntity (y su subclase ResponseEntity) también permiten acceder a las cabeceras
de respuesta y solicitud. Por ejemplo:
@RequestMapping("/handle")
publicResponseEntity<String>handle(HttpEntity<byte[]>requestEntity)
throwsUnsupportedEncodingException{
StringrequestHeader=requestEntity.getHeaders().getFirst("User-Agent");
byte[]requestBody=requestEntity.getBody();
//hacemosalgoconlascabeceraycuerpodesolicitud
MultiValueMap<String,String>responseHeaders=newLinkedMultiValueMap<String,String>();
responseHeaders.add("Content-Type","text/plain");
returnnewResponseEntity<>("Holamundo",responseHeaders,HttpStatus.CREATED);
}

415
2. Técnicas avanzadas con Spring
Crear controladores que simplemente devuelvan vistas en forma de páginas JSP o HTML no ofrecería ningún
beneficio respecto a otras tecnologías. La gran potencia de los marcos de trabajo basados en el patrón MVC
está en la gestión de datos y clases de servicio que manipulan estos datos. En este capítulo veremos cómo se
trabaja con la parte de Modelo y Vistas de Spring.
2.1. Añadir objetos de comando y de formulario al proyecto.
Los objetos de comando se corresponden con clases que almacenan los datos de la lógica de la aplicación
web. En aplicaciones de Java Web se corresponden con beans. Siguiendo con el ejemplo de la aplicación
PruebaSpring crearemos una clase que almacene los datos de un usuario:
package modelo;
publicclassUsuario{
privateStringnombre;
privateStringclave;
publicUsuario(){
}
publicUsuario(Stringnombre,Stringclave){
this.nombre=nombre;
this.clave=clave;
}
// …… getters y setters ……
}
Las propiedades de las instancias de las clases de comando se poblarán normalmente desde un formulario
web. Sin embargo, si aplicamos estrictamente los patrones de diseño de Spring MVC se recomienda crear una
clase de formulario que recoja los datos de formulario y que permita convertirse fácilmente al objeto de
comando correspondiente. En diseños sencillos la clase de comando y de formulario pueden coincidir.
Para simplificar nuestro ejemplo, crearemos una clase de formulario UsuarioForm que será una subclase de
Usuario.
packagemodelo.form;
importmodelo.Usuario;
publicclassUsuarioFormextendsUsuario{
publicUsuariogetUsuario(){
returnthis;
}
}
Se le ha añadido un método de conversión al tipo Usuario. Esto puede resultar muy adecuado sobre todo en
los casos donde no exista una relación de herencia.
2.1.1. Implementación de un controlador que gestione un formulario.
Veremos ahora cómo crear un controlador que gestionará el proceso de capturar datos de usuario desde un
formulario, procesar dichos datos y redirigir a una página de respuesta.
Crearemos una página formUsuario.jsp que solicitará el nombre y clave de un usuario, estos datos serán
encapsulados en un objeto UsuarioForm y recibidos por el controlador. El proceso de gestión del
controlador se ilustra en la siguiente figura:

416
Figura 10

Crearemos un controlador llamado LoginController para gestionar la recepción de datos del formulario y la
redirección a una vista de respuesta.
packagecontroladores;
importmodelo.form.UsuarioForm;
importorg.springframework.stereotype.Controller;
importorg.springframework.ui.Model;
importorg.springframework.validation.Errors;
importorg.springframework.web.bind.annotation.ModelAttribute;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestMethod;
importorg.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/login.htm")
publicclassLoginController{
//ElformularioseráinvocadomedianteGETyportantoseprocesaráestemétodo
@RequestMapping(method=RequestMethod.GET)
publicStringinicializarForm(Modelmodel){
//inicializamos el objeto de comando queusaráelformulario
model.addAttribute("usuarioForm",newUsuarioForm());
// retornamos la vista
return"formUsuario";
}
//DesdeelformularioseharáunainvocaciónPOSTyportantoseprocesaráestemétodo
@RequestMapping(method={RequestMethod.POST})
protectedModelAndViewonSubmit(
@ModelAttribute("usuarioForm")UsuarioFormusuarioForm,
Errorserrors
)throwsException{
// devolvemos la vista "inofUsuario" a la cual hay que inyectar el nombre de usuario.
ModelAndViewmv=newModelAndView("infoUsuario");
mv.addObject("usuario",usuarioForm.getUsuario().getNombre);
returnmv;
}
}
Ahora este controlador tiene dos métodos de acción. Uno para procesar la solicitud de la vista con el
formulario, el método inicializarFom(), que habitualmente será con HTTP GET. Es importante resaltar que
el método de acción GET no solo devuelve la vista "formUsuario", sino que también debe inyectarle un
objeto de comando UsuarioForm. Este objeto quedará disponible en la página JSP de la vista como un
atributo de ámbito y será utilizado para asociarlo a los controles de edición, por tanto el nombre de atributo

417
de este objeto debe coincidir con el que se le da aquí y el que se le dará en el formulario. Podemos inicializar
el objeto UsuarioForm a los valores deseados para ser mostrados por primera vez en la vista.
Cuando el usuario postee los datos del formulario se realizará una solicitud HTTP POST que será recogida
por el método onSubmit(). Este método recibirá el objeto de comando "usuarioForm" con los datos
posteados y una colección de posibles errores asociados con los datos.
La página formUsuario.jsp contendrá el formulario:
<!--FICHEROWEB-INF/jsp/formUsuario.jsp -->
<%@taglibprefix="form"uri="http://www.springframework.org/tags/form"%>
<%@pagecontentType="text/html"pageEncoding="UTF-8"%>
<!DOCTYPEhtml>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=UTF-8">
<title>Entradadeusuario</title>
</head>
<body>
<form:formcommandName="usuarioForm"method="POST">
Nombre<form:inputpath="nombre"/><br/>
Clave<form:passwordpath="clave"/><br/>
<inputtype="submit"value="enviar"/>
</form:form>
</body>
</html>
A destacar en el marcado del formulario el uso de las etiquetas <form:/>. Estas etiquetas están definidas en
la librería "http://www.springframework.org/tags/form". La etiqueta <form:form> define un
formulario asociado a un objeto de comando (atributo commandName) cuyo nombre debe coincidir con el
que se inyecta desde el controlador. También se proporcionan etiquetas para renderizar los controles
habituales de un formulario; estas etiquetas proporcionan el atributo path para asociar el control de
formulario con una propiedad del objeto de comando.
Basta con invocar la página login.htm para ver cómo funciona el controlador LoginController.
Figura 11

2.1.2. Proporcionar un enlace a datos desde el modelo con @ModelAttribute.


La anotación @ModelAttribute tiene dos usos en los controladores. Cuando se usa para decorar un
parámetro de método, mapea un atributo del modelo al parámetro especificado. Esto funciona como si el
controlador obtuviese una referencia al objeto de datos del formulario.
En el ejemplo previo se utiliza @ModelAtribute para inyectar el objeto de comando "usuarioForm" en el
método de acción onSubmit():

418
@RequestMapping(method={RequestMethod.POST})
protectedModelAndViewonSubmit(
@ModelAttribute("usuarioForm")UsuarioFormusuarioForm,
Errorserrors
)throwsException{
El enlazador de datos de Spring se encargará inyectar el objeto UsuarioForm con los datos recogidos desde
la vista del formulario.
Pero también podemos combinar @ModelAttribute con la anotación @SessionAttributes para
proporcionar una referencia a un objeto del ámbito de sesión inicializado antes del mapeado. Veremos este
segundo uso con un ejemplo trivial pero ilustrativo:
@Controller
@RequestMapping("/login.htm")
@SessionAttributes("bufer")
public class LoginController {
@ModelAttribute
private StringBuffer creaBufer() {
return new StringBuffer();
}
@RequestMapping(method = RequestMethod.GET)
public String inicializarForm(Model model, @ModelAttributeStringBufferbufer) {
model.addAttribute("usuarioForm", new UsuarioForm());
bufer.append("formUsuario");
return bufer.toString();
}
…………………..
}
Primero se define a nivel de clase un atributo de sesión con @SessionAttributes("bufer"). A continuación
se define un método anotado con @ModelAttribute que inicializará un objeto de tipo StringBuffer. En
principio no existe relación entre este método y el atributo de sesión.
A continuación, en el método de acción inicializarForm() se declara un parámetro anotado con
@ModelAttribute. Al no especificar un valor intentará buscar un atributo de sesión definido con
@SessionAttributes que tenga el mismo nombre que el parámetro, e intentará inicializarlo con algún
método anotado con @ModelAttribute que retorne un StringBuffer.
2.1.3. Personalizarlainicializacióndel enlace de datos.
Seutilizalaanotación@InitBinderparadecorarunmétodoqueseráinvocadoaefectodeinicializacióndelenlacededat
os.Seusaráestemétodoparapoblarobjetosdecomando.Elmétododeinicializacióndeberetornarvoid.
Elsiguienteejemplomuestraelusodeestaanotaciónparaconfiguraruneditordedatospersonalizadosparatodoslosja
va.util.Date.
@Controller
publicclassMiController{
@InitBinder
publicvoidinitBinder(WebDataBinderbinder){
SimpleDateFormatdateFormat=newSimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class,newCustomDateEditor(dateFormat,false));
}
…………………..
}
2.2. Añadiendo e inyectando servicios al proyecto.
Los servicios se corresponden con clases que normalmente aplican la lógica de nuestra aplicación sobre
objetos de comando. Estas clases de servicios son invocadas desde los controladores para aplicar alguna
funcionalidad sobre los datos de la aplicación.
2.2.1. Uso de un servicio en un controlador.
Spring permite inyectar las instancias de servicios en los controladores. Por ello es recomendable que los
servicios implementen una interfaz con las funcionalidades básicas del servicio. De esta manera la clase

419
controladora definirá una propiedad del tipo de servicio y Spring se encargará de poblar dicha propiedad con
la clase apropiada.
Para nuestro ejemplo crearemos una interfaz de servicio llamada IUsuarioService que proporcione un
método para obtener el nombre completo de un usuario.
packageservice;
importmodelo.Usuario;
publicinterfaceIUsuarioService{
StringgetNombreCompleto(Usuariousuario);
}
Una clase que implemente esta interfaz será UsuarioService:
packageservicios;
importmodelo.Usuario;
importorg.springframework.stereotype.Service;
@Service
publicclassUsuarioServiceimplementsIUsuarioService{
@Override
publicStringgetNombreCompleto(Usuariousuario){
//porsimplicidadretornamoselpropionombre
returnusuario.getNombre();
}
}
Para la posterior inyección de código, las clases de servicio deben estar anotadas con @Service. La inyección
de esta clase en el controlador LoginController se realiza mediante la anotación @Resource.
package controladores;
import javax.annotation.Resource;
import modelo.form.UsuarioForm;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import servicios.IUsuarioService;
import servicios.UsuarioService;
@Controller
@RequestMapping("/login.htm")
public class LoginController {
@Resource(type = UsuarioService.class)
IUsuarioService usuarioService;
…………………….
@RequestMapping(method = {RequestMethod.POST})
protected ModelAndView onSubmit(
@ModelAttribute("usuarioForm") UsuarioForm usuarioForm,
Errors errors
) throws Exception {
ModelAndView mv = new ModelAndView("infoUsuario");
String nombreCompleto = usuarioService.getNombreCompleto(usuarioForm.getUsuario());
mv.addObject("usuario", nombreCompleto);
return mv;
}
}
2.2.2. Anotaciones para inyección de código.
Cualquier clase, cuyas instancias queramos inyectar en tiempo de ejecución, deberá estar decorada con la
anotación @Component, o uno de sus estereotipos @Service y @Repository.
Existen dos anotaciones básicas para inyección de instancias de clases: @Autowired y @Resource.
La anotación @Autowired se puede aplicar sobre una variable de instancia, sobre un método setter o sobre
un constructor, y es capaz de inferir la clase que debe instanciar para una variable a partir de su tipo. Por
ejemplo, se puede instanciar un objeto UsuarioService en la clase LoginController de la siguiente manera:
import org.springframework.beans.factory.annotation.Autowired;

420
@Controller
@RequestMapping("/login.htm")
public class LoginController {
@Autowired
IUsuarioService usuarioService;
…………………….
}
Spring es capaz de inferir que debe instanciar la clase UsuarioService aunque la variable sea del tipo de la
interfaz. Esto es así porque de momento la clase UsuarioService es la única anotada que implementa la
interfaz.
Si creamos más clases que implementen la interfaz, o subclases de éstas, debemos especificar cuál se debe
instanciar usando la anotación @Resource.
import javax.annotation.Resource;
@Controller
@RequestMapping("/login.htm")
public class LoginController {
@Resource(type = UsuarioService.class)
IUsuarioService usuarioService;
…………………….
}
Para facilitar la inyección podemos establecer en el fichero del configuración del contexto la clase bean que
deseamos inyectar. Por ejemplo:
<?xml version='1.0' encoding='UTF-8' ?>
<beans …… >
<bean name="servUsuario" class="servicios.UsuarioService" />
</beans>
Ahora podemos usar el nombre "servUsuario" para identificar el bean que se debe inyectar:
import javax.annotation.Resource;
@Controller
@RequestMapping("/login.htm")
public class LoginController {
@Resource(name = "servUsuario")
IUsuarioService usuarioService;
…………………….
}
2.3. Añadiendo e inyectando validadores.
El marco Spring permite usar validación de datos a dos niveles. Podemos aplicar validación en los
controladores cuando reciben los datos, o se pueden usar anotaciones de validación sobre los atributos de las
clases de comando.
2.3.1. Mecanismo de gestión de errores del marco Spring.
El marco Spring incorpora un mecanismo de gestión de mensajes de error basado en un archivo de recursos
de propiedades y validación de datos durante el mapeado de los parámetros de formulario a los objetos de
comando del negocio. Si el mapeador de datos detecta algún error de conversión asocia un mensaje de error
con la propiedad que ha fallado.
Si queremos crear mensajes de error personalizados para validación, deben escribirse en un archivo de
recursos (un fichero .properties) en la carpeta /WEB-INF/classes. Siguiendo con nuestro ejemplo
crearemos un fichero mensajes.properties con varios mensajes de error:
FICHERO/WEB-INF/classes/mensajes.properties
error-nombrevacio=Debeescribirseunnombre.
error-clavecorta=Laclavedebetenercomomínimo8caracters.
También podemos utilizar este fichero para escribir mensajes localizados que podemos recuperar
directamente con la etiqueta <fmt:message/> de la librería JSTL FMT.
Debemos registrar el fichero de recursos en el fichero dispatcher-servlet.xml de la siguiente manera:
FICHERO dispatcher-servlet.xml
<?xmlversion="1.0"encoding="UTF-8"?>

421
<beans………………>
………………………………
<beanclass="org.springframework.context.support.ResourceBundleMessageSource"
id="messageSource">
<propertyname="basename"value="mensajes"/>
</bean>
</beans>
En las vistas se pueden recuperar los mensajes de error asociados a una propiedad mediante la etiqueta
<form:errorpath=""/>. Por ejemplo, modificaremos la página formUsuario.jsp para incluir posibles
mensajes de error:
<!--FICHEROWEB-INF/jsp/formUsuario.jsp-->
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Entrada de usuario</title>
</head>
<body>
<form:form commandName="usuarioForm">
Nombre <form:input path="nombre"/><form:errorspath="nombre"/><br/>
Clave <form:password path="clave" /><form:errorspath="clave"/><br/>
<input type="submit" value="enviar" />
</form:form>
</body>
</html>
2.3.2. Uso de clases validadoras.
Aunque podemos crear clases de servicio que apliquen validaciones personalizadas, lo más adecuado es crear
una clase validadora que implemente la interfaz org.springframework.validation.Validator. Este interfaz
trabaja usando un objeto de tipo org.springframework.validation.Errors en el que asigna fallos de
validación de datos asociándolos a mensajes personalizados.
Como ejemplo crearemos una clase que valide los datos de un UsuarioForm de forma que no admita un
nombre vacío ni una clave de menos de 8 caracteres:
package servicios;
import modelo.form.UsuarioForm;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
@Component
public class UsuarioValidator implements Validator {
@Override
public boolean supports(Class type) {
return type.equals(UsuarioForm.class);
}
@Override
public void validate(Object o, Errors errors) {
UsuarioForm usuario = (UsuarioForm) o;
// Validamos que el nombre no esté vacío
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "nombre", "error-nombrevacio");
// Validamos la clave comprobando su tamaño
if (usuario.getClave().length() < 8) {
errors.rejectValue("clave", "error-clavecorta");
}
}
}
El método supports() establece qué tipos puede validar este validador. El método validate() recibe como
primer argumento del objeto de comando a validar, y como segundo argumento un objeto Errors donde

422
debe inyectar los posibles errores. La clase auxiliar ValidationUtils proporciona métodos de validación para
errores típicos como datos vacíos.
Al estar anotada con @Componente podemos inyectar esta clase en el controlador para tomar decisiones de
validación:
package controladores;
………………
@Controller
@RequestMapping("/login.htm")
public class LoginController {
……………………….
@Autowired
UsuarioValidator validador;
@RequestMapping(method = {RequestMethod.POST})
protected ModelAndView onSubmit(
@ModelAttribute("usuarioForm") UsuarioForm usuarioForm,
Errors errors
) throws Exception {
validador.validate(usuarioForm, errors);
if (errors.hasErrors()) {
return new ModelAndView("formUsuario", "usuarioForm", usuarioForm);
}
ModelAndView mv = new ModelAndView("infoUsuario");
mv.addObject("usuario", usuarioForm.getUsuario().getNombre());
return mv;
}
}
Primero se utiliza la anotación @Autowired para inyectar una instancia de UsuarioValidador, la cual se
utiliza en el método de acción onSubmit() para validar el objeto de comando. Si existe algún error en el
objeto errors el controlador retorna a la vista "usuarioForm" volviéndole a inyectar el objeto de comando.
Esto permitirá que la página JSP muestre los últimos datos introducidos y que visualice los mensajes de error
asociados a cada error.
2.3.3. Uso de anotaciones de validación.
El marco Spring también permite aplicar validación sobre la propia clase de comando mediante anotaciones
aplicadas sobre los atributos o métodos getter. El marco Spring acepta las anotaciones predefinidas del
paquete javax.validation.constraints. (Véase la unidad 10 (ElAPIJavaPersistence), sección 2.4.1.)
Este paquete ofrece las anotaciones: @Size, @DecimalMax, @DecimalMin, @Digits, @Future, @Max,
@Min, @NotNull, @Null, @Past y @Pattern.
Comoejemplodeuso,vamosaaplicar en la clase Usuario las validaciones sobre el nombre de usuario y la
longitud de la contraseña, para que sean heredadas por la clase UsuarioForm.
package modelo;
import javax.validation.constraints.Size;
import javax.validation.constraints.NotNull;
public class Usuario {
@NotNull @Size(min = 1, message = "Debe introducir un nombre")
private String nombre;
@Size(min=8, message = "El tamaño mínimo es de 8 caracteres")
private String clave;
…………………….
}
Para que estas validaciones se produzcan, en el controlador debemos anotar el parámetro que recibe el objeto
de comando con la anotación @Validated:
@RequestMapping(method = {RequestMethod.POST})
protected ModelAndView onSubmit(
@Validated @ModelAttribute("usuarioForm") UsuarioForm usuarioForm,
Errors errors
) throws Exception {
ModelAndView mv = new ModelAndView("infoUsuario");

423
if (errors.hasErrors()) {
return new ModelAndView("formUsuario", "usuarioForm", usuarioForm);
}
mv.addObject("usuario", usuarioForm.getUsuario().getNombre());
return mv;
}
Nota. Véase la unidad 10 (ElAPIJavaPersistence), sección 2.4.2. para repasar cómo crear anotaciones de
validación personalizadas.
2.4 . Gestionadores de mapeados.
En versiones previas a Spring 2.5 los usuarios debían definir los mapeados de vistas a controladores en el
fichero de contexto web de la aplicación.
Ahora el despachador de servlets permite el mapeado mediante anotaciones. Este mapeador busca
anotaciones @RequestMapping y @Controller. Normalmente no necesitaremos reescribir este mapeado
por defecto, a menos que necesitemos cambiar los valores por defecto de algunas propiedades. Estas
propiedades son:
interceptors Lista de interceptores para usar.
defaultHandler Gestionador por defecto.
order Basado en el valor de la propiedad order (de tipo
org.springframework.core.Ordered), Spring ordena todos los gestionadores del
contexto y aplica al primero que casa con la URL.
alwaysUseFullPath Si es true, Spring usa la ruta completa sin el contexto actual de servlet para encontrar
los gestionadores apropiados. Si es false (por defecto), se usa la ruta actual del servlet.
Por ejemplo, si un servlet es mapeado usando /testing/* y alwaysUseFullPath es
true, se usará /testing/viewPage.htm; si es false, se usará /viewPage.htm.
urlDecode Por defecto es true.
lazyInitHandlers Permite inicializaciones perezosas de gestionadores singleton. El valor por defecto es
false.
El siguiente ejemplo muestra cómo sobreescribir el mapeado por defecto y añadir interceptores:
<beans>
<beanid="handlerMapping"
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<propertyname="interceptors">
<beanclass="example.MyInterceptor"/>
</property>
</bean>
<beans>
2.4.1. Intercepción de solicitudes (la interfaz HandlerInterceptor).
El mecanismo de mapeado de gestionadores de Spring incluye la gestión de interceptores, los cuales son
habituales cuando queremos aplicar funcionalidades específicas para ciertas solicitudes; por ejemplo, para
verificar la identidad del usuario actual.
Los interceptores deben implementar la interfaz org.springframework.web.servlet.HandlerInterceptor.
Esta interfaz define tres métodos: uno es llamado antes de que se ejecute el gestionador, otro es llamado
después de que el gestionador se ejecute, y otro es llamado después de que se complete la solicitud.
El método preHandle() retorna un valor booleano. Debemos usar este método para romper o continuar con
el proceso. Cuando este método retorna true, se encadena la ejecución del gestionador, y cuando retorna
false, el despachador de servlets asume que el interceptor se ha encargado por sí mismo de la solicitud y no
continúa la ejecución de otro interceptor ni del gestionador.
El siguiente ejemplo define un mapeado para todas las solicitudes con el patrón "/*.form" y "/*.view" con
un controlador llamada editAccountFormController. Se añade un interceptor que captura la solicitud y
redirige al usuario a una página específica si la hora está entre las 9 a.m. y 6 p.m.
<!--Elficherodeconfiguración de contexto -->
<beans>
<beanid="handlerMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

424
<propertyname="interceptors">
<list>
<refbean="officeHoursInterceptor"/>
</list>
</property>
<propertyname="mappings">
<value>
/*.form=editAccountFormController
/*.view=editAccountFormController
</value>
</property>
</bean>
<beanid="officeHoursInterceptor"
class="samples.TimeBasedAccessInterceptor">
<propertyname="openingTime"value="9"/>
<propertyname="closingTime"value="18"/>
</bean>
<beans>
Donde la clase del interceptor es la siguiente:
packagesamples;
publicclassTimeBasedAccessInterceptorextendsHandlerInterceptorAdapter{
privateintopeningTime;
privateintclosingTime;
publicvoidsetOpeningTime(intopeningTime){
this.openingTime=openingTime;
}
publicvoidsetClosingTime(intclosingTime){
this.closingTime=closingTime;
}
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)
throwsException{
Calendarcal=Calendar.getInstance();
inthour=cal.get(HOUR_OF_DAY);
if(openingTime<=hour<closingTime){
returntrue;
}else{
response.sendRedirect("http://host.com/outsideOfficeHours.html");
returnfalse;
}
}
}
2.5. Resolución de vistas.
Todos los marcos MVC para aplicaciones web proporcionan un modo de direccionar vistas. Spring
proporciona un solucionador de vistas, el cual permite renderizar modelos en un navegador sin escribir una
tecnología de vistas específica. Spring permite usar páginas JSP, plantillas Velocity y vistas XSLT, por
ejemplo.
Las dos interfaces importante para gestionar vistas son ViewResolver y View. La interfaz ViewResolver
proporciona un mapeado entre nombres de vistas y vistas actuales. La interfaz View direcciona la preparación
de una solicitud y la soluciona sobre una de las tecnologías de vistas.
2.5.1. Resolución de vistas con la interfaz ViewResolver.
Como se ha explicado previamente, todos los métodos de acción de los controladores deben resolver el
nombre lógico de una vista, bien explícitamente (retornando un String, View o ModelAndView) o
implícitamente (basándose en alguna convención). Las vistas en Spring se direccionan por un nombre lógico y
se resuelven mediante un solucionador de vista. Spring incluye un pocos solucionadores de vistas. Esta tabla
describe algunos de ellos:
Solucionadordevistas Descripción
AbstractCachingViewResolver Un solucionador abstracto que almacena en caché las vistas.
Normalmente las vistas necesitan alguna preparación antes de ser usadas;
425
extendiendo esta solucionador proporcionamos opciones de caché.
XmlViewResolver Es una implementación de ViewResolver que acepta un fichero de
configuración escrito en XML. El fichero de configuración por defecto
es /WEB-INF/views.xml.
ResourceBundleViewResolver Implementación de ViewResolver que usa definiciones bean en una
ResourceBundle, especificado por el nombre base. Normalmente se
definen archivos de propiedades, localizados en la ruta de las clases.
UrlBasedViewResolver Es una implementación sencilla que identifica los nombres lógicos de las
vistas con su URL.
InternalResourceViewResolver Una subclase de UrlBasedViewResolver que soporta
InternalResourceView (para servlets y JSPs) y subclases como
JstlView y TilesView. Podemos especificar la clase de la vista para todas
las vistas generadas por este solucionador usando el método
setViewClass(...).
VelocityViewResolver y Subclases de UrlBasedViewResolver que soportan VelocityView (para
FreeMarkerViewResolver
plantillas Velocity) o FreeMarkerView, y subclases personalizadas.
ContentNegotiatingViewResolver Implementación de ViewResolver que asocia una vista según el nombre
de fichero solicitado o la cabecera Accept.
Como un ejemplo, con JSP como tecnología de vistas, podemos usar el UrlBasedViewResolver. Este
solucionador asocia un nombre de vista a una URL y maneja la solicitud mediante un RequestDispatcher
para renderizar la vista.
<beanid="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<propertyname="viewClass"value="org.springframework.web.servlet.view.JstlView"/>
<propertyname="prefix"value="/WEB-INF/jsp/"/>
<propertyname="suffix"value=".jsp"/>
</bean>
Cuando se retorna test como nombre lógico de vista, el solucionador redirige la solicitud mediante un
RequestDispatcher que envía la respuesta a la página /WEB-INF/jsp/test.jsp.
Cuando combinamos diferentes tecnologías de vistas en una aplicación web, podemos usar el
ResourceBundleViewResolver:
<beanid="viewResolver"
class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<propertyname="basename"value="views"/>
<propertyname="defaultParentView"value="parentView"/>
</bean>
El ResourceBundleViewResolver inspecciona los recursos identificados por el nombre base views, y para
cada vista que debe resolver usa el valor de la propiedad [nombredelavista].(class) como la clase de la
vista y el valor de la propiedad [nombredelavista].url como la url de la vista.
2.5.2. Encadenando solucionadores de vistas.
Spring soporta varios solucionadores de vistas. Podemos encadenarlos y por ejemplo, reescribir vistas
específicas en ciertas circunstancias. Se encadenan solucionadores de vistas añadiéndolos al contexto de la
aplicación, y si es necesario, asignando la propiedad order para ordenarlos.
En el siguiente ejemplo, el encadenamiento consta de dos solucionadores, un
InternalResourceViewResolver, el cual siempre será el último de la cadena, y un XmlViewResolver para
especificar vistas de Excel. Las vistas de Excel no son soportadas por el InternalResourceViewResolver.
<beanid="jspViewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<propertyname="viewClass"value="org.springframework.web.servlet.view.JstlView"/>
<propertyname="prefix"value="/WEB-INF/jsp/"/>
<propertyname="suffix"value=".jsp"/>
</bean>
<beanid="excelViewResolver"class="org.springframework.web.servlet.view.XmlViewResolver">
<propertyname="order"value="1"/>

426
<propertyname="location"value="/WEB-INF/views.xml"/>
</bean>
<!--Enviews.xml-->
<beans>
<beanname="report"class="org.springframework.example.ReportExcelView"/>
</beans>
2.5.3. Redireccionando las vistas.
Un controlador normalmente retornar un nombre de vista lógico, el cual debe ser resuelto por un
solucionador de vistas. Para tecnologías como JSP se utiliza internamente un método
RequestDispatcher.forward() o RequestDispatcher.include(). Para otras tecnologías, como Velocity,
XSLT y otras se escribe el contenido directamente al flujo de salida.
• Uso de «RedirectView».
Una forma de forzar un redireccionamiento como el resultado de la respuesta de un controlador es creando
y retornando una instancia de la clase RedirectView. En este caso, el despachador de servlets no usa el
mecanismo normal de resolución. Es esta caso simplemente indica al navegador qué vista debe renderizar,
utilizando el método HttpServletResponse.sendRedirect() para retornar al navegador un redirección
HTTP. Todos los atributos del modelo serán expuestos como parámetros de solicitud HTTP.
Un ejemplo de esta clase es el siguiente. El controlador UsuarioController redirige al navegador a la página
inicial si detecta un error:
@RequestMapping(method = {RequestMethod.POST})
protected ModelAndView onSubmit(
@Validated @ModelAttribute("usuarioForm") UsuarioForm usuarioForm,
Errors errors
) throws Exception {
if (errors.hasErrors()) {
return new ModelAndView(newRedirectView("index.htm"));
}
………………….
}
• El prefijo «redirect:».
Normalmente deberíamos operar a nivel de nombres de vistas que deben ser inyectadas internamente. El
prefijo especial redirect: permite actuar de este modo. Podemos retomar un nombre lógico de vista con
este prefijo, de forma que el solucionador de vistas lo reconocerá y redireccionará a la vista adecuada.
Si retornamos un nombre de vista como "redirect:/sitioweb/informes.html" relativa al contexto actual
de servlets seremos direccionados a un recurso del mismo sitio web, mientras que si retornamos un nombre
como "redirect:http://www.google.es" seremos direccionados a una ruta absoluta.
• El prefijo «forward:».
También podemos usar el prefijo especial forward: con los nombre de vistas. En este caso se utiliza para
realizar redirecciones internas dentro del sitio web.

3. Introducción al marco Structs 1.3.


Struts 1.3 es otro marco de trabajo basado en el patrón de diseño MVC para el desarrollo de aplicaciones
web, el cual hace que la implementación de las mismas sea más sencilla, más rápido, y con menos
complicaciones. Además hace que estas sean más robustas y flexibles. El objetivo de Struts 2 es muy sencillo:
hacer que el desarrollo de aplicaciones web sea simple para los desarrolladores.
3.1. Fundamentos de Structs.
Al igual que el corazón de Spring es un servlet conocido como el DispatcherServlet, el corazón de Struts 1.3
es otro servlet, conocido como el ActionDispatcher. Este es el punto de entrada del framework. A partir de él
se lanza la ejecución de todas las peticiones que involucran al framework.
Las principales responsabilidades del ActionDispatcher son:
• Analiza la URI recibida para saber que ActionMapping usar (objetos que mapean una ruta a una Acción).
• Recoge las entradas de usuario dentro de objetos ActionForm Bean (los equivalentes a objetos del
modelo, de comando o de formulario).
• Ejecuta el Action o Acción que manejará la petición (el equivalente a controladores de Spring).

427
• Lee el ActionForward (respuesta de acción) procedente de la Acción y redirige la respuesta a la página JSP
que renderizará el resultado.
Figura 12

Las clases participantes de Structs 1.3 son:


• ActionForward. Indica una selección de vista asociada a un término.
• ActionForm Bean. Los datos del modelo.
• ActionMapping. Asocian una Acción con una ruta de URI.
• ActionServlet. La parte del controlador que recibe las solicitudes del usuario y las despacha hacia una
Acción.
• Action. La parte del controlador que interactúa con el modelo, ejecuta la lógica del negocio y decide la
vista con la que debe responderse.
El fichero descriptor de Structs «struct-config.xml» contiene las definiciones de ActionForm Beans, Actions,
ActionMappings y ActionForwards.
3.2. Proyecto de Struct con NetBeans.
En esta apartado desarrollaremos un proyecto Structs usando el entorno de desarrollo NetBeans.
1.4.1. Creación del esqueleto de un proyecto Structs.
Empezaremos creando un nuevo proyecto de aplicación Web en NetBeans. Para ello se siguen los siguientes
pasos:
1) Seleccionar «NewProject»enelmenú «File».Seleccionarlacategoría «JavaWeb»yeltipodeproyecto
«WebApplication».Acontinuaciónpulsarelbotón «Next».
Figura 13

428
2) En el nombre de proyecto escribir WebStruct, seleccionar la carpeta donde se guardará el proyecto y
pulsar el botón Next.
Figura 14

3) En el siguiente paso seleccionar el servidor Tomcat (o GlassFish) y pulsar el botón «Next».


Figura 15

4) En el siguiente paso marcarelmarco «Structs 1.3».Sepuedemarcarlaopción «Add Structs


TLDs»paraqueseincluyanlaslibrerías de apoyo de Structs en la creación de páginas
JSP.Porúltimopulsarelbotón «Finish».

429
Figura 16

Se puede observar que se define el servlet de Acciones con el nombre action y el patrón URL *.do.
Al finalizar, se crea un proyecto web con la siguiente estructura:
Figura 17

1.4.2. Configuración del fichero descriptor.


El proyecto Structs crea un fichero web.xml similar al siguiente:
FICHERO«web.xml»

430
<?xml version="1.0" encoding="UTF-8"?>
<web-app …………… >
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
……………………………
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Lo primero que destacaremos es la declaración de index.jsp como la página inicial del sitio web. La página
index.jsp contiene simplemente una redirección a welcome.do, la cual será capturada por el ActionServlet
(puesto que captura todas las solicitudes a páginas *.do):
Podemos modificar esta configuración (tanto en web.xml, como en index.jsp) para establecer otra página
inicial.
El ActionServlet se define asociado a la clase predefinidaorg.apache.struts.action.ActionServlet, y declara
varios parámetros de inicialización. El parámetro config determina la ubicación del fichero descriptor de
Structs, que es por defecto WEB-INF/struts-config.xml.
1.4.3. El fichero de configuración de Structs.
El fichero struct-config.xml centraliza toda la configuración de Structs 1.3. En este fichero se define todos los
elementos de la arquitectura de Structs.
FICHERO«struts-config.xml»
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts-config PUBLIC"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_3.dtd">
<struts-config>
<form-beans>
</form-beans>
<global-exceptions>
</global-exceptions>
<global-forwards>
<forward name="welcome"path="/Welcome.do"/>
</global-forwards>
<action-mappings>
<action path="/welcome" forward="/welcomeStruts.jsp"/>
</action-mappings>
<controller processorClass="org.apache.struts.tiles.TilesRequestProcessor"/>
<message-resources parameter="com/myapp/struts/ApplicationResource"/>
<plug-in className="org.apache.struts.tiles.TilesPlugin" >
<set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml" />
<set-property property="moduleAware" value="true" />
</plug-in>
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-propertyproperty="pathnames"
value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
</plug-in>
</struts-config>
Destacaremos los siguientes elementos:

431
• <form-beans>, aquí se declararán todos los FormAction Beans utilizados por los formularios.
• <global-forwards>, aquí se declararán páginas globales asociadas a un nombre. Estas páginas podrán
ser referenciadas en los mapeados por su nombre.
• <action-mappings>, aquí se definen los mapeados entre URIs virtuales y Acciones. Cada elemento
<action> define una Acción con una ruta virtual asociada en su atributo path, teniendo en cuenta que
Structs le añadirá implícitamente la extensión ".do". En los atributos forward se indica rutas reales de
ficheros respecto a la raíz del sitio web.
• <message-resources>, indica el fichero de recursos de idiomas utilizado por Structs. En el fichero
indicado se podrán añadir mensajes personalizados.
3.3. Creación de una Acción para redireccionar a una vista.
Si ejecutamos la aplicación creada previamente se mostrará la página welcomeStruts.jsp. Esta página es
invocada a través de index.jsp, quien realiza una redirección a Welcome.do:
<jsp:forward page="Welcome.do"/>
Cuando el ServletAction captura las solicitudes no tiene en cuenta mayúsculas y minúsculas. En el fichero
descriptor encuentra el ActionMapping:
<action path="/welcome" forward="/welcomeStruts.jsp"/>
Que hace referencia a un redireccionamiento directo a la página JSP. Por tanto, si queremos realizar
redireccionamientos directos a una página podemos añadir un elemento <action path="" forward="" />.
Por ejemplo, si añadimos la página HTML menu.html y queremos redireccionarla mediante la ruta
menu.do podemos agregar el siguiente ActionMapping:
<action-mappings>
<action path="/menu" forward="/menu.html"/>
</action-mappings>
¿Pero qué pasa si queremos invocar directamente una página en la que tenemos que inyectar algún dato? En
ese caso deberemos crear una Acción que no esté asociada con ningún ActionForm Bean.
Supongamos que añadimos la siguiente página JSP:
<!--FICHERO socios.jsp -->
<%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Listado de socios</title>
</head>
<body>
<ul>
<logic:iterate id="socio" collection="${socios}">
<li>${socio}</li>
</logic:iterate>
</ul>
</body>
</html>
Este fichero recupera de un contexto el atributo socios, que debe ser una colección, y muestra sus elementos
en una lista no ordenada. En este código se muestra el uso de la etiqueta <logic:iterate> que pertenece a
una de las librerías de ayuda de Structs, y que es similar a la etiqueta <c:forEach>.
Para invocar esta página, la aplicación debe inyectarle dicha colección. Para conseguir esto asociaremos esta
página con una Acción llamada SociosAction. Para crear esta acción hay que agregar a la aplicación un
fichero «Structs Action» de la categoría «Struct».

432
Figura 18

En el siguiente paso daremos nombre a la Acción, SociosAction, la ubicaremos en el paquete action y la


asociaremos con la ruta de acción /socios.
Figura 19

En el siguiente paso desmarcaremos todas las opciones, puesto que no usaremos un ActionForm Bean.

433
Figura 20

Al finalizar se creará la clase SociosAction, en la cual añadiremos código para crear un atributo de solicitud
con la lista de socios.
package action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
public class SociosAction extends org.apache.struts.action.Action {
private static final String SUCCESS = "success";
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
String [] socios = {"Socio 1", "Socio 2", "Socio 3"};
request.setAttribute("socios", socios);
return mapping.findForward(SUCCESS);
}
}
El método execute() inyecta atributos de ámbito y retorna el ActionForward "sucess". Este resultado
deberá ser asociado con la página socios.jsp en el fichero de configuración de Structs.
Si abrimos el fichero structs-config.xml veremos que en el nodo <action-mappings> se ha creado un
elemento <action path= "/socios" type= "action.SociosAction" />. Le añadiremos un ActionForward
que asocie la respuesta "success" con la página "/socios.jsp". Para ello hay que hacer clic con el botón
secundario del ratón sobre el elemento <action> y pulsar la opción de menú «Add Forward».

434
Figura 21

El resultado será:
<action-mappings>
<action path="/socios" type="action.SociosAction">
<forward name="success" path="/socios.jsp"/>
</action>
<action path="/welcome" forward="/welcomeStruts.jsp"/>
</action-mappings>
Podemos probar esta configuración invocando en un navegador el recurso /socios.do.
Figura 22

3.4. Creación de una Acción que recibe una ActionForm Bean.


Veremos ahora cómo crear una Acción que servirá un formulario para solicitar las credenciales de un usuario.
La acción validará las credenciales y tomará decisiones para determinar la vista que debe retornar.
Primero crearemos el ActionForm Bean Usuario, que incluirá las propiedades nombre y clave.Para ello hay
que añadir un «Structs ActionForm Bean» de la categoría «Structs».

435
Figura 23

En el siguiente paso asignaremos el nombre a la ActionForm Bean y su paquete. Como se ve en la ilustración,


un ActionForm hereda de la clase org.apache.structs.action.ActionForm.
Figura 24

Tras finalizar se crea la clase Usuario.


package bean;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMapping;

436
import org.apache.struts.action.ActionMessage;
public class Usuario extends org.apache.struts.action.ActionForm {
private String nombre;
private String clave;
// ……… getters y setters ………..
public Usuario() {
}
@Override
public void reset(ActionMapping mapping, HttpServletRequest request) {
setNombre("d");
setClave("d");
}
@Override
public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
if (getNombre() == null || getNombre().trim().length() < 1) {
errors.add("nombre", new ActionMessage("error.nombre.required"));
}
if (getClave() == null || getClave().length() < 8) {
errors.add("clave", new ActionMessage("error.clave.required"));
}
return errors;
}
}
La clase ActionForm incluye un método para resetear y validar un objeto de formulario. El método reset()
será invocado cuando desde un formulario se pulse un botón de reseteo, y el método validate() será
invocado cuando desde un formulario se posteen los datos. Para la clase Usuario se resetean los atributos a
un string vacío, y se valida que el nombre no sea vacío, y que la clave tenga al menos 8 caracteres.
El método validate() retorna un objeto ActionErrors, el cual contendrá mensajes de error asociados con las
claves creadas en el fichero de propiedades ApplicationResource.properties. En este fichero de recursos
debemos agregar las entradas necesarias para guardar nuestros mensajes.
La página que incluirá el formulario es la siguiente.
<!-- PÁGINA login.jsp -->
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html:html lang="true">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Página de login</title>
</head>
<body>
<html:form action="/login" >
Usuario <html:text property="nombre" /><html:errors property="nombre" /><br>
Clave <html:password property="clave" /><html:errors property="clave" /><br>
<html:submit value="Enviar" /><html:reset value="cancelar"/>
</html:form>
</body>
</html:html>
Y crearemos dos páginas más, una para cuando se validen las credenciales y otra por si no se validan:
<!-- PÁGINA correcto.jsp -->
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>

437
<h1>USUARIO VALIDADO</h1>
</body>
</html>

<!-- PÁGINA fallo.jsp -->


<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>FALLO EN LAS CREDENCIALES</h1>
</body>
</html>
Las etiquetas de ayuda <html: /> permiten asociar los controles de formulario con el ActionForm. También
se pueden recuperar los mensajes de error mediante las etiquetas <html:error>.
Por último crearemos la Acción LoginAction. Para ello hay que agregar un «Struct Action» de la categoría
«Structs». En el primer paso asignaremos el nombre, el paquete y la ruta de acción.
Figura 25

En el siguiente paso se asigna el ActionForm Bean Usuario, el recurso asociado /login.jsp y el nombre de
atributo usuario que se inyectará en el ámbito de sesión o de solicitud. También marcaremos la opción para
validar el ActionForm Bean.

438
Figura 26

Tras finalizar añadiremos a la clase LoginAction un código que valide las credenciales. Por simplicidad se
validará un usuario si su nombre y clave coinciden. No es necesario validar cada dato por separado, puesto
que eso ya lo hace Structs automáticamente.
package action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
public class LoginAction extends org.apache.struts.action.Action {
private static final String SUCCESS = "success";
private static final String FALLO = "fallo";
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
Usuario usuario = (Usuario) form;
if (usuario.getNombre().equalsIgnoreCase(usuario.getClave())) {
return mapping.findForward(SUCCESS);
} else {
return mapping.findForward(FALLO);
}
}
}
Por último debemos configurar los ActionForward en el fichero structs-config.xml:
<action-mappings>
<action path="/welcome" forward="/welcomeStruts.jsp"/>
<action attribute="usuario" input="/login.jsp" name="Usuario"
path="/login" scope="session" type="action.LoginAction">
<forward name="success" path="/correcto.jsp"/>
<forward name="fallo" path="/fallo.jsp"/>
</action>
</action-mappings>
Podemos probar la Acción invocando la ruta /login.do.

439
Figura 27

PRÁCTICA
Debes crear una aplicación empresarial basada en el patrón MVC. Esta aplicación permitirá a los clientes
realizar operaciones sobre un almacén de reservas. Cada reserva constará de:
Un código numérico.
El nombre del cliente.
La fecha de la reserva.
Una descripción.
Las aplicaciones cliente deberán acceder a la aplicación empresarial mediante páginas web, las culeas
proporcionarán interfaces para crear una reserva, revocar una reserva por el nombre del cliente y la fecha,
consultar las reservas por fechas, consultar las reservas por nombre del cliente y modificar la fecha y
descripción de una reserva dado su código.
1) Crea una clase de servicio que realice las operaciones sobre las reservas.
2) Valida los datos de formulario: El código no puede repetirse, la fecha de reserva no puede ser mayor que
el día actual, la descripción no puede pasar de más de 100 caracteres.

440

También podría gustarte