Download Programación web en Java
Document related concepts
no text concepts found
Transcript
Programación web en Java AULA MENTOR educacion.es Nipo: 030-12-335-9 Autores: José Miguel Ordax Cassá Pilar Aranzazu Ocaña Díaz-Ufano Ilustración de portada: María Guija Medina Unidad de Aprendizaje 1 INTRODUCCIÓN A JAVA EE ÍNDICE 1.1 Introducción ..........................................................................3 1.1.1 Java Card .......................................................................... 3 1.1.2 Java Micro Edition (Java ME) ......................................... 4 1.1.3 Java Standard Edition (Java SE).................................... 4 1.1.4 Java Enterprise Edition (Java EE) .................................. 4 1.2 El modelo de aplicación Java EE........................................5 1.2.1 Componentes Java EE .................................................... 6 1.2.2 Contenedores Java EE .................................................... 7 1.2.3 Servicios Java EE ............................................................. 8 1.3 El diseño de aplicaciones Java EE .....................................8 1.4 Las especificaciones Java EE...........................................10 1.5 El ensamblado y despliegue de aplicaciones Java EE ..13 1.6 El Servidor de Aplicaciones Java EE................................15 PARA RECORDAR ....................................................................18 MÓDULO A – Unidad 1: Introducción a Java EE 1.1 Introducción Debido a la naturaleza del lenguaje Java: portable, seguro, multithread, etc…, está siendo utilizado en multitud de ámbitos y tecnologías, desde el chip de una tarjeta de crédito hasta un servidor de la más alta gama. Evidentemente, estos distintos ámbitos o entornos, tienen unas características y peculiaridades muy distintas entre sí. Por ejemplo, la cantidad de memoria disponible en el chip de una tarjeta de crédito y la de un servidor es muy distinta, por lo que habrá que tenerlo en cuenta a la hora de desarrollar las aplicaciones. Es por ello que existen distintas plataformas Java, dependiendo del ámbito en el que se vaya a trabajar. Son las siguientes: Java Card. Java Micro Edition (Java ME). Java Standard Edition (Java SE). Java Enterprise Edition (Java EE). Podemos verlas resumidas en el siguiente gráfico: 1.1.1 Java Card La plataforma Java Card define las APIs y requerimientos necesarios para poder ejecutar aplicaciones Java en los chips de las tarjetas. Debido a las mínimas prestaciones del entorno de ejecución contiene el API más escueto. El estudio de esta plataforma no es el objetivo de este curso, pero si el alumno quiere profundizar en este tema podrá encontrar más información en la siguiente URL: http://www.oracle.com/technetwork/java/javame/javacard/overview/getstarted/index.html 3 1.1.2 Java Micro Edition (Java ME) La plataforma Java Micro Edition define las APIs y requerimientos necesarios para poder ejecutar aplicaciones Java en dispositivos embebidos. Debido a la gran diversidad de estos dispositivos, desde teléfonos móviles o buscas con pocas prestaciones hasta televisores o automóviles mucho más potentes, se definieron distintas configuraciones con más o menos APIs y por tanto, posibilidades. Por defecto existen estos dos: CLDC (Connected Limited Device Configuration): Define las APIs y la JVM (denominada KVM) para dispositivos con muy pocas prestaciones. CDC (Connected Device Configuration): Define las APIs para dispositivos con pocas prestaciones pero conectados a la red. No requiere una JVM especial. El estudio de esta plataforma no es el objetivo de este curso, pero si el alumno quiere profundizar en este tema podrá encontrar más información en la siguiente URL: http://www.oracle.com/technetwork/java/javame/index.html 1.1.3 Java Standard Edition (Java SE) La plataforma Java Standard Edition (Java SE) define las APIs y requerimientos necesarios para poder ejecutar aplicaciones Java de escritorio en ordenadores personales o portátiles. El estudio de esta plataforma no es el objetivo de este curso, pero si el alumno quiere profundizar en este tema, Aula Mentor cuenta con otros dos cursos que la cubren: Programación en Java – Inicial Programación en Java – Avanzado Puedes consultar la información de estos cursos en www.aulamentor.es, en el apartado “Cursos” – “Programación”. 1.1.4 Java Enterprise Edition (Java EE) La plataforma Java Enterprise Edition (Java EE) define las APIs y requerimientos necesarios para poder ejecutar aplicaciones Java servidoras, con todo lo que ello supone: clienteservidor, multiusuario, transaccionalidad, escalabilidad, etc…en definitiva, características que no eran importantes o imprescindibles en aplicaciones de escritorio. Se apoya en la plataforma Java SE, por lo que es imprescindible conocer y dominar dicha plataforma antes de aventurarse en esta otra. Utiliza la misma Máquina Virtual Java (JVM). Este curso se centra precisamente en esta plataforma, aunque no en su totalidad. Como veremos un poco más adelante, las aplicaciones Java EE pueden dividirse en varias capas y este curso se centra la denominada capa web o de presentación. La plataforma Java Enterprise Edition ha pasado por distintas nomenclaturas y versiones a lo largo de su vida, de manera que hubo momentos en el tiempo en el que se denominó Java 2 y luego simplemente Java. Las versiones también han sufrido modificaciones, comenzando a numerarse como 1.2, 1.3… para a posteriori pasar a numerarse como 5.0, 6.0… Actualmente estamos en la versión 6.0, pero en su totalidad han existido: Java 2 EE 1.2 4 MÓDULO A – Unidad 1: Introducción a Java EE Java 2 EE 1.3 Java 2 EE 1.4 Java EE 5.0 Java EE 6.0 La definición de esta y las otras plataformas se realiza mediante un proceso colaborativo entre distintas empresas denominado Java Community Process (JCP). Para cada plataforma, API, funcionalidad… se crea lo que se denomina como Java Specification Request (JSR) donde se sientan las bases y especificaciones de dicha plataforma, API o funcionalidad. En concreto la discusión y definición de la futura plataforma Java EE 7.0 se puede seguir en la siguiente URL: http://www.jcp.org/en/jsr/detail?id=342 1.2 El modelo de aplicación Java EE El modelo de aplicaciones Java EE define una arquitectura para implementar servicios como aplicaciones multicapa que aseguren la escalabilidad, accesibilidad y facilidad de gestión necesarias en un ámbito empresarial. El modelo divide el trabajo a realizar en la implementación en dos partes: La lógica de presentación y de negocio a implementar por el desarrollador. Los servicios estándar que ofrece la plataforma Java EE. El desarrollador puede apoyarse en los servicios ofrecidos por la plataforma Java EE en vez de reinventar la rueda una y otra vez, facilitándole así el concentrarse únicamente en la lógica específica de su aplicación. La plataforma Java EE utiliza un modelo de programación distribuido en distintas capas. La lógica de la aplicación se divide en distintos componentes dependiendo de su funcionalidad, y estos son desplegados en las distintas capas dependiendo de a cuál pertenecen. Las distintas capas son: Capa cliente (Client Tier): responsable de la interacción con el usuario. Capa web (Web Tier): responsable del control de la aplicación y en ocasiones también de la interacción con el usuario. Capa de negocio (Business Tier): responsable de la lógica de la aplicación propiamente dicha. Capa de datos (EIS Tier): responsable de la persistencia de datos y/o lógica especializada (conocida con el nombre de EIS: Enterprise Information System, o Sistema de Información Empresarial). Por ejemplo ERPs, BBDD, Motores Transaccionales (CICS, IMS, Tuxedo…). A continuación mostramos este concepto de multicapa en un diagrama: 5 Es muy importante tener en cuenta, que esta división es puramente lógica y no física. Es decir, físicamente cada capa no tendrá por qué estar en máquinas independientes, sino que podrán compartir hardware. Por ejemplo, veremos que lo normal será que el entorno de desarrollo que montaremos para resolver las distintas actividades de este curso tendrá todas las capas físicamente en la misma máquina. Adicionalmente al tema multicapa, el modelo de aplicación Java EE define otros tres conceptos claves para entender la plataforma: Componentes: Unidades de software que forman o componen la aplicación. Contenedores: Entorno de ejecución donde se ejecutan los componentes. Servicios: Funcionalidades estándar que todo contenedor debe proveer a los componentes. Veamos qué son y en qué consisten cada uno de estos conceptos. 1.2.1 Componentes Java EE Una aplicación Java EE está compuesta de componentes. Un componente Java EE es una unidad de software funcional auto contenida que se ensambla como parte de una aplicación Java EE y que puede interactuar con otros componentes. Las especificaciones Java EE definen lo siguientes tipos de componentes: Componentes cliente: son aplicaciones Java SE (AWT/Swing, Applets) o un navegador web (Firefox, Chrome, IExplorer…). Se despliegan en la capa cliente. 6 MÓDULO A – Unidad 1: Introducción a Java EE Componentes web: son Java Servlets, JavaServer Pages (JSP) o JavaServer Faces (JSF). Se despliegan en la capa web. Componentes de negocio: Enterprise JavaBeans (EJB). Se despliegan en la capa de negocio. 1.2.2 Contenedores Java EE Normalmente el desarrollo de una aplicación empresarial es muy complicado dado que el desarrollador tiene que tener en cuenta temas muy importantes como la gestión multiusuario, la gestión de la transaccionalidad, la gestión de la seguridad, la compartición de recursos, etc… El modelo de programación de la plataforma Java EE facilita enormemente esta tarea con la definición de los contenedores Java EE. Estos contenedores ofrecen al desarrollador una serie de servicios sobre los que se puede apoyar permitiéndole centrarse en el desarrollo de la lógica de negocio de la aplicación propiamente dicha. Dependiendo del tipo de contenedor, ofrecerá unos servicios u otros, y permitirá desplegar en él un tipo de componente u otro. Los tipos de contenedores Java EE son: Contenedor cliente (Application Client Container o Applet Container). Contenedor web (Web Container). Contenedor de negocio o de EJBs (EJB Container). Como podemos ver, cada tipo de contenedor corresponde con una de las capas definidas, a excepción de la capa de datos que está implementada por otro tipo de productos (ya mencionados anteriormente) ajenos a la plataforma Java EE. En el siguiente diagrama, podemos observar la relación entre los distintos tipos de contenedores Java EE: 7 1.2.3 Servicios Java EE Las especificaciones Java EE, definen una serie de funcionalidades que los distintos tipos de contenedores deberán implementar y ofrecer a los desarrolladores de aplicaciones Java EE. Existen multitud de servicios, pero simplemente destacaremos algunos: De directorio: para la indexación y búsqueda de componentes y recursos. De despliegue: para facilitar la descripción y personalización de componentes a la hora de su instalación. De transaccionalidad: para poder ejecutar distintas acciones en una misma unidad transaccional. De seguridad: para poder autenticar y autorizar a los usuarios de una aplicación. De acceso a datos: para facilitar el acceso a las Bases de Datos. De conectividad: para facilitar el acceso a los distintos Sistemas de Información Empresarial (EIS). De mensajería: para poder comunicarse con otros componentes, aplicaciones o EISs. 1.3 El diseño de aplicaciones Java EE Todos los informáticos saben, que antes de ponerse a programar hay una fase muy importante de análisis y de diseño donde se estudia y define la solución. Para ello, contamos con el lenguaje UML (Unified Modeling Language) que define una serie de diagramas y notaciones para poder plasmar estos análisis y diseños. Nota: El lenguaje UML no es objeto de este curso pero se cubre con cierta profundidad en el curso de Aula Mentor: Programación en Java – Inicial. Adicionalmente, existe el concepto de Patrones de Diseño, que son un conjunto de soluciones (o diseños) a los problemas más comunes en la programación de aplicaciones, que han demostrado ser útiles y eficientes en la resolución de dichos problemas. Su existencia, permite poder reutilizar y aplicar soluciones existentes, y no estar reinventando la rueda constantemente. Pues bien, existe un conjunto importante de estos patrones de diseño muy relacionados con el desarrollo de aplicaciones Java EE. A lo largo del curso iremos viendo algunos, pero si el alumno quiere profundizar en este tema tiene el catálogo completo en las Blue Prints de Java EE: http://www.oracle.com/technetwork/java/catalog-137601.html 8 MÓDULO A – Unidad 1: Introducción a Java EE En este capítulo de introducción a Java EE, vamos a hablar de uno que ataca directamente las problemáticas derivadas del modelo de una aplicación Java EE multicapa. Es conocido con el nombre de: Modelo-Vista-Controlador (MVC). Las aplicaciones que manejan acceso a datos, gestionan distintas presentaciones y tienen lógica de negocio compleja, suelen sufrir un problema serio a la hora de mantenerlas debido a interdependencias entre todos los componentes. Dichas interdependencias también dificultan la reutilización de código, obligando a rescribir más veces de las deseadas una lógica muy parecida. El patrón de diseño MVC resuelve estos problemas desacoplando el acceso a datos de la lógica de negocio y esta de la presentación. De esta forma, se podrá reutilizar un acceso desde distintas funcionalidades, o reutilizar la misma funcionalidad desde distintos tipos de presentación, etc… facilitando también el mantenimiento posterior: El Modelo (Model): Representa los datos y cualquier lógica de negocio relacionada con ellos. La Vista (View): renderiza el contenido de los modelos dependiendo de la tipología de cliente (navegador web, teléfono móvil, etc…), permitiendo su visualización. El Controlador (Controller): define el comportamiento general de la aplicación coordinando a las otras dos partes (Modelo y Vista). Veamos el patrón de diseño en un diagrama: 9 Pues bien, los distintos tipos de componentes que hemos introducido en el apartado del modelo de aplicación Java EE, encajan perfectamente en este diseño: Modelo: Enterprise JavaBeans, POJOs (Plain Old Java Objects). Vista: JavaServer Pages (JSP), JavaServer Faces (JSF). Controlador: Java Servlets. Un ejemplo del flujo de una aplicación Java EE sería algo así: 1. El cliente, por ejemplo un navegador, solicitará una funcionalidad desde el interface visual (Vista). 2. Dicha petición entrará a través de un Java Servlet (Controlador). 3. Dicho Java Servlet, analizará qué se está pidiendo, qué información adicional aporta y decidirá que Enterprise JavaBean o POJO (Modelo) cubre dicha petición. 4. Lo invocará, y tras recibir un resultado, decidirá qué JavaServer Page (JSP) muestra dicho resultado al cliente (Vista). 5. El resultado será devuelto y mostrado. Y visualmente: Evidentemente, las aplicaciones se pueden desarrollar sin tener en cuenta estos patrones de diseño, pero está demostrado sobradamente en el mercado, el aumento de productividad y mejora del mantenimiento con su uso. A lo largo del curso iremos insistiendo en este punto y detallando y practicando tanto este patrón como otros. 1.4 Las especificaciones Java EE Las especificaciones Java EE son el conjunto de las definiciones detalladas de los conceptos que forman parte de la plataforma Java EE: componentes, contenedores y servicios. 10 MÓDULO A – Unidad 1: Introducción a Java EE No entraremos en el detalle de cada una. A lo largo del curso, iremos desgranando aquellas especificaciones relacionadas con la programación web. En concreto, las especificaciones Java EE 6.0 (última versión en el momento de la redacción de este manual) están compuestas por las siguientes definiciones (en negrita las que cubriremos en el curso): Java API for RESTful Web Services (JAX-RS) 1.1 Implementing Enterprise Web Services 1.3 Java API for XML-Based Web Services (JAX-WS) 2.2 Java Architecture for XML Binding (JAXB) 2.2 Web Services Metadata for the Java Platform Java API for XML-Based RPC (JAX-RPC) 1.1 Java APIs for XML Messaging 1.3 Java API for XML Registries (JAXR) 1.0 Java Servlet 3.0 JavaServer Faces 2.0 JavaServer Pages 2.2/Expression Language 2.2 Standard Tag Library for JavaServer Pages (JSTL) 1.2 Debugging Support for Other Languages 1.0 Contexts and Dependency Injection for Java (Web Beans 1.0) Dependency Injection for Java 1.0 Bean Validation 1.0 Enterprise JavaBeans 3.1 (incluye Interceptors 1.1) Java EE Connector Architecture 1.6 Java Persistence 2.0 Common Annotations for the Java Platform 1.1 Java Message Service API 1.1 Java Transaction API (JTA) 1.1 JavaMail 1.4 Java Authentication Service Provider Interface for Java Authorization Contract for Containers 1.3 Java EE Application Deployment 1.2 11 J2EE Management 1.1 Dado que la plataforma Java EE extiende la Java SE, existen especificaciones Java SE incluidas en Java EE: Java API for XML Processing (JAXP) 1.3 Java Database Connectivity 4.0 Java Management Extensions (JMX) 2.0 JavaBeans Activation Framework (JAF) 1.1 Streaming API for XML (StAX) 1.0 Java Naming and Directory Interface (JNDI) 1.2 Como ya hemos comentado, la plataforma Java EE es un tema muy extenso que toca gran cantidad de ámbitos y tecnologías. Nosotros en este curso titulado “Programación Web” nos centraremos exclusivamente en aquellas especificaciones relacionadas con la presentación y el acceso a Bases de Datos. Existe documentación on-line de todas estas especificaciones o APIs que serán de enorme utilidad a la hora de desarrollar aplicaciones Java EE: http://download.oracle.com/javaee/6/api/ 12 MÓDULO A – Unidad 1: Introducción a Java EE 1.5 El ensamblado y despliegue de aplicaciones Java EE Una aplicación Java EE está formada por un empaquetamiento de una o varias unidades conocidas con el nombre de módulos. Estos módulos contienen a su vez: Uno o varios componentes (Java Servlets, Enterprise JavaBeans (EJB)…). Un descriptor de despliegue que describe el contenido y características del módulo (desde la versión 5.0 estos descriptores son opcionales, ya que el propio código puede ser auto descriptivo mediante el uso de anotaciones). Existen distintos tipos de módulos dependiendo de su contenido y el contenedor donde se vaya a ejecutar. Los distintos módulos van empaquetados en un fichero JAR (Java ARchive). No obstante, la extensión de dicho fichero dependerá del tipo de módulo: Módulo Web (Web Module): contiene normalmente Java Servlets, JavaServer Pages (JSP), JavaServer Faces (JSF), contenidos estáticos como imágenes, HTMLs, CSSs… La extensión del fichero empaquetado será WAR (Web ARchive). Modulo de EJBs (EJB Module): como su nombre indica, contiene Enterprise JavaBeans (EJB). La extensión del fichero empaquetado es la de por defecto, JAR (Java ARchive). Modulo cliente (Application Client Module): contiene el código de la aplicación cliente. La extensión del fichero empaquetado es la de por defecto, JAR (Java ARchive). Modulo Adaptador (Resource Adapter Module): contiene un conector para comunicarse con un Sistema de Información Empresarial (EIS). La extensión del fichero empaquetado será RAR (Resource ARchive). La aplicación Java EE a su vez, también es un empaquetado de los distintos módulos que la forman. La extensión del fichero empaquetado será EAR (Enterprise ARchive). Una aplicación Java EE no tiene por qué contener módulos de todos los tipos, lo que si es necesario, es que tenga como mínimo uno independientemente del tipo. Gráficamente, la estructura de una aplicación Java EE empaquetada sería la siguiente, partiendo desde la raíz del EAR (Assembly Root): 13 El descriptor de despliegue de una aplicación Java EE es un fichero XML llamado application.xml que reside en un directorio denominado META-INF. Como ya hemos mencionado, desde la versión 5.0 de las especificaciones Java EE, este descriptor de despliegue es opcional si se han usado anotaciones en el código. No obstante, no está mal seguir utilizándolos para que los administradores de los sistemas de una manera legible y sencilla puedan entender el contenido y las características de la aplicación. Un ejemplo de descriptor de despliegue sería: <?xml version="1.0" encoding="UTF‐8"?> <application xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:application="http://java.sun.com/xml/ns/javaee/application_5.xsd " xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_6.xsd" id="PruebaEAR" version="6"> <module> <connector>PruebaConnector.rar</connector> </module> <module> <java>PruebaClient.jar</java> </module> <module> <ejb>PruebaEJB.jar</ejb> </module> <module> <web> <web‐uri>PruebaWeb.war</web‐uri> <context‐root>PruebaWeb</context‐root> </web> </module> </application> Se trata de una aplicación Java EE que contiene un módulo de cada tipo. No hay que preocuparse si no se entiende el contenido del fichero como tal, ya que de momento, simplemente se trata de ver un ejemplo del concepto de descriptor de despliegue. 14 MÓDULO A – Unidad 1: Introducción a Java EE Debido a que este curso es sobre programación web, nos centraremos únicamente en los módulos web, aunque no está de más conocer la existencia de otro tipo de módulos. 1.6 El Servidor de Aplicaciones Java EE Hasta aquí, todo han sido especificaciones, definiciones… pero para poder ejecutar una aplicación Java EE necesitamos un entorno de ejecución. Dicho entorno de ejecución se conoce con el nombre de Servidor de Aplicaciones. Un Servidor de Aplicaciones por tanto, es un producto de software que implementa todas las especificaciones Java EE. De manera que al desplegar o instalar una aplicación Java EE en el servidor, sabemos seguro que va a encontrarse con todos los contenedores y servicios definidos por la especificación y que seguramente utiliza y necesita la aplicación. Existe una batería de pruebas estándar que todo proveedor de Servidores de Aplicaciones debe pasar satisfactoriamente para poder decir que es Java EE. Es lo que se conoce con el nombre de JCK (java Compatibility Kit o Kit de Compatibilidad Java). Gracias a la existencia del estándar Java EE, podemos tener la tranquilidad de que nuestra aplicación debe funcionar perfectamente en el Servidor de Aplicaciones de cualquier proveedor, asegurándonos así que no debemos de trabajar con ninguno en concreto. Existen multitud de Servidores de Aplicaciones en el mercado. Podríamos categorizarlos de la siguiente forma: Gratuitos o de pago. Certificados Java EE o no. A continuación comentamos algunos. No están todos los que son, ni son todos los que están, pero si los más usados y conocidos: Apache Tomcat: Es gratuito de código abierto. No cumple con el 100% de las especificaciones Java EE por lo que no se puede decir que sea un Servidor de Aplicaciones Java EE. Pero si que cubre algunas de las especificaciones relacionadas con la programación web que estudiamos en este curso: Java Servlet 3.0, JavaServer Pages 2.2, Java Database Connectivity 4.0 y Java Naming and Directory Interface 1.2 Este es el Servidor de Aplicaciones que usaremos en este curso. En los Materiales de Apoyo de la Mesa del Alumno, encontraréis las instrucciones de descarga, instalación y configuración del servidor. URL: http://tomcat.apache.org 15 Jetty: Es gratuito y de código abierto. Al igual que pasara con Apache Tomcat, tampoco implementa el 100% de las especificaciones Java EE. Una vez más, se centra exclusivamente en algunas especificaciones web. URL: http://jetty.codehaus.org/jetty/ WebSphere Application Server: Es de pago de la empresa IBM. Se trata de un Servidor de Aplicaciones Java EE completo. URL: http://www-01.ibm.com/software/webservers/appserv/was/ Apache Gerónimo: Es gratuito y de código abierto. Se trata de un Servidor de Aplicaciones Java EE completo. URL: http://geronimo.apache.org Oracle Weblogic Server: Es de pago de la empresa Oracle. Se trata de un Servidor de Aplicaciones Java EE completo. 16 MÓDULO A – Unidad 1: Introducción a Java EE URL: http://www.oracle.com/us/products/middleware/application-server/ GlassFish: Es gratuito y de código abierto. Se trata de un Servidor de Aplicaciones Java EE completo. URL: http://glassfish.java.net/ JBoss: Es gratuito y de código abierto. Se trata de un Servidor de Aplicaciones Java EE completo. URL: http://www.jboss.org/jbossas 17 PARA RECORDAR En esta unidad hemos visto la diferencia entre las distintas versiones de Java: Java Card, Java Micro Edition, Java Standar Edition y Java Enterprise Edition (Java EE). Las aplicaciones Java EE, están distribuidas en distintas capas para una mejor organización funcional: capa cliente (para interactuar con el usuario), capa web (para llevar el control de la aplicación y a veces interactuar con el usuario), capa de negocio (contiene la lógica del negocio como tal) y la capa de datos (contiene la información de negocio). A su vez la plataforma Java EE, para poder ejecutar las aplicaciones Java EE multicapa, está formada por: Componentes: Unidades de software que forman o componen la aplicación Contenedores: Entorno de ejecución donde se ejecutan los componentes. Servicios: Funcionalidades estándar que todo contenedor debe proveer a los componentes. Para poder diseñar aplicaciones Java EE, ya existen distintos patrones de diseño muy extendidos que posibilitan la reutilización de los componentes y un mejor mantenimiento de los mismos, como es el patrón Modelo-Vista-Controlador (MVC). Para que una aplicación Java EE, pueda ejecutarse en un servidor Java EE, estos deben de cumplir las especificaciones determinadas en una versión dada o al menos aquellas necesarias para el tipo de aplicación que se desea implementar. Las aplicaciones Java EE (fichero EAR) están formadas por al menos un módulo de los siguientes tipos: Módulo Web (fichero WAR), Modulo de EJBs (fichero JAR), Modulo cliente (fichero JAR), Modulo Adaptador (fichero RAR). 18 En el mercado existen una multitud de servidores de aplicaciones, pudiendo ser clasificados por dos categorías: Gratuitos o de pago. Certificados Java EE o no. 19 20 Unidad de Aprendizaje 2 EL ENTORNO DE DESARROLLO ÍNDICE 2.1 Introducción ................................................................... 23 2.2 Descarga, instalación y configuración del entorno de desarrollo........................................................................ 24 2.2.1 Java Runtime Environment (JRE) .................................24 2.2.2 Eclipse IDE for Java EE Developers ............................29 2.2.3 Apache Tomcat...............................................................34 PARA RECORDAR.................................................................... 40 MÓDULO A – Unidad 2: El Entorno de Desarrollo 2.1 Introducción Existen multitud de herramientas de desarrollo y ejecución de aplicaciones Java EE. Muchas de ellas son de pago, pero existen también gratuitas proporcionadas por la Comunidad Open Source. Aunque en condiciones normales, en el día a día de una empresa se suelen utilizar soluciones de pago, principalmente por su estabilidad y soporte oficial, para la realización de este curso nos vamos a apoyar en herramientas gratuitas. En concreto: Eclipse IDE for Java EE Developers 3.7.x de Eclipse.org: Herramienta para el desarrollo. Apache Tomcat 7.0.x de The Apache Software Foundation: Herramienta para la ejecución de aplicaciones web propuestas en este curso. Como decimos, ambas dos son gratuitas y descargables de la red. No obstante, vamos a comentar algunas otras herramientas de desarrollo, aunque no las vayamos a utilizar, para el conocimiento del alumno (los servidores de aplicaciones ya se han mencionado en la Unidad 1): Java Enterprise Edition SDK: Es el entorno de desarrollo de referencia. Es gratuito y no cuenta con herramientas visuales. Es decir, sería el homónimo a la JDK de Java SE. URL: http://www.oracle.com/technetwork/java/javaee/downloads/index.html Eclipse IDE for Java EE Developers: Es gratuito y de código abierto. En concreto, es el que utilizaremos en este curso como hemos mencionado anteriormente. URL: http://www.eclipse.org/ Rational Application Developer: Es de pago de la empresa IBM. 23 URL: http://www.ibm.com/software/awdtools/developer/application/ NetBeans IDE: Es gratuito y de código abierto. URL: http://netbeans.org/ Oracle JDeveloper: Es de pago de la empresa Oracle. URL: http://www.oracle.com/technetwork/developer-tools/jdev/overview/index.html 2.2 Descarga, instalación y configuración del entorno de desarrollo A continuación, describiremos los pasos para descargar, instalar y configurar las herramientas seleccionadas en este curso anteriormente mencionadas para poder realizar satisfactoriamente las actividades de este curso. 2.2.1 Java Runtime Environment (JRE) Como seguramente ya conocerá el alumno, el Java Runtime Environment (JRE) es el entorno de ejecución Java, conocido también con el nombre de Java Virtual Machine (o Máquina Virtual Java). Es imprescindible para poder ejecutar código Java en un ordenador, y debido a que las dos herramientas que vamos a utilizar: Eclipse IDE for Java EE Developers y Apache Tomcat están desarrollados en Java, es necesaria la existencia de un JRE en nuestra máquina para poder ejecutarlas. Ir a la siguiente URL http://www.oracle.com/technetwork/java/javase/downloads mediante un navegador, y descargar el JRE 7. Para ello, seleccionar “Download JRE”: 24 MÓDULO A – Unidad 2: El Entorno de Desarrollo En la siguiente pantalla, aceptar la Licencia (“Accept License Agreement”) y elegir la plataforma deseada pulsando el enlace correspondiente (por ejemplo, para Windows x64, sería jre-7u1-windows-x64.exe). Nota: En el momento de la redacción de este manual, el número de Update del JRE 7.0 es el 1, de ahí el nombre jre-7u1-windows-x64.exe; pero en el momento en el que el alumno esté siguiendo este manual, puede que ya exista un Update más moderno y por tanto que haya cambiado el número. No obstante, a excepción del cambio de número y por tanto de nomenclatura, todos los pasos de descarga, instalación y configuración aquí descritos deberían seguir siendo válidos, por lo que el alumno puede elegir utilizar el Update 1 o uno posterior. 25 Guardar el fichero en algún directorio temporal del ordenador: Para instalarlo, ejecutar el fichero descargado (en nuestro ejemplo, jre-7u1-windowsx64.exe), mediante doble click del ratón. 26 MÓDULO A – Unidad 2: El Entorno de Desarrollo Pulsar el botón “Install >” para comenzar la instalación. Durante dicho proceso, aparecerá una barra mostrando el progreso de la instalación. Nota: La ubicación por defecto donde se instalará el JRE es C:\Program Files\Java\jre7; si deseamos instalarlo en una ubicación distinta, marcar la casilla “Change destination folder” antes de pulsar el botón “Install >”. 27 Cuando la instalación haya terminado, y si ha sido satisfactoria, debería aparecer la siguiente pantalla: Pulsar el botón “Close”. 28 MÓDULO A – Unidad 2: El Entorno de Desarrollo Aunque el instalador nos diga que la instalación ha sido satisfactoria, nunca está de más comprobar que efectivamente todo está bien. Para ello, una manera habitual de comprobarlo es abrir una sesión de DOS y ejecutar el comando: java.exe -version Si todo está correcto, debería devolver la versión del JRE instalado: 2.2.2 Eclipse IDE for Java EE Developers A continuación, pasaremos a descargar e instalar Eclipse IDE, la herramienta de desarrollo Java que vamos a utilizar durante este curso. Recordar que es importante asegurarse de la existencia de un Java Runtime Environment (JRE) en el ordenador. Si no existiese, seguir los pasos del apartado anterior. Ir a la siguiente URL http://www.eclipse.org/downloads mediante un navegador, y descargar Eclipse IDE for Java EE Developers para la plataforma deseada, pulsando sobre el enlace correspondiente (en nuestro ejemplo, Windows 64 bits): 29 Aparecerá una página con el mejor servidor para descargar el fichero que hemos solicitado dependiendo de nuestra conexión y localización. Simplemente pulsar sobre la flecha verde y guardar el fichero en un directorio temporal: 30 MÓDULO A – Unidad 2: El Entorno de Desarrollo Nota: En el momento de la redacción de este manual, la versión de Eclipse es la 3.7.1, es decir, el número de revisión es el 1, de ahí el nombre eclipse-jee-indigo-SR1-win32x86_64.zip; pero en el momento en el que el alumno esté siguiendo este manual, puede que ya exista una revisión más moderna y por tanto que haya cambiado el número. 31 No obstante, a excepción del cambio de número y por tanto de nomenclatura, todos los pasos de descarga, instalación y configuración aquí descritos deberían seguir siendo válidos. El alumno puede elegir por tanto el instalar el Update 1 o elegir otro Update posterior. Descomprimir el fichero que hemos descargado (en nuestro ejemplo, eclipse-jee-indigo-SR1win32-x86_64.zip) en el disco duro donde queramos tener Eclipse. Por ejemplo: D:\eclipse-jee-indigo-SR1-win32-x86_64 Arrancar Eclipse ejecutando: D:\eclipse-jee-indigo-SR1-win32-x86_64\eclipse.exe Nos preguntará por la ubicación del workspace (o área de trabajo) donde Eclipse va a ir guardando todos los proyectos y ficheros que vayamos desarrollando: Una vez hayamos seleccionado la ubicación, pulsar el botón “OK”: 32 MÓDULO A – Unidad 2: El Entorno de Desarrollo Cerrar la pestaña de “Welcome” (Bienvenida): 33 2.2.3 Apache Tomcat Por último, vamos a instalar el servidor Apache Tomcat, necesario para poder ejecutar las aplicaciones Java EE que desarrollemos en el Eclipse IDE for Java EE Developers. Para ello, ir a la siguiente URL http://tomcat.apache.org/download-70.cgi mediante un navegador, y descargar Tomcat para la plataforma deseada desde la sección “Core”, pulsando sobre el enlace correspondiente (en nuestro ejemplo, 64-bit Windows zip): Descargar y descomprimir el fichero (en nuestro ejemplo, apache-tomcat-7.0.22-windowsx64.zip) en el disco duro donde queramos tener Eclipse. Por ejemplo: D:\apache-tomcat-7.0.22 34 MÓDULO A – Unidad 2: El Entorno de Desarrollo Nota: En el momento de la redacción de este manual, la versión de Tomcat es la 7.0.22, es decir, el número de revisión es el 22, de ahí el nombre apache-tomcat-7.0.22-windowsx64.zip; pero en el momento en el que el alumno esté siguiendo este manual, puede que ya exista una revisión más moderna y por tanto que haya cambiado el número. No obstante, a excepción del cambio de número y por tanto de nomenclatura, todos los pasos de descarga, instalación y configuración aquí descritos deberían seguir siendo válidos. Como se ha mencionado en las herramientas anteriores, es decisión del alumno, el decidir utilizar esta revisión o una posterior. Una vez hecho esto, ya solo queda configurar Eclipse IDE for Java EE Developers para que utilice el Apache Tomcat que acabamos de descargar e instalar. Para ello, arrancamos de nuevo Eclipse y seleccionamos la vista “Servers”: 35 Pulsaremos el botón derecho del ratón sobre la zona en blanco de esta vista, y elegiremos la opción “New” -> “Server” del menú: Del listado de posibles servidores, seleccionaremos “Tomcat v7.0 Server” y pulsaremos el botón “Next >”: 36 MÓDULO A – Unidad 2: El Entorno de Desarrollo En la siguiente pantalla del asistente, añadiremos la ubicación donde hemos instalado Apache Tomcat (en nuestro ejemplo, D:\apache-tomcat-7.0.22 37 Y ya por último, pulsaremos el botón “Finish”. Si todo ha ido bien, ahora en la vista de Eclipse denominada “Servers”, debería aparecer nuestro servidor Apache Tomcat: Si seleccionamos con el ratón el nuevo servidor, y pulsamos el botón derecho deberíamos ver un menú con las distintas acciones que podemos realizar con él: 38 MÓDULO A – Unidad 2: El Entorno de Desarrollo Seleccionaremos la opción “Start” y veremos como aparecerá una nueva vista denominada “Console” donde irá apareciendo el log del servidor. En este caso, deberíamos ver cómo va saliendo toda la información del arranque. Algo parecido a esto: Volviendo a la vista “Servers”, veremos como el estado del servidor ahora ha cambiado poniendo “[Started, Synchronized]”. Podemos parar el servidor nuevamente desde el mismo menú de opciones desde donde lo arrancamos en el paso anterior y una vez lo haya hecho, cerrar Eclipse. Con esto damos por finalizada la instalación y configuración del entorno de desarrollo necesario para llevar a cabo este curso. 39 PARA RECORDAR En esta unidad hemos aprendido a instalar la herramienta de desarrollo, Eclipse IDE for Java EE Developers, el servidor de aplicaciones Apache Tomcat y la Java Runtime Environment (JRE) necesarias para la implementación de las aplicaciones web propuestas en este curso. 40 Unidad de Aprendizaje 3 PROTOCOLO HTTP Y LENGUAJE HTML ÍNDICE 3.1 Introducción ................................................................... 43 3.2 El protocolo HTTP ......................................................... 44 3.2.1 Dirección de Internet o URL..........................................45 3.2.2 Conversaciones HTTP ...................................................46 3.2.3 Sniffer HTTP ....................................................................51 3.3 El lenguaje HTML........................................................... 54 3.3.1 La etiqueta HTML ...........................................................55 3.3.2 La página HTML..............................................................56 3.3.3 El formulario HTML.........................................................57 3.3.3.1 Campos de texto ......................................................58 3.3.3.2 Áreas de texto ..........................................................59 3.3.3.3 Checkboxes...............................................................60 3.3.3.4 Radiobuttons............................................................61 3.3.3.5 Listas y Combos ........................................................62 3.3.3.6 Botones ....................................................................63 3.3.3.7 Campos ocultos ........................................................64 3.4 Ensamblado y despliegue ............................................ 64 PARA RECORDAR.................................................................... 67 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML 3.1 Introducción Al estudiar las aplicaciones Java EE (o Web), es imposible no hablar primero de la red de redes: Internet, y de los fundamentos sobre los que se apoya. Simplificando mucho, esos dos fundamentos son el protocolo de comunicaciones HTTP (HiperText Transfer Protocol) y el lenguaje de etiquetado HTML (HiperText Markup Language). Vamos a hablar un poco más en detalle de lo que es un sistema de comunicaciones en general, y el sistema de comunicaciones en el que se basa Internet: TCP/IP. Un sistema de comunicaciones en general, se representa como una pila de capas en las que cada una aporta una funcionalidad concreta a la comunicación. Existe el modelo de referencia OSI, que representa los sistemas de comunicación desde un punto de vista teórico en 7 capas y luego sistemas de comunicación concretos reales, que implementan ese modelo de referencia de distintas formas, como por ejemplo el TCP/IP o el SNA. En el siguiente diagrama se puede ver la comparativa entre el modelo de referencia OSI y el TCP/IP que es el sistema de comunicaciones que utiliza Internet: Muy brevemente, repasamos lo que cubre cada uno de los niveles: 1. Físico: Define las características físicas de la red material. 2. Enlace: Proporciona el servicio de envío de datos a través del enlace físico. 3. Red: Gestiona las conexiones de red para las capas superiores. 4. Transporte: Proporciona servicios de detección y corrección de errores. 5. Sesión: Gestiona las conexiones entre aplicaciones cooperativas. 6. Presentación: Estandariza la forma en la que se presentan los datos a las aplicaciones. 7. Aplicación: Aplicaciones que usan la red. 43 Cono podemos observar, el TCP/IP implementa los niveles teóricos en cuatro, a través de distintos protocolos como IP en el nivel que llama Internet, TCP y UDP entre otros en el nivel que llama Transporte, y HTTP, FTP, Telnet, etc… en el nivel que llama Aplicación. Es en este último nivel donde se encuentra el protocolo HTTP que realmente nos interesa para programar aplicaciones Java EE aunque este, sin el resto de los niveles sería inútil. De ahí que se hayan mencionado en esta introducción. Por otro lado, teníamos el lenguaje de etiquetado HTML, la otra piedra angular sobre la que se sustenta Internet. El HTML es el lenguaje mediante el cual definimos los contenidos que fluyen por Internet, conocidas como páginas HTML o páginas Web. Y quizás sin saberlo, todos los días estamos usando ambos dos fundamentos. Los navegadores web (IExplorer, Firefox, Chrome, Opera…) no son mas que aplicaciones que implementan el protocolo HTTP a través del cual se reciben bajo petición páginas HTML. ¿Por qué es tan importante tener claro los conceptos del HTTP y HTML entonces? Porque las aplicaciones Java EE que vamos a aprender a desarrollar, son las que están al otro lado sirviendo las peticiones de los navegadores web. Por tanto, van a tener que entender el protocolo HTTP y van a tener que saber contestar con páginas HTML que muestran la información solicitada. Nota: como decíamos al inicio de esta introducción, esta es una visión muy simplista pero que proporciona una idea clara. Es simplista en el sentido de que no solo se utiliza el protocolo HTTP (aunque si en la mayoría de los casos) ni siempre se utiliza HTML como medio para mostrar la información (aunque nuevamente si en la mayoría de los casos). Otras opciones a modo de ejemplo, mucho menos utilizadas, serían protocolos como FTP o Telnet, y lenguajes de etiquetado como WML o cHTML. 3.2 El protocolo HTTP Como hemos dicho ya, el protocolo HTTP es el protocolo fundamental desde el punto de vista del desarrollo de aplicaciones Java EE. Es el encargado de la transferencia de los recursos que componen la red. Es un protocolo que sigue un esquema de petición – respuesta (request – response) entre un cliente (habitualmente un navegador) y un servidor (habitualmente un servidor de aplicaciones Java EE como ya vimos en las unidades anteriores) donde el cliente solicita un recurso y el servidor se lo devuelve. Actualmente se encuentra en su versión 1.1, y es un estándar definido por el IETF (RFC 2616): http://www.ietf.org/rfc/rfc2616.txt 44 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML 3.2.1 Dirección de Internet o URL Las URLs (Uniform Resource Locator) identifican de forma unívoca un recurso en la red. Un recurso que puede ser una página HTML, una imagen JPG o incluso un componente Java EE como un Java Servlet o una JavaServer Page (JSP). Las URLs constan de las siguientes partes: Protocolo: HTTP, HTTPS, FTP, Telnet… a utilizar para acceder al recurso. Servidor: Dirección IP o nombre del servidor donde reside el servicio o aplicación que sabe servir el recurso. Puerto: Donde está escuchando el servicio o aplicación que sabe servir el recurso. Path (o camino): Donde está ubicado el recurso. Recurso: El recurso como tal que estamos solicitando. Utilizando el siguiente formato: Protocolo://Servidor:Puerto/Path/Recurso Nota: Es importante mantener el formato de los dos puntos y las barras. Lo entenderemos mucho mejor con un ejemplo muy sencillo que la mayoría hemos usado alguna vez al buscar algo en Google. Para ello, lo que hacemos es escribir en el navegador la siguiente dirección de Internet o URL: http://www.google.es:80/index.html ¿Qué significa realmente esto que escribimos habitualmente para buscar cosas en Internet? Significa que mediante el protocolo HTTP, nos conectamos a la aplicación que está escuchando en el puerto 80 del servidor www.google.es y le solicitamos el recurso index.html que está en el directorio raíz (porque no hemos especificado ningún path). Muchos estaréis pensando que vosotros no escribís nunca ese 80, o que nunca habíais especificado eso de index.html; correcto. ¿Qué ocurre entonces? Lo que ocurre, es que para ahorrar trabajo a los usuarios, se establecen muchas cosas por defecto. Por ejemplo, se ha establecido un puerto por defecto para cada protocolo de manera que si no decimos el puerto a utilizar, en este caso el navegador añade sin que nosotros lo sepamos el puerto correspondiente. Algunos puertos por defecto son: HTTP: 80 HTTPS: 443 FTP: 21 Telnet: 23 etc… 45 Y con el nombre del recurso ocurre igual. Está establecido que por defecto si no se especifica el nombre del recurso, las aplicaciones prueben con unos nombres por defecto que suelen ser: index.html index.htm index.jsp etc… Por tanto, una URL equivalente a la analizada y con mismo resultado sería: http://www.google.es Los servidores en Internet se identifican mediante direcciones IP que consisten en cuatro grupos de números desde el 0 al 255 separados por puntos, por ejemplo: 74.125.39.103 Todo servidor en Internet tiene asignada una dirección IP única. Si no fuese así, sería inaccesible porque al hacer una petición, el sistema de comunicaciones TCP/IP no sabría con cuál conectar. Como el uso de estas direcciones es algo complejo para los usuarios (difíciles de recordar, fácil de cometer un error), existen unos servidores especiales denominados DNS (Domain Name Server o Servidores de Nombres de Dominio) que se encargan de traducir nombres legibles y fáciles de recordar por los usuarios a direcciones IP. Esta traducción mediante la conexión a los servidores DNS la hace el sistema de comunicaciones TCP/IP, que comentamos en la introducción, automáticamente sin la intervención del usuario. Por ejemplo, en el momento de la redacción de este manual, la dirección IP de www.google.es era la del ejemplo: 74.125.39.103, es decir, que en el navegador deberíamos ver la misma página si escribiéramos: http://74.125.39.103 o si escribiéramos: http://ww.google.es 3.2.2 Conversaciones HTTP Como ya comentamos al inicio de este capítulo, HTTP es un protocolo que sigue un esquema de petición – respuesta (request – response) entre un cliente (habitualmente un navegador) y un donde el cliente solicita un recurso y el servidor se lo devuelve. Una petición HTTP se compone a grandes rasgos de: Método HTTP: se trata de la acción a realizar. Algunos ejemplos como veremos mas adelante son GET y POST. Recurso sobre el que actuar: es decir, una URL. 46 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML Parámetros: como los argumentos de un método Java. Por el otro lado, una respuesta HTTP se compone de: Código de estado: un código numérico que indica qué ocurrió con la petición. Tipo del contenido: que indica el formato de la respuesta. Por ejemplo, una página HTML, un documento XML, una imagen JPG, etc… Contenido: la respuesta como tal. El envío de peticiones y respuestas, se realiza mediante un mensaje HTTP, es decir, una estructura bien definida que sigue una serie de reglas que todo cliente y servidor que mantengan una conversación HTTP deben conocer y cumplir. Un mensaje HTTP se compone de: Una cabecera: que incluye información técnica. Un cuerpo: que incluye el contenido del mensaje como tal. Los métodos HTTP que se pueden utilizar como parte de una petición HTTP son los siguientes: HEAD: solicita un recurso al servidor, pero que la respuesta no ha de incluir el contenido, solo las cabeceras. GET: solicita un recurso al servidor (normalmente estático como por ejemplo un fichero). POST: solicita un recurso al servidor (normalmente dinámico, es decir, la ejecución de una aplicación). PUT: solicita el envío de un recurso al servidor. DELETE: solicita la eliminación de un recurso en el servidor. TRACE: solicita al servidor que le conteste lo mismo que le solicita. Se utiliza normalmente para analizar problemas en la comunicación donde entre el cliente y el servidor existen otra serie de elementos de comunicaciones como proxies, firewalls, etc… OPTIONS: solicita al servidor conocer qué métodos HTTP soporta. CONNECT: solicita al servidor cambiar a un sistema de comunicación cifrada (HTTPS). Mas adelante, veremos un poco más en detalle los métodos GET y POST que son los utilizados habitualmente en la programación de aplicaciones Java EE incluyendo algún ejemplo. Los códigos de estado que se pueden recibir en una respuesta HTTP son muchos y variados. Aquí comentamos algunos: Códigos 2xx: Petición realizada con éxito. 47 o 200: OK. Este es el código que deberíamos recibir habitualmente. o 204: Sin contenido. o 205: Sin contenido, recargar. o 206: Contenido parcial. Códigos 3xx: Redirección. o 301: Recurso movido de forma permanente. o 303: Ver otro recurso. o 307: Recurso movido de forma temporal. Códigos 4xx: Error del cliente. o 400: Petición incorrecta. Se produce si el mensaje HTTP de petición tiene errores. No es habitual cuando se usa un navegador, pero si, sí estamos programando un cliente HTTP. o 403: Prohibido. Se produce cuando intentamos acceder a un recurso protegido y no tenemos acceso. o 404: No encontrado. Se produce cuando se solicita un recurso no existente en el servidor. Códigos 5xx: Error en el servidor. o 500: Error interno. Más habitual de lo que desearíamos. o 503: Servicio no disponible. o 505: Versión HTTP no soportada. Vamos a centrarnos en los dos métodos, es decir, en los dos tipos de petición HTTP más comunes en el desarrollo de aplicaciones Java EE y que con total seguridad utilizaremos: GET y POST. GET es el más simple, y se suele utilizar para solicitar un recurso estático, es decir, un fichero de tipo HTML, JPG, XML…. No obstante, también puede ser usado para solicitar un recurso dinámico, es decir, por ejemplo un componente de una aplicación web, y por tanto, permite el envío de parámetros. Eso si, dichos parámetros van añadidos como parte de la URL y son visibles, lo que desde un punto de vista de seguridad puede ser un problema. Veamos un ejemplo de una petición HTTP con el método GET de un servlet Java que suma dos números: GET /EjemploWeb/Sumador?param1=12¶m2=41 HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, */* Referer: http://127.0.0.1/Practica23a/index.html Accept-Language: en-us,es;q=0.5 Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727) Host: 127.0.0.1:8080 48 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML Connection: Keep-Alive Este mensaje HTTP, que en concreto es una petición (lo sabemos porque su primera línea es un método HTTP en vez de un código de estado) solo tiene cabecera. No tiene cuerpo. La primera línea de toda petición HTTP siempre es el método HTTP, seguido del recurso sobre el que queremos ejecutar la acción, y por último la versión de protocolo HTTP utilizada por el cliente. Después, hay una secuencia de distintas propiedades (de la forma nombre: valor) que añaden información técnica al mensaje. Por ejemplo, qué tipo de cliente se está utilizando (User-Agent). Hay muchas, y solo comentaremos a lo largo del curso aquellas que tengan relevancia. En este ejemplo, estamos pidiendo el recurso /EjemploWeb/Sumador pasándole dos parámetros: param1 y param2 con los valores 12 y 41. Como se puede ver, se indica que hay parámetros mediante el símbolo ? y se separan los parámetros (parejas de nombre=valor) mediante el símbolo &. Veamos ahora la respuesta HTTP de este mismo ejemplo: HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Set-Cookie: JSESSIONID=304CE17CCEB55869A606CE06271F915B; Path=/EjemploWeb Content-Type: text/html;charset=ISO-8859-1 Content-Length: 314 Date: Sun, 08 Jun 2008 09:19:19 GMT <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <TITLE>Calculadora</TITLE> </HEAD> <BODY> El resultado de la suma de 12 y 41 es: 53 </BODY> </HTML> En este caso, el mensaje HTTP si que incluye tanto cabecera como cuerpo. Lo primero, sabemos que es una respuesta porque la primera línea comienza con la versión HTTP utilizada por el servidor, seguido del código de estado. En este caso, un 200 que indica que todo ha ido bien. Hay otras cabeceras adicionales muy importantes para saber interpretar la respuesta como son: Content-Type, que indica el tipo de contenido del cuerpo (en este caso un fichero HTML) y Content-Lenght, que indica el tamaño del contenido (en este caso 314 bytes). Separado por un salto de línea viene el cuerpo del mensaje HTTP que como ya nos indicaba la cabecera es un fichero HTML (más adelante, en esta misma unidad, hablaremos del lenguaje HTML). 49 POST es el método que habitualmente se utiliza para enviar parámetros, porque van en el cuerpo de la petición en vez de añadidos a la URL del recurso solicitado. Veamos el mismo ejemplo que antes para identificar las diferencias. En este caso la petición sería: POST /EjemploWeb/Sumador HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, */* Referer: http://127.0.0.1/EjemploWeb/index.html Accept-Language: en-us,es;q=0.5 Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727) Host: 127.0.0.1:8080 Content-Length: 19 Connection: Keep-Alive Cache-Control: no-cache param1=45¶m2=21 Nuevamente identificamos que se trata de una petición HTTP porque la primera línea comienza con un método HTTP. Y vemos, que a diferencia de lo que ocurría con el método GET, en esta ocasión la URL no incluye ningún parámetro sino que el mensaje tiene un cuerpo con dichos parámetros. Hay alguna diferencia en alguna cabecera más, pero tampoco entraremos en tanto detalle. La respuesta sería: HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Set-Cookie: JSESSIONID=304CE17CCEB55869A606CE06271F915B; Content-Type: text/html;charset=ISO-8859-1 Content-Length: 314 Date: Sun, 08 Jun 2008 09:27:24 GMT <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <TITLE>Calculadora</TITLE> </HEAD> <BODY> El resultado de la suma de 45 y 21 es: 66 </BODY> </HTML> Como vemos, es igual que la del ejemplo anterior, ya que lo que nos devuelve el servidor si le pedimos lo mismo (aunque sea por dos métodos distintos) siempre será lo mismo. Los parámetros que se envían en una petición, pueden ser cualquier carácter alfanumérico ASCII, incluyendo los caracteres: . (punto), - (guion), * (asterisco) y _ (subrayado). Cualquier otro tipo de carácter ha de ir codificado: El espacio en blanco con el carácter + (suma). Cualquier otro carácter con un valor hexadecimal de la forma %xx. Por ejemplo, la ñ (eñe), con %F1. En Internet están las tablas de conversión y el propio lenguaje Java 50 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML tiene métodos para decodificar y decodificar caracteres de una URL en la clase java.net.Encoder y java.net.Decoder Es importante tener presente, que si se usa el método GET para el envío de dichos parámetros, es responsabilidad del desarrollador la codificación. Por el contrario, si se usa el método POST, la codificación se realiza de forma automática. No obstante, en los casos en los que el cliente HTTP sea un navegador, como ocurrirá la mayoría de las veces en este curso, es el propio navegador el que se encarga de dicha codificación automáticamente. 3.2.3 Sniffer HTTP En principio, toda esta comunicación HTTP suele ocurrir sin un conocimiento muy profundo por parte del desarrollador de aplicaciones Java EE. Pero como iremos viendo en las sucesivas unidades, si que hay conceptos de los tratados hasta este momento en esta unidad que son imprescindibles de conocer. E incluso, en muchas ocasiones, para depurar un problema de nuestras aplicaciones, será imprescindible analizar los mensajes HTTP que fluyen entre el cliente (por ejemplo, un navegador) y nuestra aplicación Java EE servidora. Ahora, ¿y cómo podemos ver los mensajes HTTP que viajan por la red entre el cliente y el servidor? Para esto existen unas herramientas habituales en el mundo de las redes de comunicaciones que se llaman “sniffers”, que se encargan de monitorizar el tráfico de red. El funcionamiento habitual, consiste en ponerse entre medias del cliente y del servidor, y registrar todo el tráfico que fluye: tanto peticiones como respuestas. La herramienta de desarrollo que utilizamos en este curso, Eclipse IDE for Java EE Developers, incluye un sniffer denominado TCP/IP Monitor. Para acceder a él, ir a las opciones de menú “Window” -> “Show view” -> “Other…”: 51 Seleccionar la vista de “TCP/IP Monitor” de la carpeta “Debug”: 52 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML Se abrirá el TCP/IP Monitor en una pestaña más: En sus propiedades, añadimos una configuración mediante el botón “Add…”, estableciendo el puerto por el que escucha, y a qué dirección y puerto debe renviar todo el tráfico. En nuestro caso, si monitorizamos el tráfico con el Servidor de Aplicaciones Tomcat que tenemos en el propio Eclipse IDE, podríamos decirle que escuche en el puerto 80 (por ejemplo) y que renvíe a nuestra misma máquina al puerto 8080 (puerto por donde escucha por defecto el Servidor de Aplicaciones Tomcat): Por último, lo arrancamos mediante el botón “Start”. Ahora solo queda, realizar peticiones desde nuestro navegador al servidor, pero en vez de hacerlo directamente al puerto 8080, lo haremos a través del puerto 80 para que el sniffer intercepte las comunicaciones y las renvíe. Así, escucharemos en el puerto 80 de nuestra 53 máquina y lo renviaremos al 8080 del servidor de aplicaciones (que en este ejemplo, también está en nuestra máquina): De momento, no hay que preocuparse mucho por su funcionamiento. Ya tendremos ocasión de probarlo con las primeras aplicaciones Java EE que desarrollemos en la siguiente unidad. 3.3 El lenguaje HTML El HTML (HiperText Markup Languaje) es el lenguaje utilizado para crear documentos web (o lo que comúnmente se denominan páginas web). Básicamente se trata de un lenguaje de etiquetado, que define la estructura y el contenido de un documento a ser compartido y accedido en Internet a través de un navegador web. Junto con el protocolo HTTP son la base sobre la que se construyó la red de redes conocida como Internet. Este lenguaje está estandarizado bajo la supervisión del W3C. En el momento de la redacción de este manual, la última versión del lenguaje HTML es la 4.01: http://www.w3.org/TR/html4/ aunque la siguiente versión, 5.0, está en proceso de definición: http://www.w3.org/TR/html5/ No obstante, el lenguaje HTML requeriría un manual dedicado para cubrir todas sus características y posibilidades. En nuestro caso, al igual que hemos hecho con el protocolo HTTP, solo haremos un breve recorrido introductorio para conocer lo imprescindible que nos permita desarrollar aplicaciones Java EE. 54 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML Para profundizar en el lenguaje HTML recomendamos el curso de Aula Mentor HTML. Adicionalmente, junto con el lenguaje HTML, han ido surgiendo otras tecnologías y lenguajes para dotar a las páginas web de cierto dinamismo y potencia. Algunas de ellas son el lenguaje JavaScript, tecnologías AJAX, etc… que no cubriremos en este curso. Nuevamente, en la oferta de cursos de Aula Mentor, los hay orientados a estas tecnologías. 3.3.1 La etiqueta HTML Como ya hemos dicho, el lenguaje HTML está orientado a etiquetas. Una etiqueta no es más que un elemento que define qué se debe hacer o cómo se debe presentar la información que viene a continuación de ella dentro de una página web. Normalmente, las etiquetas se utilizan a pares, es decir, etiqueta de inicio de definición y etiqueta de final de definición. El formato es el siguiente: <nombre_etiqueta [atributo1="valor1", atributo2="valor2",...]> [contenido] [</nombre_etiqueta> ] Como podemos observar, las etiquetas van definidas entre los caracteres < y >. Y para diferenciar cuándo se trata de una etiqueta de apertura o de cierre, en la de cierre se utiliza el carácter / después del carácter <. Entre la pareja de etiquetas, se añade el contenido que se verá afectado por el significado de dichas etiquetas. Adicionalmente, las etiquetas pueden permitir atributos que complementan el significado y comportamiento de la etiqueta. Veámoslo con un ejemplo rápido: <FONT size="3" color="red"> Hola mundo </FONT> Este ejemplo es un fragmento de una página o documento HTML. Y en él, podemos observar fácilmente, que se trata del uso de la etiqueta FONT. En la primera línea vemos el uso de la etiqueta de inicio <FONT>, y en la tercera línea la etiqueta de final </FONT>. El contenido que se va a ver afectado por el significado y comportamiento de la etiqueta es el texto Hola mundo de la segunda línea. Adicionalmente, podemos observar que la etiqueta de inicio incluye una serie de atributos, como son ‘size’ (tamaño) y ‘color’. Una etiqueta de final, nunca lleva atributos. El resultado visual en un navegador web de una página web, que incluyera este fragmento sería este: 55 3.3.2 La página HTML Una vez visto la base del lenguaje HTML (las etiquetas), vamos a centrarnos en la página o documento HTML (también nombrada página web). Una página o documento HTML, es un fichero de texto con extensión *.htm o *.html que contiene una información formateada mediante el lenguaje HTML. Aunque los navegadores web, desde siempre han sido muy permisivos con las páginas HTML mal formateadas (etiquetas de final no añadidas, estructura de la página errónea, etc…) vamos a intentar crearlas siempre correctamente. La estructura mínima de una página HTML sintácticamente correcta, es la siguiente: <!DOCTYPE HTML PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN"> <HTML> <HEAD> <TITLE>Mi primer HTML</TITLE> </HEAD> <BODY> Mi primer ejercicio </BODY> </HTML> Este código HTML debería ir salvado en un fichero de extensión *.html y al abrirlo en un navegador, veríamos algo así: La primera línea es un tanto especial. Se trata de una etiqueta de descripción técnica del fichero HTML donde especifica la versión de HTML utilizada, entre otras cosas. 56 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML Pero lo realmente importante viene después, y es, la estructura de una página HTML. Toda página HTML debe ir siempre definida por la etiqueta HTML. Una página HTML se divide en dos secciones distintas: La cabecera, definida por la etiqueta HEAD. El cuerpo, definido por la etiqueta BODY. En la cabecera de una página HTML se añadirá información relacionada con la página, como por ejemplo su título (definido por la etiqueta TITLE), mientras que en el cuerpo, irá el contenido de la página como tal. En nuestro caso, simplemente va el texto Mi primer ejercicio. Como se puede observar en la imagen de ejemplo, los navegadores web normalmente muestran el título de la página HTML en la pestaña que está mostrando dicha página (u otros, en la barra de título del navegador). Otro contenido típico de la cabecera de una página HTML, es información específica para algunos buscadores de Internet, que les ayuda a indexar y categorizar la página. 3.3.3 El formulario HTML Como ya hemos comentado, este curso no tiene como objetivo el lenguaje HTML sino la programación de aplicaciones Java EE. Por ello, no vamos a enumerar y explicar las decenas de etiquetas existentes en el lenguaje HTML, Solo nos vamos a centrarnos en un subconjunto que si son imprescindibles para interactuar con aplicaciones Java EE servidoras que son las relacionadas con los formularios HTML. Un formulario es la presentación visual de entrada de datos en una página HTML para ser enviados a una aplicación servidora. La etiqueta que lo define es FORM. Dicha etiqueta puede contener distintos atributos, algunos obligatorios y otros opcionales. Los dos atributos obligatorios son: Method: El método HTTP que se va a utilizar al enviar al servidor la información recogida en el formulario. Action: El recurso o componente del servidor al que se le va a enviar la información. Otros atributos posibles son: ‘name’, ‘target’, ‘id’, etc… Ejemplo: <FORM method=”POST” action=”/EjemploWeb/EjemploServlet”> </FORM> 57 Este formulario va a enviar a un Java Servlet del servidor mediante el método HTTP POST toda la información que contenga. De momento, tal cual está en el ejemplo, no contiene ninguna información. Un formulario puede contener distintos elementos para recoger información del usuario: Campos de texto. Áreas de texto. Checkboxes. Radiobuttons. Listas y combos. Botones. Campos ocultos. Veamos uno por uno. 3.3.3.1 Campos de texto Un campo de texto se define mediante la etiqueta INPUT y su atributo ‘type’ con el valor ‘text’. Admite otros atributos como son: Name: el nombre del campo y por tanto, nombre del parámetro que viajará al servidor. Value: valor que debe aparecer por defecto si así se desea. Size: tamaño del campo en la pantalla desde un punto de vista visual. Maxlength: número máximo de caracteres que se pueden introducir en este campo. Disabled: no tiene un valor, sino que su sola presencia significa que el campo está deshabilitado. Esta etiqueta no requiere su homónima de final. Ejemplo: <FORM method=”POST” action=“/EjemploWeb/EjemploServlet”> Introduzca su nombre: <INPUT type=”text” name=”nombre” maxlength=”20”> </FORM> El navegador mostraría algo así: 58 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML Existe un caso particular de campo de texto, que es el utilizado para introducir información secreta (como por ejemplo una contraseña) donde no se quiere que se pueda leer la información tecleada. Se define igual que el campo de texto con la diferencia de que el atributo ‘type” debe tener el valor ‘password’. Ejemplo: <FORM method=”POST” action=“/EjemploWeb/EjemploServlet”> Introduzca su contraseña: <INPUT type=”password” name=”contrasena” maxlength=”20”> </FORM> El navegador mostraría algo así: 3.3.3.2 Áreas de texto Un área de texto se define mediante la etiqueta TEXTAREA. Admite los siguientes atributos: Name: el nombre del campo y por tanto, nombre del parámetro que viajará al servidor. Cols: el número de columnas visible. Rows: el número de filas visible. Readonly: no tiene un valor, sino que su sola presencia significa que el campo es de solo lectura. Disabled: no tiene un valor, sino que su sola presencia significa que el campo está deshabilitado. 59 Esta etiqueta si requiere su homónima de final. Y el contenido que queramos que aparezca por defecto en ella si así lo deseamos, debe ir entre ambas etiquetas. Ejemplo: <FORM method=”POST” action=“/EjemploWeb/EjemploServlet”> Introduzca sus comentarios: <BR> <TEXTAREA name=”comentarios” cols=”10” rows=”20”> </TEXTAREA> </FORM> El navegador mostraría algo así: 3.3.3.3 Checkboxes Una checkbox se define mediante la etiqueta INPUT y su atributo ‘type’ con el valor ‘checkbox’. Admite otros atributos como son: Name: el nombre del campo y por tanto, nombre del parámetro que viajará al servidor. Value: el valor del campo en caso de estar seleccionado. Checked: no tiene un valor, sino que su sola presencia significa que la checkbox está seleccionada por defecto. Disabled: no tiene un valor, sino que su sola presencia significa que el campo está deshabilitado. Esta etiqueta no requiere su homónima de final. Ejemplo: <FORM method=”POST” action=“/EjemploWeb/EjemploServlet”> Seleccione sus deportes favoritos: <BR> Fútbol: <INPUT type="checkbox" name="futbol" value="true" checked> <BR> 60 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML Baloncesto: <INPUT type="checkbox" name="baloncesto" value="true"> <BR> Petanca: <INPUT type="checkbox" name="petanca" value="true"> </FORM> El navegador mostraría algo así: 3.3.3.4 Radiobuttons Un radiobutton se define mediante la etiqueta INPUT y su atributo ‘type’ con el valor ‘radio’. Admite otros atributos como son: Name: el nombre del campo y por tanto, nombre del parámetro que viajará al servidor. Si varios radiobuttons comparten el mismo nombre, entonces forman un grupo donde solo uno de ellos puede estar seleccionado. Value: el valor del campo en caso de estar seleccionado. Checked: no tiene un valor, sino que su sola presencia significa que el radiobutton está seleccionada por defecto. Disabled: no tiene un valor, sino que su sola presencia significa que el campo está deshabilitado. Esta etiqueta no requiere su homónima de final. Ejemplo: <FORM method=”POST” action=“/EjemploWeb/EjemploServlet”> Seleccione su sexo: <BR> <INPUT type="radio" name="sexo" value="tipo1" checked> Hombre <BR> <INPUT type="radio" name="sexo" value="tipo2"> Mujer <BR> </FORM> El navegador mostraría algo así: 61 3.3.3.5 Listas y Combos Tanto las listas como los combos se definen mediante la etiqueta SELECT. Admite los siguientes atributos: Name: el nombre del campo y por tanto, nombre del parámetro que viajará al servidor. Size: especifica el número de valores visible. Si solo es visible un valor, se mostrará como un Combo. Si son visibles dos o más, se mostrará como una lista. Multiple: no tiene un valor, sino que su sola presencia significa que se pueden seleccionar más de un elemento a la vez. Disabled: no tiene un valor, sino que su sola presencia significa que el campo está deshabilitado. Esta etiqueta si requiere su homónima de final. Y las distintas opciones que vayan a formar parte de la lista deberán ir definidas entre ambas mediante la etiqueta OPTION. La etiqueta OPTION admite los siguientes atributos: Value: el valor del campo en caso de estar seleccionado. Selected: no tiene un valor, sino que su sola presencia significa que esta opción está seleccionada por defecto. Esta etiqueta si requiere su homónima de final. Y el contenido que queramos que aparezca como opción debe ir entre ambas etiquetas. Ejemplo: <FORM method=”POST” action=“/EjemploWeb/EjemploServlet”> Seleccione el postre: <BR> <SELECT name="postre" size="3"> <OPTION value="op1">Fruta</OPTION> <OPTION value="op2" selected>Helado</OPTION> <OPTION value="op3">Cuajada</OPTION> <OPTION value="op4">Tarta</OPTION> </SELECT> </FORM> 62 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML El navegador mostraría algo así: 3.3.3.6 Botones Un botón se define mediante la etiqueta INPUT. Y dependiendo del valor que tenga su atributo “type” será de un tipo u otro: Button: se trata de un botón genérico que por sí solo no hace nada. Hay que acompañarlo de JavaScript, por ejemplo, para que realice alguna operativa. Submit: se trata de un botón que al ser pulsado envía toda la información recogida en el formulario al recurso del servidor identificado en el atributo “action” del formulario mediante el método HTTP especificado en el atributo “method” del formulario. Reset: se trata de un botón que al ser pulsado borra toda la información recogida en el formulario. Admite otros atributos como son: Name: el nombre del botón, que puede ser útil cuando se programa con JavaScript en la página HTML. Value: el texto que aparece escrito en el botón. Disabled: no tiene un valor, sino que su sola presencia significa que el botón está deshabilitado. Esta etiqueta no requiere su homónima de final. Ejemplo: <FORM method=”POST” action=“/EjemploWeb/EjemploServlet”> <INPUT type="submit" value="Enviar"> <INPUT type="reset" value="Borrar"> </FORM> El navegador mostraría algo así: 63 3.3.3.7 Campos ocultos Existen ocasiones, en las que nos interesará que como parte del formulario se envíe información no visible para el usuario pero necesaria para la lógica de nuestra aplicación. Dichos campos se definen mediante la etiqueta INPUT y su atributo ‘type’ con el valor ‘hidden’. Admite otros atributos como son: Name: el nombre del campo y por tanto, nombre del parámetro que viajará al servidor. Value: el valor del campo, no modificable por el usuario. Esta etiqueta no requiere su homónima de final. Ejemplo: <FORM method=”POST” action=“/EjemploWeb/EjemploServlet”> <INPUT type=”hidden” name=”usuario” value=”1234”> </FORM> Evidentemente, en el navegador no se vería nada. Pues bien, esto es todo lo que hay que conocer de HTML para poder interactuar entre el usuario y la aplicación Java EE mediante un navegador web. Cualquier otra etiqueta HTML ya sería para dar formato a la página: tablas, cabeceras, imágenes, enlaces, frames, etc… 3.4 Ensamblado y despliegue Las páginas HTML pueden desplegarse de distintas maneras. Por ejemplo, se pueden desplegar en un Servidor Web como Apache HTTP Server (http://httpd.apache.org/) o Internet Information Server (http://www.iis.net/) entre otros. Estos servidores, se limitan a servir webs estáticas basadas en páginas HTML y sus recursos: imágenes, hojas de estilo, etc... Pero como vimos en la primera unidad de este curso, también se pueden desplegar como parte de una aplicación Java EE (dentro de un módulo web) en un Servidor de Aplicaciones Java EE. 64 MÓDULO A – Unidad 3: El protocolo HTTP y el lenguaje HTML Esta segunda opción será la que estudiaremos y utilizaremos a lo largo del curso, pero a partir de la siguiente unidad. De momento, en las actividades de esta unidad, nos limitaremos a crear las páginas HTML en el sistema de archivos local y abrirlas con el navegador accediendo al disco duro (no escribiendo ninguna URL de un servidor). Por ejemplo, con Mozilla Firefox 10: O con Internet Explorer 9: 65 66 PARA RECORDAR El modelo de referencia OSI representa los sistemas de comunicación desde un punto de vista teórico en 7 capas implementado en un sistema de comunicación real como es el TCP/IP. El protocolo HTTP es el encargado de la transferencia de los recursos, de las aplicaciones web, que componen la red. Estos recursos son identificados de forma unívoca mediante las URLs, que tienen el siguiente formato: Protocolo://Servidor:Puerto/Path/Recurso Las conversaciones HTTP, de petición – respuesta se componen de: La petición contiene un método HTTP (GET y POST.), un recurso o URL y los parámetros o argumentos. Una respuesta contiene un código de estado o resultado, tipo del contenido de la respuesta y el contenido como tal. Se han estudiado los métodos GET y POST para las solicitudes de recursos estáticos y dinámicos a un servidor de aplicaciones, su formato tanto en las peticiones como en las respuestas y la utilización de sniffers para poder depurar las solicitudes. HTML es el lenguaje mediante el cual definimos los contenidos que fluyen por Internet, conocidas como páginas HTML o páginas Web. Básicamente se trata de un lenguaje de etiquetado, que define la estructura y el contenido de un documento a ser compartido y accedido en Internet a través de un navegador web. Una etiqueta es un elemento que define qué o como se debe presentar la información que viene a tras la etiqueta, dentro de una página web. Una página HTML, es un fichero de texto con extensión *.htm o *.html que contiene una información formateada mediante el lenguaje HTML. Está formada normalmente por una cabecera <HEAD> y un cuerpo <BODY>. Un formulario (FORM) es la presentación visual de entrada de datos en una página HTML para ser enviados a una aplicación servidora. Se han estudiado distintos elementos como INPUT, TEXTAREA …. 67 68 Unidad de Aprendizaje 4 JAVA SERVLETS ÍNDICE 4.1 Introducción ................................................................... 71 4.2 Mi primer Java Servlet .................................................. 72 4.3 Java Servlets .................................................................. 84 4.3.1 El ciclo de vida de un Java Servlet ..............................84 4.3.2 Petición y respuesta de un Java Servlet .....................88 4.3.3 Procesamiento de un formulario HTML ......................93 4.3.4 Empaquetado y despliegue ..........................................97 RECORDAR............................................................................. 103 MÓDULO B – Unidad 4: Java Servlets 4.1 Introducción Ya vimos en la primera unidad del curso, que los Java Servlets eran un tipo de componente Java EE que se empaquetaba en un módulo web (WAR) y se desplegaba y ejecutaba en el contenedor web de un Servidor de Aplicaciones Java EE. Adicionalmente, dentro del patrón de diseño MVC ocupaban el espacio del Controlador, como receptor/validador de peticiones, seleccionador de la lógica a ejecutar, y seleccionador de la vista a presentar como resultado. No obstante, de momento, dado que es el primer componente que vamos a estudiar, va a cubrir completamente todas las responsabilidades del MVC. Y según vayamos avanzando en el curso, le iremos quitando responsabilidades que no debieran ser del Java Servlet, para dárselas a otro tipo de componente como la JavaServer Page (JSP). Repetimos, esto es un mal diseño y una mala práctica de programación. Pero consideramos que es imprescindible ir aprendiendo poco a poco. Por tanto, este diseño es temporal mientras avanzamos en las sucesivas unidades del curso. 71 Por último, desde un punto de vista del lenguaje de programación Java, un Java Servlet no es más que una clase Java que implementa una determinada interface definida en las especificaciones. 4.2 Mi primer Java Servlet Antes de entrar a estudiar en profundidad los Java Servlets, vamos a guiar los pasos para desarrollar nuestro primer Java Servlet con Eclipse IDE for Java EE Developers y desplegarlo en Apache Tomcat. Es requisito indispensable, haber seguido los pasos de instalación y configuración de ambas dos herramientas detallados en la unidad 2 de este curso. Estos mismos pasos nos servirán para los sucesivos ejemplos y actividades del resto del curso. Arrancamos Eclipse IDE for Java EE Developers y mediante el las opciones de menú “File” -> “New” -> “Dynamic Web Project”: Creamos la estructura de desarrollo de un módulo web (WAR). Como nombre del proyecto, pondremos “HolaMundoWeb”, y nos aseguraremos que seleccionamos “Apache Tomcat v7.0” 72 MÓDULO B – Unidad 4: Java Servlets como entrono de ejecución y “3.0” como el nivel de especificaciones Java EE (en cuanto a contenedor web) que queremos utilizar. Pulsamos el botón “Next” dos veces: En la última ventana del asistente, antes de pulsar el botón “Finish”, nos aseguramos de marcar la opción “Generate web.xml deployment descriptor”: 73 La estructura del proyecto creado debería ser algo así: 74 MÓDULO B – Unidad 4: Java Servlets En el directorio /Java Resources/src es donde iremos colocando el código fuente de nuestras clases Java. En el directorio /WebContent es donde iremos colocando los recursos web, como por ejemplo, las páginas HTML, las JavaServer Pages (JSP), imágenes, etc… Y el fichero web.xml de /WebContent/WEB-INF es el descriptor de despliegue del módulo web (en nuestro caso, del proyecto HolaMundoWeb). Ahora crearemos un Java Servlet. Botón derecho sobre el nuevo proyecto -> “New” -> “Class”: 75 En la siguiente ventana, rellenamos el nombre del paquete Java (Package) con “es.aulamentor”, el nombre de la clase Java (Name) con “HolaMundoServlet” y la clase padre (Superclass) con “javax.servlet.http.HttpServlet”. Nota: No incluir las comillas en los nombres mencionados Y pulsamos el botón “Finish”: 76 MÓDULO B – Unidad 4: Java Servlets Escribimos el siguiente código Java en la clase creada y salvamos: package es.aulamentor; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class HolaMundoServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=UTF‐8"); PrintWriter out = resp.getWriter(); 77 out.println("<!DOCTYPE HTML PUBLIC \"‐//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD>"); out.println(" <TITLE>Hola Mundo!</TITLE>"); out.println(" </HEAD>"); out.println(" <BODY>"); out.println(" Hola Mundo!"); out.println(" </BODY>"); out.println("</HTML>"); } } No nos preocupemos en este momento por no entender el código. De momento, simplemente queremos mostrar los pasos para el desarrollo y despliegue de un Java Servlet. En los sucesivos apartados iremos entendiendo el por qué de cada línea. A continuación, editaremos el descriptor de despliegue del módulo web, el fichero web.xml de /WebContent/WEB-INF y añadiremos este código XML salvando al final el fichero: <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <servlet> <servlet‐name>HolaMundoServlet</servlet‐name> <servlet‐class>es.aulamentor.HolaMundoServlet</servlet‐class> </servlet> <servlet‐mapping> <servlet‐name>HolaMundoServlet</servlet‐name> <url‐pattern>/HolaMundoServlet</url‐pattern> </servlet‐mapping> </web‐app> Ya solo queda, probar la aplicación. Para ello, pulsamos el botón derecho sobre la clase del Java Servlet -> “Run As” -> “Run on Server”: 78 MÓDULO B – Unidad 4: Java Servlets En la siguiente ventana, seleccionar “Tomcat v7.0 Server at localhost” y pulsar el botón “Finish”: 79 En la vista de “Console” veremos las trazas que va dejando el Servidor de Aplicaciones Java EE al arrancar, cómo despliega la aplicación y automáticamente abre un navegador interno de Eclipse IDE con la URL: http://localhost:8080/HolaMundoWeb/HolaMundoServlet donde se ve el resultado de nuestro primer Java Servlet: 80 MÓDULO B – Unidad 4: Java Servlets Perfectamente podríamos utilizar otro navegador web (tipo Mozilla Firefox o Internet Explorer) y escribir la misma URL. El comportamiento debería ser el mismo. Para abrir el navegador interno de Eclipse IDE a mano, utilizar el botón de la barra de herramientas: Otra opción también válida habría sido utilizar el asistente de Java Servlets directamente en vez del asistente de una clase Java convencional, pero preferíamos que se viera un ejemplo “a mano”. Esta opción, habría sido con las opciones de menú “New” -> “Servlet”: 81 Este asistente, añade una anotación especial sobre la clase Java en el código fuente de manera que no es necesario añadir nada al descriptor de despliegue. Como ya comentamos en la primera unidad, esta era una novedad de la última versión de la especificación Java EE. En este curso, comentaremos ambas dos opciones. Ya solo nos queda desinstalar la aplicación mediante la opción “Add and Remove…”: 82 MÓDULO B – Unidad 4: Java Servlets Y apagar el servidor mediante la opción “Stop”: 83 4.3 Java Servlets Todas las clases Java relacionadas con los Java Servlets, están en los paquetes: Los javax.servet javax.servlet.http Java Servlets fueron concebidos para ser independientes del protocolo de comunicaciones. Pero la realidad ha sido, que el 99% de su uso está relacionado con el protocolo HTTP (redes Internet e Intranets). De ahí, que existan clases en el API que nos ayudan a gestionar las peculiaridades de dicho protocolo en el segundo paquete mencionado: javax.servlet.http 4.3.1 El ciclo de vida de un Java Servlet Un Java Servlet tiene definido un ciclo de vida, forzado por la interface javax.servlet.Servlet, por tanto, siempre que queramos implementar un Java Servlet, nuestra clase deberá 84 MÓDULO B – Unidad 4: Java Servlets implementar dicho interface. Es una especie de contrato entre el Java Servlet y el contenedor web del Servidor de Aplicaciones Java EE para poder funcionar. Dicho ciclo de vida tiene los siguientes estados: Cuando se recibe una petición de la ejecución de un Java Servlet en el Servidor de Aplicaciones Java EE y este no ha sido invocado nunca, el contenedor web instancia la clase del Java Servlet (llama a su constructor) y acto seguido invoca el método: public void init(ServletConfig config); Dicho método, solo se ejecuta una vez en la vida del Java Servlet y se suele utilizar para llevar a cabo todas las labores de inicialización que necesitemos para su posterior ejecución. Como se puede observar, recibe como parámetro una instancia de la clase ServletConfig, que representa información de configuración que el administrador del Servidor de Aplicaciones Java EE haya podido añadir. Veremos este método en más detalle en los siguientes apartados. Una vez está ya inicializado, entonces invoca el método: public void service(ServletRequest request, ServletResponse response); Dicho método se ejecuta cada vez que se recibe una petición para este java Servlet. En su implementación, colocaremos la lógica para la que se creó dicho Java Servlet. Como se puede observar, recibe como parámetros un par de instancias de las clases ServletRequest y ServletResponse. Ambos dos parámetros representan la petición (la usaremos para entender qué nos están pidiendo) y la respuesta (la utilizaremos para devolver el resultado de la ejecución del java Servlet al peticionario). Si por alguna razón el contenedor web necesita eliminar al Java Servlet (se detiene el Servidor de Aplicaciones Java EE, se queda sin recursos de memoria y necesita liberar espacio, etc…), entonces invoca el método: public void destroy(); Dicho método solo se ejecuta una vez en la vida del java Servlet y se suele utilizar para liberar todos aquellos recursos que pudiéramos estar utilizando, para dejar el sistema lo mas limpio posible. Lo que ocurre, es que el API ya tiene una serie de clases que no solo implementan por defecto el ciclo completo de vida de un Java Servlet, sino que como decíamos, también nos ayuda con los temas relacionados con el protocolo HTTP. Así, tenemos la clase javax.servlet.http.HttpServlet que además de implementar los métodos comentados anteriormente (es decir, implementar el interface javax.servlet.Servlet), añade otros más potentes que nos dan valor añadido al trabajar con el protocolo HTTP. 85 Por ejemplo, si recordamos de la unidad anterior donde hablábamos del protocolo HTTP, comentábamos que existían distintos métodos utilizables en la petición: GET, POST, PUT, etc… Pues esta clase, implementa un método Java por cada método HTTP existente, de forma, qué él ya se encarga de recibir la petición HTTP, examinarla, e invocar el método Java correspondiente al método HTTP utilizado, así como darnos acceso a un par de objetos que representan tanto la petición como la respuesta HTTP. Los más habituales que utilizaremos nosotros son: protected void doGet(HttpServletRequest request, HttpServletResponse response); protected void doPost(HttpServletRequest request, HttpServletResponse response); Dichos métodos normalmente los sobrescribiremos en nuestro Java Servlet para incluir nuestra lógica particular. Veamos un ejemplo de un Java Servlet que simplemente muestra por la consola del Servidor de Aplicaciones Java EE trazas del ciclo de vida: package es.aulamentor; import java.io.IOException; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class CicloDeVidaServlet extends HttpServlet { public CicloDeVidaServlet() { super(); System.out.println("CicloDeVidaServlet()"); } public void init(ServletConfig config) throws ServletException { System.out.println("init()"); } public void destroy() { System.out.println("destroy()"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { System.out.println("doGet()"); } } El editor de despliegue tendría algo así: 86 MÓDULO B – Unidad 4: Java Servlets <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <servlet> <servlet‐name>CicloDeVidaServlet</servlet‐name> <servlet‐class>es.aulamentor.CicloDeVidaServlet</servlet‐class> </servlet> <servlet‐mapping> <servlet‐name>CicloDeVidaServlet</servlet‐name> <url‐pattern>/CicloDeVidaServlet</url‐pattern> </servlet‐mapping> </web‐app> Al invocar varias veces a este Java Servlet desde un navegador, y después parar el Servidor de Aplicaciones Java EE, deberíamos ver algo así: Como podemos ver, en la consola salen en negro las trazas que dejamos desde nuestro código: primero el constructor, seguido del init(), seguido de tres doGet() porque ese fue el 87 número de veces que invocamos seguidas el Java Servlet, y por último, al apagar el Servidor de Aplicaciones Java EE el destroy(). En el navegador, evidentemente no se ve nada, puesto que no devolvimos ningún resultado en el código del Java Servlet. 4.3.2 Petición y respuesta de un Java Servlet La invocación de un Java Servlet independientemente del método HTTP que se utilice, siempre recibe dos parámetros: javax.servlet.http.HttpServletRequest y javax.servlet.http.HttpServletResponse. javax.servlet.http.HttpServletRequest, es un interfaz que define cómo interactuar con la información que viene en una petición HTTP, como por ejemplo, las cabeceras HTTP de la petición, los parámetros de la petición, etc… Algunos métodos son: java.util.Enumeration<java.lang.String> getParameterNames(): devuelve una colección con los nombres de todos los parámetros enviados en la petición. java.lang.String getParameter(java.lang.String name): devuelve el valor de un parámetro concreto enviado en la petición. java.lang.String[] getParameterValues(java.lang.String name): devuelve todos los valores de un parámetro concreto enviado en la petición (imaginemos un conjunto de checkboxes con el mismo nombre de un formulario). java.util.Enumeration<java.lang.String> getAttributeNames(): devuelve una colección con los nombres de todos los atributos de la petición. java.lang.Object getAttribute(java.lang.String name): devuelve el valor de un atributo concreto de la petición. java.util.Enumeration<java.lang.String> getHeaderNames(): devuelve una colección con los nombres de todas las cabeceras HTTP enviadas en la petición. java.lang.String getHeader(java.lang.String name): devuelve el valor de una cabecera HTTP concreta de la petición. java.lang.String getContextPath(): devuelve la porción de URL de la petición que corresponde al ContextPath. El ContextPath es el identificador que utiliza el Servidor de aplicaciones Java EE para diferenciar una aplicación u otra. Siempre va en la URL por delante del recurso pedido como tal. Existen muchos métodos más, algunos muy importantes para el manejo de la Sesión HTTP y de las Cookies (los veremos en la siguiente unidad), para redireccionar la petición a otro java Servlet o JavaServer Page (también los veremos en una unidad posterior) y otros 88 MÓDULO B – Unidad 4: Java Servlets muchos para acceder a más información disponible en una petición HTTP que podemos ver en el siguiente ejemplo: package es.aulamentor; import java.io.IOException; import java.util.Enumeration; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class RequestServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("ContextPath: " + request.getContextPath()); System.out.println("LocalAddr: " + request.getLocalAddr()); System.out.println("LocalName: " + request.getLocalName()); System.out.println("LocalPort: " + request.getLocalPort()); System.out.println("Method: " + request.getMethod()); System.out.println("Protocol: " + request.getProtocol()); System.out.println("QueryString :" + request.getQueryString()); System.out.println("RemoteAddr :" + request.getRemoteAddr()); System.out.println("RemoteHost :" + request.getRemoteHost()); System.out.println("RemotePort :" + request.getRemotePort()); System.out.println(); Enumeration<String> headers = request.getHeaderNames(); while(headers.hasMoreElements()) { String header = headers.nextElement(); System.out.println(header + ": " + request.getHeader(header)); } System.out.println(); Enumeration<String> parameters = request.getParameterNames(); while(parameters.hasMoreElements()) { String parameter = parameters.nextElement(); System.out.println(parameter + ": " + request.getParameter(parameter)); } } } Y el descriptor de despliegue: <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <servlet> <servlet‐name>RequestServlet</servlet‐name> <servlet‐class>es.aulamentor.RequestServlet</servlet‐class> </servlet> <servlet‐mapping> 89 <servlet‐name>RequestServlet</servlet‐name> <url‐pattern>/RequestServlet</url‐pattern> </servlet‐mapping> </web‐app> Si en el navegador utilizamos la siguiente URL para invocar el Java Servlet: http://localhost:8080/EjemplosWeb/RequestServlet?param1=valor1¶m2=valor2 Nota: Fijaros, que hemos añadido dos parámetros en la URL. En la consola del Servidor de Aplicaciones Java EE deberíamos ver algo como esto (en cada caso dependerá un poco del entorno utilizado): En el API Java EE, están detallados todos los métodos: http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html javax.servlet.http.HttpServletResponse, es un interfaz que define cómo trabajar y generar la respuesta HTTP desde el Java Servlet, como por ejemplo, acceder al stream de escritura, especificar el tipo y longitud del contenido, etc… Algunos métodos son: java.io.PrintWriter getWriter() throws java.io.IOException: nos devuelve una referencia al objeto de escritura en modo carácter de la respuesta, a través del cual escribiremos. ServletOutputStream getOutputStream() throws java.io.IOException: nos devuelve una referencia al objeto de escritura en modo byte de la respuesta, a través del cual escribiremos. La diferencia con el anterior es el modo de escritura: carácter o byte. 90 MÓDULO B – Unidad 4: Java Servlets void setContentType(java.lang.String type): nos sirve para especificar el tipo de contenido que vamos a enviar en la respuesta. Por ejemplo: text/html, text/plain, image/gif, etc… Los distintos tipos de contenido están estandarizados a través de las especificaciones MIME (Multipurpose Internet Mail Extensions). En la siguiente URL están documentados todos los posibles tipos: http://www.iana.org/assignments/media-types/index.html Recordemos de la tercera unidad de este manual, cuando hablábamos del protocolo HTTP, que esta era una información que viajaba en la cabecera de una respuesta HTTP. Los siguientes métodos, también sirven para añadir información específica de una respuesta HTTP. void setContentLength(int len): nos sirve para especificar la longitud del cuerpo de la respuesta void setStatus(int sc): nos sirve para devolver un código de estado HTTP. void sendError(int sc, java.lang.String msg) throws java.io.IOException: nos sirve para devolver un código de error HTTP junto con un mensaje descriptivo. Existen muchos métodos más, algunos muy importantes para el manejo de las Cookies (los veremos en la siguiente unidad). Veamos un ejemplo: package es.aulamentor; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ResponseServlet extends HttpServlet { private byte[] buffer = new byte[0]; public void init(ServletConfig config) throws ServletException { try { File file = new File("C:\\images\\duke.gif"); FileInputStream in = new FileInputStream(file); buffer = new byte[(int)file.length()]; in.read(buffer); in.close(); } catch(FileNotFoundException ex) 91 { ex.printStackTrace(); } catch(IOException ex) { ex.printStackTrace(); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("image/gif"); response.setContentLength(buffer.length); response.getOutputStream().write(buffer); response.setStatus(200); } } Y el descriptor de despliegue: <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <servlet> <servlet‐name>ResponseServlet</servlet‐name> <servlet‐class>es.aulamentor. ResponseServlet</servlet‐class> </servlet> <servlet‐mapping> <servlet‐name>ResponseServlet</servlet‐name> <url‐pattern>/ResponseServlet</url‐pattern> </servlet‐mapping> </web‐app> Si en el navegador utilizamos la siguiente URL para invocar el Java Servlet: http://localhost:8080/EjemplosWeb/ResponseServlet En el navegador deberíamos ver la imagen que hemos utilizado en el ejemplo En el API Java EE, están detallados todos los métodos: 92 MÓDULO B – Unidad 4: Java Servlets http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html 4.3.3 Procesamiento de un formulario HTML Hasta ahora, todos los ejemplos no han tenido interacción real con el usuario. Vamos a ver cómo sería un Java Servlet que procese una información recogida a través de un formulario HTML. Por ejemplo, una calculadora muy sencilla que simplemente sume dos números. Tendremos la siguiente página HTML: <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Calculadora</TITLE> </HEAD> <BODY> <FORM method="POST" action="/EjemplosWeb/SumadorServlet"> <TABLE border="0"> <TR> <TD>Primer número:</TD> <TD><INPUT name="param1"></TD> </TR> <TR> <TD>Segundo número:</TD> <TD><INPUT name="param2"></TD> </TR> <TR> <TD><INPUT type="submit" value="Sumar"></TD> </TR> </TABLE> </FORM> </BODY> </HTML> Y el siguiente Java Servlet: package es.aulamentor; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class SumadorServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { procesarPeticion(request, response); } 93 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { procesarPeticion(request, response); } private void procesarPeticion(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF‐8"); try { int param1 = Integer.parseInt(request.getParameter("param1")); int param2 = Integer.parseInt(request.getParameter("param2")); int result = param1 + param2; response.setStatus(200); PrintWriter out = response.getWriter(); out.println(""); out.println("<!DOCTYPE HTML PUBLIC \"‐//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD>"); out.println(" <TITLE>Calculadora</TITLE>"); out.println(" </HEAD>"); out.println(" <BODY>"); out.println(" El resultado de la suma es: " + result); out.println(" <BR><BR>"); out.println(" <A href=\"/EjemplosWeb/calculadora.html\">Volver atrás...</A>"); out.println(" </BODY>"); out.println("</HTML>"); } catch(NumberFormatException ex) { response.sendError(500, "Alguno de los números no contenía dígitos válidos..."); } } } Nota: Recordar que los contenidos estáticos como una página HTML se ubican dentro del directorio /WebContent. Al cargar la página en el navegador, deberíamos ver algo así: 94 MÓDULO B – Unidad 4: Java Servlets Al introducir los números 2 y 8 y pulsar el botón de Sumar, desde el navegador se lanzará una petición HTTP al Servidor de Aplicaciones Java EE pidiendo la ejecución del Java Servlet (que está en el mismo servidor que el HTML) con dichos parámetros y en el navegador, deberíamos ver algo así: En caso de que no hubiéramos introducido algún parámetro, o alguno de ellos llevase algún carácter que no fuese un dígito, deberíamos ver una página de error parecida a esta: 95 En la siguiente unidad, hablaremos un poco mas del tratamiento de errores. En este ejemplo, hemos optado por mostrar una página de error HTTP, pero podríamos perfectamente haber contestado una página HTML al estilo de la que mostraba el resultado correcto, indicando los motivos del error. Aprovechando este ejemplo, vamos a comentar otro tema interesante. En la unidad donde tratamos el protocolo HTTP, comentamos que la diferencia entre los métodos HTTP GET y POST era que uno enviaba los parámetros en la URL (GET) y otro en el cuerpo del mensaje HTTP (POST). En este ejemplo, hemos utilizado el método POST, pero fijaros lo que veríamos en la URL del navegador si en vez de POST hubiéramos utilizado GET: http://localhost:8080/EjemplosWeb/SumadorServlet?param1=2¶m2=8 Por eso comentábamos, que lo habitual cuando se utilizan formularios es emplear el método POST para evitar mostrar al usuario URLs farragosas, además de que por temas de seguridad, se intente evitar que se lea en claro una contraseña (por ejemplo). 96 MÓDULO B – Unidad 4: Java Servlets 4.3.4 Empaquetado y despliegue Como comentábamos en la primera unidad de este curso de introducción al Java EE, una aplicación Java EE está formada por un empaquetamiento de una o varias unidades conocidas con el nombre de módulos. Este empaquetamiento final era un EAR (Enterprise ARchive). Uno de los distintos tipos de módulos mencionados son los módulos Web, que contienen normalmente Java Servlets, JavaServer Pages (JSP), JavaServer Faces (JSF), contenidos estáticos como imágenes, HTMLs, CSSs… Su extensión del fichero empaquetado es WAR (Web ARchive). En este curso, nos vamos a centrar exclusivamente de este tipo de módulos, ya que intentamos cubrir solo la parte web de la programación Java EE y no el resto de aspectos que darían lugar a un curso demasiado extenso o a un segundo curso. A pesar de que las aplicaciones Java EE se empaquetan en un EAR, los Servidores de Aplicación que solo cubren parte de la especificación Java EE, como ocurre con Apache Tomcat, permiten desplegar simplemente módulos Web aislados. Este será nuestro caso durante este curso. Gráficamente, la estructura de un módulo Web empaquetado sería la siguiente: Como podemos observar, tiene bastante parecido a la estructura del proyecto web dentro de Eclipse IDE for Java EE Developers aunque no es igual. En principio, el raíz del fichero 97 WAR correspondería al directorio /WebContent y el directorio /classes del fichero WAR correspondería al directorio /build/classes. Ahora bien, normalmente no nos preocuparemos mucho de la generación de un módulo Web u otro tipo de módulo, porque son los IDEs los que nos van a ayudar a generarlo. Por ejemplo, en nuestro caso, con Eclipse IDE for Java EE Developers basta con seleccionar el proyecto, pulsar el botón derecho del ratón y seleccionar las opciones “Export” -> “WAR file”: 98 MÓDULO B – Unidad 4: Java Servlets En la siguiente ventana, nos aseguramos que el proyecto seleccionado es el correcto, y decidimos donde y con que nombre se va a generar el fichero WAR, por ejemplo, “C:\EjemplosWeb.war”. Pulsar el botón “Finish”: Si vamos a una sesión de DOS, mediante el comando de la JDK jar.exe (jar tvf nombreFichero.war), podemos ver su contenido y confirmar que la estructura generada es exactamente la de la especificación Java EE: 99 Este fichero WAR, sería el que un administrador de Servidores de Aplicación Java EE desplegaría y configuraría para que la aplicación estuviese disponible y dando servicio. Pero en nuestro caso, simplemente añadiremos o quitaremos aplicaciones en el Apache Tomcat de Eclipse IDE for Java EE Developers mediante el interfaz de usuario: botón derecho del ratón y la opción “Add and Remove…”, o directamente sobre el componente y “Run As” -> “Run on Server”. Nos queda no obstante hablar un poco sobre el descriptor de despliegue. Como ya adelantábamos en la primera unidad, se trata de un fichero XML donde se describe el contenido y posible comportamiento del contenido del módulo. En el caso concreto de los módulos Web, su descriptor de despliegue es el fichero web.xml A lo largo de esta unidad, hemos visto en los ejemplos algunas etiquetas, pero ahora hablaremos un poco más despacio de algunas de ellas: <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <servlet> <servlet‐name>SumadorServlet</servlet‐name> <servlet‐class>es.aulamentor.SumadorServlet</servlet‐class> </servlet> <servlet‐mapping> <servlet‐name>SumadorServlet</servlet‐name> <url‐pattern>/SumadorServlet</url‐pattern> </servlet‐mapping> </web‐app> La primera línea es obligatoria, y simplemente indica que el fichero es XML 1.0 Después, viene el bloque “web-app” que es el que delimita el contenido del descriptor de despliegue del módulo Web. En la etiqueta de inicio, se añaden una serie de atributos entre los cuales está la ubicación del esquema XML que contiene la especificación utilizada: http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd Los bloques “servlet” y “servlet-mapping” son más interesantes. Sirven para registrar los Java Servlets que hay en el módulo Web, y especificar la URL para cada uno de ellos. Por ejemplo, en el descriptor de despliegue que hemos usado, decimos que hay un Java Servlet que llamamos SumadorServlet que está implementado en la clase Java es.aulamentor.SumadorServlet y que su URL es /SumadorServlet. Si no tuviéramos esos dos bloques en el descriptor de despliegue, el Servidor de Aplicaciones Java EE nos devolvería un error HTTP 404 que significaba que el recurso solicitado no existe. Veamos el error visualmente: 100 MÓDULO B – Unidad 4: Java Servlets Aunque el Java Servlet vaya en el módulo Web, si no está especificado en el descriptor de despliegue, el Servidor de Aplicaciones Java EE no lo sabe identificar. Existen muchas más etiquetas a utilizar en un descriptor de despliegue de un módulo Web, pero las iremos viendo a lo largo del curso según las vayamos necesitando. De momento nos quedamos con la general “web-app” y las necesarias para registrar e identificar un Java Servlet. No obstante, muchos recordaréis que en la primera unidad comentábamos que con la última especificación Java EE, había aparecido la opción de obviar los descriptores de despliegue a cambio de anotar el código fuente con unas anotaciones especiales. Esta es otra opción, en cuyo caso no hace falta ni tener físicamente el fichero web.xml Una anotación se identifica precediéndola del carácter @ (arroba) y acompaña a la definición de una clase, de un atributo o de un método Java. Al igual que ocurre con las etiquetas del descriptor de despliegue, hay muchas anotaciones, pero las iremos viendo poco a poco a lo largo del curso. De momento, para sustituir a los bloques “servlet” y “servlet-mapping” que como hemos visto registran y definen un Java Servlet, existe la anotación: @WebServlet Que puede añadir atributos entre paréntesis para complementar si definición. En nuestro caso, el código fuente Java de nuestro Java Servlet sería algo así: .... import javax.servlet.annotation.WebServlet; .... @WebServlet(name="SumadorServlet", urlPatterns={"/SumadorServlet"}) public class SumadorServlet extends HttpServlet { .... } Mediante la anotación, al desplegar el módulo Web en el Servidor de Aplicaciones Java EE, estamos registrando un Java Servlet que se llama SumadorServlet, cuya URL es /SumadorServlet. 101 Todos los posibles atributos de la anotación @WebServlet están en el API: http://docs.oracle.com/javaee/6/api/javax/servlet/annotation/WebServlet.html Si nuestro Java Servlet está desarrollado haciendo uso de la anotación, entonces no hace falta que lo registremos en el descriptor de despliegue. Incluso, si el descriptor de despliegue solo llevaba esa información, podemos prescindir de él. Ambas dos opciones pueden convivir en un mismo módulo Web, es decir, podemos describir algunas cosas en el descriptor de despliegue, y otras mediante anotaciones, aunque normalmente no es una buena práctica el mezclar las dos opciones. Normalmente se utiliza una u otra. ¿Y cuál es mejor utilizar? Pues depende. Normalmente los desarrolladores prefieren las anotaciones porque les es más rápido el desarrollo, pero los administradores de sistemas prefieren los descriptores de despliegue porque les permite entender el contenido de un módulo web sin necesidad de saber interpretar código fuente (código fuente que en ocasiones no tienen a su disposición debido a que solo reciben los bytecodes de las clases Java). A lo largo del curso, iremos explicando siempre ambas dos opciones. 102 RECORDAR Los Java Servlets son un tipo de componente Java EE que se empaquetan en un módulo web (WAR) y se despliegan y ejecutan en el contenedor web de un Servidor de Aplicaciones Java EE. Adicionalmente, dentro del patrón de diseño MVC ocupan el espacio del Controlador, como receptor/validador de peticiones, seleccionador de la lógica a ejecutar, y seleccionador de la vista a presentar como resultado Los Java Servlets siempre deben de implementar la interface javax.servlet.Servlet o heredar de la clase javax.servlet.http.HttpServlet. El ciclo de vida viene determinado por los siguientes métodos init: se ejecuta una vez en la vida del Java Servlet y se suele utilizar para llevar a cabo todas las labores de inicialización que necesitemos para su posterior ejecución. service: se ejecuta cada vez que se recibe una petición para el Java Servlet. En su implementación, se coloca la lógica para la que se creó. Si se ha heredado de la clase javax.servlet.http.HttpServlet, se utilizan los métodos doGet o doPost. destroy: se ejecuta una vez en la vida del Java Servlet y se suele utilizar para liberar todos aquellos recursos que pudiéramos haber utilizado, con el afán de dejar el sistema lo más limpio posible. Los objetos de los tipos javax.servlet.http.HttpServletRequest y javax.servlet.http.HttpServletResponse. que reciben los métodos doGet y doPost, se utilizan para identificar la petición realizada y la respuesta dada. Contienen métodos para obtener de la petición parámetros, atributos, cabeceras … poner en la respuesta la respuesta en si misma, su longitud, el estado de la petición … 103 Hemos aprendido que la principal diferencia entre las peticiones GET y POST es que en las primeras el paso de parámetros es apreciable en la URL. Para poder utilizar el POST, se utilizan los formularios (FORM) de HTML. Para poder desplegar una aplicación web, se puede realizar mediante un EAR o un WAR (dependiendo del servidor de aplicaciones). El descriptor de despliegue web.xml, aunque no es obligatorio si se utilizan anotaciones, conviene que exista para poder determinar de una manera sencilla que es lo que se va a desplegar y el comportamiento asociado. 104 Unidad de Aprendizaje 5 JAVASERVER PAGES ÍNDICE 5.1 Introducción ................................................................. 107 5.2 Mi primer JavaServer Page ........................................ 108 5.3 JavaServer Pages ........................................................ 118 5.3.1 Etiqueta de comentario................................................119 5.3.2 Etiqueta de directiva.....................................................119 5.3.3 Etiqueta de declaración ...............................................120 5.3.4 Etiqueta de scriptlets ...................................................121 5.3.5 Etiqueta de expresión ..................................................122 5.3.6 Ejemplo con todas las etiquetas ................................123 5.4 Otros conceptos .......................................................... 127 5.4.1 El ciclo de vida de un JavaServer Page ....................127 5.4.2 Objetos implícitos de un JavaServer Page...............128 5.4.3 Empaquetado y despliegue ........................................129 PARA RECORDAR.................................................................. 134 MÓDULO B – Unidad 5: JavaServer Pages 5.1 Introducción Ya vimos en la primera unidad del curso, que los JavaServer Pages, también conocidos por su acrónimo JSP, eran un tipo de componente Java EE que se empaquetaba en un módulo web (WAR) y se desplegaba y ejecutaba en el contenedor web de un Servidor de Aplicaciones Java EE. Adicionalmente, dentro del patrón de diseño MVC ocupaban el espacio de la Vista, como encargado de mostrar el resultado de la petición al usuario final. Es decir, al contrario que los Java Servlets, estas están enfocadas a la presentación. No obstante, de momento, va a cubrir completamente todas las responsabilidades del MVC. Y según vayamos avanzando en el curso, le iremos quitando responsabilidades que no debieran ser del JavaServer Page, para dárselas a otro tipo de componente como los Java Servlets. Repetimos, esto es un mal diseño y una mala práctica de programación. Pero consideramos que es imprescindible ir aprendiendo poco a poco. Por tanto, este diseño es temporal mientras avanzamos en las sucesivas unidades del curso. 107 Por último, desde un punto de vista del lenguaje de programación Java, una JavaServer Page no es más que un fichero con un lenguaje de etiquetado como HTML y trozos de código Java embebidos. 5.2 Mi primer JavaServer Page Antes de entrar a estudiar en profundidad los JavaServer Pages, vamos a guiar los pasos para desarrollar nuestro primer JavaServer Page con Eclipse IDE for Java EE Developers y desplegarlo en Apache Tomcat. Es requisito indispensable, haber seguido los pasos de instalación y configuración de ambas dos herramientas detallados en la unidad 2 de este curso. Estos mismos pasos nos servirán para los sucesivos ejemplos y actividades del resto del curso. Arrancamos Eclipse IDE for Java EE Developers y mediante el las opciones de menú “File” -> “New” -> “Dynamic Web Project”: 108 MÓDULO B – Unidad 5: JavaServer Pages Creamos la estructura de desarrollo de un módulo web (WAR). Como nombre del proyecto, pondremos “HolaMundoWeb”, y nos aseguraremos que seleccionamos “Apache Tomcat v7.0” como entrono de ejecución y “3.0” como el nivel de especificaciones Java EE (en cuanto a contenedor web) que queremos utilizar. Pulsamos el botón “Next” dos veces: En la última ventana del asistente, antes de pulsar el botón “Finish”, nos aseguramos de marcar la opción “Generate web.xml deployment descriptor”: 109 La estructura del proyecto creado debería ser algo así: 110 MÓDULO B – Unidad 5: JavaServer Pages En el directorio /Java Resources/src es donde iremos colocando el código fuente de nuestras clases Java. En el directorio /WebContent es donde iremos colocando los recursos web, como por ejemplo, las páginas HTML, las JavaServer Pages, imágenes, etc… Y el fichero web.xml de /WebContent/WEB-INF es el descriptor de despliegue del módulo web (en nuestro caso, del proyecto HolaMundoWeb). Ahora crearemos un JavaServer Page. Botón derecho sobre el nuevo proyecto -> “New” -> “JSP File”: 111 En la siguiente ventana, rellenamos el nombre del fichero (File name) con “HolaMundo.jsp”. Nos aseguraremos de que el directorio donde se va a crear sea /WebContent (normalmente se selecciona por defecto). Y pulsamos el botón “Finish”: 112 MÓDULO B – Unidad 5: JavaServer Pages Escribimos el siguiente código y salvamos: <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Hola Mundo!</TITLE> </HEAD> <BODY> Hola Mundo! </BODY> </HTML> No nos preocupemos en este momento por no entender el código. De momento, simplemente queremos mostrar los pasos para el desarrollo y despliegue de un JavaServer Page. En los sucesivos apartados iremos entendiendo el por qué de cada línea. 113 Ya solo queda, probar la aplicación. Para ello, pulsamos el botón derecho sobre el fichero JSP -> “Run As” -> “Run on Server”: En la siguiente ventana, seleccionar “Tomcat v7.0 Server at localhost” y pulsar el botón “Finish”: 114 MÓDULO B – Unidad 5: JavaServer Pages En la vista de “Console” veremos las trazas que va dejando el Servidor de Aplicaciones Java EE al arrancar, cómo despliega la aplicación y automáticamente abre un navegador interno de Eclipse IDE con la URL: http://localhost:8080/HolaMundoWeb/HolaMundo.jsp donde se ve el resultado de nuestro primer JavaServer Page: 115 Perfectamente podríamos utilizar otro navegador web (tipo Mozilla Firefox o Internet Explorer) y escribir la misma URL. El comportamiento debería ser el mismo. Para abrir el navegador interno de Eclipse IDE a mano, utilizar el botón de la barra de herramientas: Ya solo nos queda desinstalar la aplicación mediante la opción “Add and Remove…”: 116 MÓDULO B – Unidad 5: JavaServer Pages Y apagar el servidor mediante la opción “Stop”: 117 5.3 JavaServer Pages Una JavaServer Page, es un fichero de extensión *.jsp que utiliza principalmente lenguajes de etiquetado como el HTML (estudiado en la unidad 3) u otros menos extendidos como WML, SVG, cHTML, etc, en el que se pueden embeber mediante unas etiquetas especiales, código Java. Es decir, vendrían a ser como el opuesto de los Java Servlets. Si aquellos eran código Java con código de etiquetado embebido en las sentencias de escritura, estas son código de etiquetado con código Java embebido. A la hora de su ejecución, el Servidor Java EE convertirá estos ficheros en un Java Servlet como los estudiados en la unidad anterior. Es decir, generará de forma automática, un Java Servlet en el que incluirá todos los fragmentos de Java embebidos, y añadirá sentencias de impresión con el lenguaje de etiquetado de presentación estático. Aunque como decimos, este proceso ocurre a sin conocimiento directo del desarrollador y lo realiza automáticamente el Servidor de Aplicaciones Java EE, siempre va bien tenerlo en 118 MÓDULO B – Unidad 5: JavaServer Pages cuenta para entender correctamente el significado de las etiquetas específicas de las JavaServer Pages. Existen unas cuantas etiquetas específicas de las JavaServer Pages, que nada tienen que ver con el lenguaje de etiquetado que se esté utilizando para la presentación, y son las que estudiaremos a continuación como parte de la sintaxis. 5.3.1 Etiqueta de comentario Los comentarios en una JavaServer Page se añaden delimitados por las siguientes dos etiquetas de inicio y fin: <%-- Comentario --%> Dichos comentarios, no se envían como parte de la respuesta desde el Servidor Java EE. Es decir, no es información que viaje por la red y por tanto no afectan al volumen de información transferido. Veamos un ejemplo: <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Hola Mundo!</TITLE> </HEAD> <BODY> <%‐‐ Mi primera página JSP ‐‐%> Hola Mundo! </BODY> </HTML> Al ejecutar el ejemplo, en el navegador no veréis nada distinto. Y si al navegador se le pide ver el código fuente de la página HTML que ha cargado, tampoco veréis ninguna de las etiquetas relacionadas con las JavaServer Pages. 5.3.2 Etiqueta de directiva Las etiquetas de directiva siguen la siguiente sintaxis: <%@ directiva [atributo=”valor”] %> Existen tres tipos de directivas. De hecho, en los ejemplos anteriores ya hemos utilizado una de ellas: page, como primera línea de la JavaServer Page. Los tres tipos son: 119 page: añade información técnica a la página utilizada por el Servidor de Aplicaciones Java EE para su posterior conversión a un Java Servlet. Puede utilizar múltiples atributos, pero los más extendidos e importantes son: o contentType: para identificar el contenido. o pageEncoding: para identificar el juego de caracteres utilizado. o import: fundamental para poder importar las clases Java utilizadas en los trozos de código java embebido y poder compilar correctamente. o Otras como errorPage, isThreadSafe o session, que veremos en siguientes unidades. Veamos un ejemplo: <%@ page contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1" %> <%@ page import="java.util.Date, es.aulamentor.*" %> include: se utiliza para incluir en ese punto el resultado de la ejecución de otra JavaServer Page. Dichas JavaServer Pages suelen ser fragmentos de una presentación, por ejemplo, una cabecera, un mensaje de copyright, etc. Es muy útil para reutilizar código y no tener que escribirlo n veces, además de facilitar su posterior mantenimiento. Utiliza un único atributo que es “file”, para identificar la JavaServer Page a incluir. Veamos un ejemplo: <%@ include file="cabecera.jsp” %> taglib: se utiliza para definir una librería de etiquetas (tags) propia que se va a utilizar en la JavaServer Page. La estudiaremos en siguientes unidades. No obstante, veamos un ejemplo: <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 5.3.3 Etiqueta de declaración En una JavaServer Page, es posible definir atributos y métodos, como si de una clase Java se tratase, que serán accesibles desde toda la JavaServer Page. Dichas declaraciones se realizan mediante las etiquetas: <%! … %> Por ejemplo, si queremos definir un atributo de tipo java.lang.String con el valor “Hola Mundo!” lo haríamos así: 120 MÓDULO B – Unidad 5: JavaServer Pages <%! String var = “Hola Mundo!” %> El Servidor de Aplicaciones Java EE, al generar el Java Servlet añadiría dicha variable como un atributo en la clase Java generada. Si lo que queremos es definir un método que sume dos números, por ejemplo, lo haríamos así: <%! public int suma(int param1, int param2) { return param1 + param2; } %> El Servidor de Aplicaciones Java EE, al generar el Java Servlet, añadiría dicha definición como un método en la clase Java generada. Nota: es muy importante en este punto, tener en cuenta que este tipo de aplicaciones Java EE son multiusuario y por tanto multitarea. Es decir, se producen n ejecuciones paralelas del mismo código y por tanto el uso de los atributos o variables de instancia pueden ser “peligrosos” y producir efectos no deseados. Asumimos que el lector es conocedor de las problemáticas asociadas a la programación concurrente tratadas en el Curso de Aula Mentor “Programación en Java – Avanzado”. No obstante, volveremos de nuevo a este tema en las siguientes unidades. 5.3.4 Etiqueta de scriptlets Los scriptles son las porciones de código Java embebidos en la JavaServer Page. Utilizan las siguientes etiquetas: <% … %> Por ejemplo: <% String var = “Hola Mundo!”; %> Seguramente, os estaréis preguntando si esto no es lo mismo que la etiqueta de declaración que vimos antes. La respuesta es no. La declaración, creaba un atributo, es decir, una variable de instancia. Sin embargo, en este caso de ahora, al usar un scriptlet, se trata de una variable local. Pero variable local, ¿de qué método? Como ya hemos comentado varias veces, el Servidor de Aplicaciones Java EE automáticamente convierte la JavaServer Page en un Java Servlet. Y por tanto, tendrá un método service() como ya vimos en la unidad anterior dedicada a los Java Servlets. Pues bien, todo el etiquetado de presentación de la JavaServer Page, así como los scriptlets, son incluidos en el método service() del Java Servlet autogenerado. Es por tanto en dicho método donde se crea la variable local de nuestro último ejemplo. 121 Un uso habitual de los scriptlets que dota de gran potencia y dinamismo a las JavaServer Pages, es su integración con el etiquetado de presentación. Por ejemplo: <% if(sexo.equals(“Hombre”)) { %> <H1>Buenos días señor</H1> <% } else { %> <H1>Buenos días señora</H1> <% } %> Como podemos observar, hemos conseguido dotar de dinamismo al lenguaje HTML que por su naturaleza es estático. Dependiendo del valor de una variable, la etiqueta HTML que se envía al cliente como parte de la respuesta es una u otra. Igual que hemos utilizado una bifurcación, pensad en la flexibilidad que nos da poder usar también un bucle para alimentar listas o tablas de forma dinámica. 5.3.5 Etiqueta de expresión Este es el último tipo de etiquetas específicas de la tecnología JavaServer Page. Este tipo lo que permite es devolver un valor de una variable o un método e insertarlo como parte de la respuesta. La sintaxis de la etiqueta es así: <%= … %> Por ejemplo: <H1>El resultado de sumar 2 + 2 es: <%= 2 + 2 %>.</H1> O por ejemplo: La hora actual es: <%= Calendar.getInstance().getTime() %>. Es importante fijarse, que la expresión Java no se termina con el signo de punto y coma (;) como se hace habitualmente. Evidentemente, no se pueden encadenar expresiones, solo se permite una. El Servidor de Aplicaciones Java EE, incluirá en el Java Servlet autogenerado el mix de código estático y código dinámico como ya aprendimos a hacerlo en la unidad de Java Servlets. 122 MÓDULO B – Unidad 5: JavaServer Pages 5.3.6 Ejemplo con todas las etiquetas Vemos un ejemplo completo con todos los tipos de etiquetas en funcionamiento: <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1" %> <%@ page import="java.util.Calendar" %> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Calculadora</TITLE> </HEAD> <BODY> <%‐‐ Método para sumar dos números enteros ‐‐%> <%! public int sumar(int param1, int param2) { return param1 + param2; } %> <% if(request.getParameter("switch") == null) { %> <FORM method="POST" action="/EjemplosWeb/calculadora.jsp" > <INPUT type="hidden" name="switch" value="true"> <TABLE border="0"> <TR> <TD>Primer número:</TD> <TD><INPUT name="param1"></TD> </TR> <TR> <TD>Segundo número:</TD> <TD><INPUT name="param2"></TD> </TR> <TR> <TD><INPUT type="submit" value="Sumar"></TD> </TR> </TABLE> </FORM> <% } else { try { int param1 = Integer.parseInt(request.getParameter("param1")); int param2 = Integer.parseInt(request.getParameter("param2")); int result = param1 + param2; %> El resultado de la suma es: <%= sumar(param1, param2) %><BR><BR> <% } catch(NumberFormatException ex) { %> 123 Alguno de los números no contenía dígitos válidos...<BR><BR> <% } %> <%= Calendar.getInstance().getTime() %><BR><BR> <A href="/EjemplosWeb/calculadora.jsp">Volver atrás...</A> <% } %> </BODY> </HTML> Si analizamos detenidamente el ejemplo, podemos observar el uso de los distintos tipos de etiquetado: Directivas para definir algunos aspectos técnicos de la JavaServer Page, así como añadir imports necesarios para la correcta compilación. Comentarios para documentar el objetivo del método sumar. Declaraciones para definir el método sumar. Scriptlets para implementar la lógica de control. Expresiones para devolver los valores calculados. Evidentemente se trata de un ejemplo, donde hemos intentado utilizar el 100% de los conceptos expuestos. No quiere decir que la mejor solución implique utilizarlos todos. Por cierto, algún lector se habrá dado cuenta de la utilización de un objeto “request” sin haberlo mencionado ni definido en ningún sitio. Las JavaServer Pages cuentan con una serie de objetos implícitos accesibles desde cualquier punto de la página. Los comentaremos en la siguiente sección. Adicionalmente, y simplemente a modo ilustrativo, adjuntamos el código fuente del Java Servlet que el Servidor de Aplicaciones Java EE habría generado para la JavaServer Page de ejemplo. Como decimos, es puramente a modo ilustrativo, por si puede ayudar a entender los conceptos explicados. Recordar que la generación y compilación de este Java Servlet es algo automático e intrínseco del Servidor de Aplicaciones Java EE: /* * Generated by the Jasper component of Apache Tomcat * Version: Apache Tomcat/7.0.22 * Generated at: 2012‐02‐17 12:25:10 UTC * Note: The last modified time of this file was set to * the last modified time of the source file after * generation to assist with modification tracking. */ package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; import java.util.Calendar; 124 MÓDULO B – Unidad 5: JavaServer Pages public final class calculadora_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { public int sumar(int param1, int param2) { return param1 + param2; } private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants; private javax.el.ExpressionFactory _el_expressionfactory; private org.apache.tomcat.InstanceManager _jsp_instancemanager; public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; } public void _jspInit() { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletCon text()).getExpressionFactory(); _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(ge tServletConfig()); } public void _jspDestroy() { } 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; try { response.setContentType("text/html; charset=ISO‐8859‐1"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write("\r\n"); out.write("\r\n"); 125 out.write("<!DOCTYPE html PUBLIC \"‐//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n"); out.write("<HTML>\r\n"); out.write(" <HEAD>\r\n"); out.write(" <META http‐equiv=\"Content‐Type\" content=\"text/html; charset=ISO‐8859‐1\">\r\n"); out.write(" <TITLE>Calculadora</TITLE>\r\n"); out.write(" </HEAD>\r\n"); out.write(" <BODY>\r\n"); out.write(" "); out.write("\r\n"); out.write(" "); out.write("\r\n"); out.write(" \t"); if(request.getParameter("switch") == null) { out.write("\r\n"); out.write("\t<FORM method=\"POST\" action=\"/EjemplosWeb/calculadora.jsp\" >\r\n"); out.write("\t\t<INPUT type=\"hidden\" name=\"switch\" value=\"true\">\r\n"); out.write("\t\t<TABLE border=\"0\">\r\n"); out.write("\t\t\t<TR>\r\n"); out.write("\t\t\t\t<TD>Primer número:</TD>\r\n"); out.write("\t\t\t\t<TD><INPUT name=\"param1\"></TD>\r\n"); out.write("\t\t\t</TR>\r\n"); out.write("\t\t\t<TR>\r\n"); out.write("\t\t\t\t<TD>Segundo número:</TD>\r\n"); out.write("\t\t\t\t<TD><INPUT name=\"param2\"></TD>\r\n"); out.write("\t\t\t</TR>\r\n"); out.write("\t\t\t<TR>\r\n"); out.write("\t\t\t\t<TD><INPUT type=\"submit\" value=\"Sumar\"></TD>\r\n"); out.write("\t\t\t</TR>\r\n"); out.write("\t\t</TABLE>\r\n"); out.write("\t</FORM>\r\n"); out.write("\t"); } else { try { int param1 = Integer.parseInt(request.getParameter("param1")); int param2 = Integer.parseInt(request.getParameter("param2")); out.write("\r\n"); out.write("\tEl resultado de la suma es: "); out.print( sumar(param1, param2) ); out.write("<BR><BR>\r\n"); out.write("\t"); } catch(NumberFormatException ex) { out.write("\r\n"); out.write("\tAlguno de los números no contenía dígitos válidos...<BR><BR>\r\n"); out.write("\t"); } out.write('\r'); out.write('\n'); 126 MÓDULO B – Unidad 5: JavaServer Pages out.write(' '); out.print( Calendar.getInstance().getTime() ); out.write("<BR><BR>\r\n"); out.write("\t<A href=\"/EjemplosWeb/calculadora.jsp\">Volver atrás...</A>\r\n"); out.write("\t"); } out.write("\r\n"); out.write(" </BODY>\r\n"); out.write("</HTML>"); } catch (java.lang.Throwable t) { if (!(t instanceof javax.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { out.clearBuffer(); } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } } Aunque hay muchas líneas de código que no son fáciles de interpretar a estas alturas del curso, si que de un simple vistazo el alumno debería ser capaz de identificar los métodos _jspInit(), _jspService() y _jspDestroy() que son invocados por los métodos del ciclo de vida de los Java Servlets estudiados en la unidad anterior. Y si nos centramos en concreto en el método _jspService(), se debería poder identificar fácilmente la lógica de control de nuestro JavaServer Page, así como las impresiones del código estático HTML a la respuesta. También se puede identificar al inicio, la declaración del método sumar() como parte del Java Servlet generado. 5.4 Otros conceptos En este apartado trataremos algunos otros conceptos relacionados con los JavaServer Pages que ya hemos mencionado pero no hemos tratado con el suficiente detalle. 5.4.1 El ciclo de vida de un JavaServer Page Al igual que ocurría con los Java Servlets, los JavaServer Pages también siguen un ciclo de vida definido por el API de la especificación Java EE. En concreto el interface: javax.servlet.jsp.HttpJspBase Dicho ciclo de vida es el mismo que el de los Java Servlets ya que como hemos indicado en varias ocasiones ya, los JavaServer Pages terminan siendo transformados por el Servidor de Aplicaciones Java EE en un Java Servlet. La única diferencia radica en los nombres. Los métodos del ciclo de vida de un JavaServer Page son: 127 public void _jspInit(); public _jspService(HttpServletRequest request, HttpServletResponse response); public void _jspDestroy(); Si quisiéramos sobrescribir el comportamiento de inicialización o finalización de un JavaServer Page, deberíamos hacerlo redefiniendo el método en cuestión mediante las etiquetas de declaración. Pero es raro tener que hacerlo. El método _jspService por el contrario, no se sobrescribe nunca, porque ya hemos visto que cualquier cosa que añadamos al JavaServer Page forma parte de él. Es decir, crear un JavaServer Page implica siempre sobreescribir el método _jspService. 5.4.2 Objetos implícitos de un JavaServer Page El contenedor Web de un Servidor de Aplicaciones Java EE, pone a disposición de los desarrolladores de JavaServer Pages una serie de objetos implícitos sin necesidad de que estos tengan que crearlos o buscarlos. En el último ejemplo que vimos, utilizamos el objeto request que representa a la petición HTTP recibida por el JavaServer Page sin necesidad de crearlo, o pedirlo. Directamente existe y es utilizable. Los objetos implícitos son: request: representa la petición HTTP y normalmente la utilizaremos para acceder a los parámetros enviados desde el cliente. response: representa la respuesta HTTP. session: representa la sesión HTTP del usuario. Este tema lo veremos con mayor profundidad en siguientes unidades. application: representa el contexto de la aplicación. Este tema lo veremos con mayor profundidad en siguientes unidades. config: representa la configuración inicial que se ha hecho para este JavaServer Page. Este tema lo veremos con mayor profundidad en siguientes unidades. page: representa la propia página. Sería lo mismo que usar la keyword de Java: this. exception: representa la excepción que se ha producido y que ha motivado que el Servidor de Aplicaciones Java EE redirigiese la ejecución a esta página. Debe utilizar la directiva “isErrorPage”. Si no se es una página definida como página de error, este objeto no estará accesible. out: representa el writer de escritura de la respuesta HTTP. No suele ser habitual su uso desde una JavaServer Page.. 128 MÓDULO B – Unidad 5: JavaServer Pages 5.4.3 Empaquetado y despliegue Como comentábamos en la primera unidad de este curso de introducción al Java EE, una aplicación Java EE está formada por un empaquetamiento de una o varias unidades conocidas con el nombre de módulos. Este empaquetamiento final era un EAR (Enterprise ARchive). Uno de esos módulos son los Web, que contienen normalmente Java Servlets, JavaServer Pages (JSP), JavaServer Faces (JSF), contenidos estáticos como imágenes, HTMLs, CSSs… Su extensión del fichero empaquetado es WAR (Web ARchive). En este curso, nos vamos a centrar exclusivamente de este tipo de módulos, ya que intentamos cubrir solo la parte web de la programación Java EE y no el resto de aspectos que darían lugar a un curso demasiado extenso o a un segundo curso. A pesar de que las aplicaciones Java EE se empaquetan en un EAR, los Servidores de Aplicación que solo cubren parte de la especificación Java EE como ocurre con Apache Tomcat, permiten desplegar simplemente módulos Web aislados. Este será nuestro caso durante este curso. Gráficamente, la estructura de un módulo Web empaquetado sería la siguiente: Como podemos observar, tiene bastante parecido a la estructura del proyecto web dentro de Eclipse IDE for Java EE Developers aunque no es igual. En principio, el raíz del fichero WAR correspondería al directorio /WebContent y el directorio /classes del fichero WAR correspondería al directorio /build/classes. 129 Ahora bien, normalmente no nos preocuparemos mucho de la generación de un módulo Web u otro tipo de módulo, porque son los IDEs los que nos van a ayudar a generarlo. Por ejemplo, en nuestro caso, con Eclipse IDE for Java EE Developers basta con seleccionar el proyecto, pulsar el botón derecho del ratón y seleccionar las opciones “Export” -> “WAR file”: 130 MÓDULO B – Unidad 5: JavaServer Pages En la siguiente ventana, nos aseguramos que el proyecto seleccionado es el correcto, y decidimos donde y con que nombre se va a generar el fichero WAR, por ejemplo, “C:\EjemplosWeb.war”. Pulsar el botón “Finish”: Si vamos a una sesión de DOS, mediante el comando de la JDK jar.exe (jar tvf nombreFichero.war), podemos ver su contenido y confirmar que la estructura generada es exactamente la de la especificación Java EE: 131 Este fichero WAR, sería el que un administrador de Servidores de Aplicación Java EE desplegaría y configuraría para que la aplicación estuviese disponible y dando servicio. Pero en nuestro caso, simplemente añadiremos o quitaremos aplicaciones en el Apache Tomcat de Eclipse IDE for Java EE Developers mediante el interfaz de usuario: botón derecho del ratón y la opción “Add and Remove…”, o directamente sobre el componente y “Run As” -> “Run on Server”. Nos queda no obstante hablar un poco sobre el descriptor de despliegue. Como ya adelantábamos en la primera unidad, se trata de un fichero XML donde se describe un poco el contenido y posible comportamiento del contenido del módulo. En el caso concreto de los módulos Web, su descriptor de despliegue es el fichero web.xml A lo largo de esta unidad, nunca hemos hecho mención de él, ya que a diferencia de los Java Servlets, los JavaServer Pages pueden ser invocados directamente mediante el nombre del fichero sin necesidad de registrarlos en el descriptor de despliegue. No obstante, es posible registrarlos con otro alias para evitar que sean invocados mediante el nombre del fichero. Esto se consigue de forma parecida a como hacíamos con los Java Servlets pero con la etiqueta ”jsp-file” en vez de “servlet-class”. Veamos un ejemplo: <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <servlet> <servlet‐name>CalculadoraJSP</servlet‐name> <jsp‐file>/calculadora.jsp</jsp‐file> </servlet> <servlet‐mapping> <servlet‐name>CalculadoraJSP</servlet‐name> <url‐pattern>/CalculadoraJSP</url‐pattern> </servlet‐mapping> </web‐app> Si utilizamos este descriptor de despliegue en nuestro ejemplo, en vez de usar esta URL para invocar nuestra JavaServer Page: http://localhost:8080/EjemplosWeb/calculadora.jsp podríamos utilizar esta otra: http://localhost:8080/EjemplosWeb/CalculadoraJSP Existen muchas mas etiquetas a utilizar en un descriptor de despliegue de un módulo Web, pero las iremos viendo a lo largo del curso según las vayamos necesitando. De momento nos quedamos con la general “web-app” y las necesarias para registrar un alias a un JavaServer Page. 132 MÓDULO B – Unidad 5: JavaServer Pages Es una buena práctica utilizar siempre el alias y ocultar de esta manera el nombre del fichero al llamante de la JavaServer Page (JSP). 133 PARA RECORDAR Los JavaServer Pages, son un tipo de componente Java EE que se empaquen en un módulo web (WAR) y se despliegan y ejecutan en el contenedor web de un Servidor de Aplicaciones Java EE. Dentro del patrón de diseño MVC ocupan el espacio de la Vista, como encargado de mostrar el resultado de la petición al usuario final. Están enfocadas a la presentación. Un JavaServer Page, es un fichero de extensión *.jsp que utiliza principalmente lenguajes de etiquetado como el HTML en el que se pueden embeber mediante unas etiquetas especiales, código Java. Contienen distintos tipos de etiquetas, como son las: Comentario: <%-- … --%> Directiva: o page: añade información técnica a la página o include: se utiliza para incluir el resultado de la ejecución de otra JSP o taglib: se utiliza para definir una librería de etiquetas (tags) que se va a utilizar en la JavaServer Page Declaración: <%! … %> utilizada para definir atributos y método que serán accesibles desde toda la JavaServer Page. Scriptles: <% … %> para incluir código Java embebido en la JavaServer Page. Expresión: <%= … %> devuelve el valor de una variable o un método para insertarlo como parte de la respuesta. El ciclo de vida de una JavaServer Page es similar a los Java Servlets, solo que cambian los nombres, jspInit, jspService y jspDestroy 134 Las JavaServer Pages, tienen una serie de objetos a su disposición de forma implícita, para utilizarlos en caso de necesitarlos: request: representa la petición HTTP response: representa la respuesta HTTP. session: representa la sesión HTTP del usuario. application: representa el contexto de la aplicación. config: representa la configuración inicial que se ha hecho para este JavaServer Page. page: representa la propia página. exception: representa la excepción que se ha out: representa el writer de escritura de la respuesta HTTP 135 136 Unidad de Aprendizaje 6 DISEÑO DE APLICACIONES JAVA EE ÍNDICE 6.1 Introducción ................................................................. 139 6.1.1 El Controlador ...............................................................140 6.1.2 La Vista...........................................................................140 6.1.3 El Modelo .......................................................................141 6.2 Conectando Java Servlets con JavaServer Pages . 142 6.2.1 RequestDispatcher .......................................................142 6.2.2 Un ejemplo completo ...................................................143 6.3 Otros patrones de diseño........................................... 147 PARA RECORDAR.................................................................. 150 MÓDULO B – Unidad 6: Diseño de Aplicaciones Java EE 6.1 Introducción Llegados a este punto, el alumno ya conoce lo básico de las tecnologías Java Servlet y JavaServer Page (JSP), sus pros y contras, puntos fuertes y puntos flojos. En más de una ocasión durante las últimas unidades, hemos comentado que estábamos haciendo un mal diseño de las aplicaciones pero que desde un punto de vista didáctico nos parecía conveniente hacerlo así, para comprender mejor esta unidad dedicada al diseño de Aplicaciones Java EE. Existen multitud de patrones de diseño relacionados con las Aplicaciones Java EE, pero hay uno ya mencionado durante el curso que tiene en cuenta a la aplicación en su conjunto y no a una parte solo. Estamos hablando del patrón MVC (Modelo-Vista- Controlador o Model-View-Controller en inglés). Este patrón de diseño divide la aplicación en tres roles, cada uno de ellos con unas responsabilidades muy concretas. El Modelo (Model): Representa los datos y cualquier lógica de negocio relacionada con ellos. La Vista (View): renderiza el contenido de los modelos dependiendo de la tipología de cliente (navegador web, teléfono móvil, etc…). El Controlador (Controller): define el comportamiento general de la aplicación coordinando a las otras dos partes (Modelo y Vista). Veamos el patrón de diseño en un diagrama: 139 Pues bien, los dos tipos de componentes Java EE que hemos estudiado ya, encajan perfectamente en este diseño: Controlador: Java Servlets. Vista: JavaServer Pages (JSP). 6.1.1 El Controlador De la experiencia que hemos cogido con las actividades relacionadas con la unidad de los Java Servlets, hemos visto que son muy potentes para contener código Java, analizar los parámetros de las peticiones, validarlos, manipularlos, etc… pero sin embargo son realmente tediosos para manejar la presentación. Para un diseñador gráfico de páginas HTML, es realmente complicado crear y mantener dichas páginas a través de líneas out.println(….) que dificultan ver el resultado final en su conjunto. 6.1.2 La Vista Por el contrario, los JavaServer Pages (JSP) han demostrado ser muy valiosos para realizar el diseño gráfico de la presentación, insertando pequeños scriptlets Java, pero muy tediosos cuando el número de líneas de código Java se incrementa y entonces la mezcla de lógica y presentación se hace muy complicada. 140 MÓDULO B – Unidad 6: Diseño de Aplicaciones Java EE 6.1.3 El Modelo Con lo visto hasta ahora, no parece que ninguna de las dos tecnologías sea la más adecuada para implementar la lógica de negocio, es decir, encapsular e implementar la razón de nuestra Aplicación Java EE. Las especificaciones Java EE contemplan un tipo de componente cuyo objetivo es cubrir este hueco: los Enterprise JavaBeans (EJBs). Pero como no son objetivo de este curso, utilizaremos directamente POJOs (Plain Old Java Object, o lo que es lo mismo, clases Java sin más). Por tanto, todo en su conjunto quedaría visualmente así: Y el flujo habitual de una aplicación Java EE a partir de ahora quedará de la siguiente manera: 1. El cliente, por ejemplo un navegador, solicitará una funcionalidad desde el interface visual (Vista). 2. Dicha petición entrará a través de un Java Servlet (Controlador). 3. Dicho Java Servlet, analizará qué se está pidiendo, qué información adicional aporta y decidirá que JavaBean (Modelo) cubre dicha petición (implementado mediante un POJO). 4. Lo invocará, y tras recibir un resultado, decidirá qué JavaServer Page muestra dicho resultado al cliente (Vista). 5. El resultado será devuelto y mostrado. Para profundizar en el patrón de diseño Model-View-Controller, conectarse al Catálogo OnLine de patrones Java EE: http://www.oracle.com/technetwork/java/catalog-137601.html 141 6.2 Conectando Java Servlets con JavaServer Pages Quedan claras entonces las interrelaciones que existirán entre los distintos componentes estudiados. ¿Pero técnicamente cómo hacemos dichos enlaces? La invocación desde un Java Servlet a un JavaBean (o POJO) es muy sencilla. Simplemente instanciará la clase en cuestión e invocará algún método de la misma. Pero, ¿y la invocación desde un Java Servlet a un JavaServer Page (JSP)? Dicha invocación se realiza mediante el interface javax.servlet.RequestDispatcher. 6.2.1 RequestDispatcher Una instancia del tipo RequestDispatcher, representa la información necesaria para lanzar una petición a un recurso web. En nuestro caso, podremos lanzar una petición a otro Java Servlet o un JavaServer Page (JSP). El acceso a dicha instancia se puede realizar mediante el método: RequestDispatcher getRequestDispatcher(java.lang.String path); de la HttpServletRequest, donde el parámetro que necesita, indica el recurso al que queremos acceder. RequestDispatcher tiene dos métodos: void forward(ServletRequest request, ServletResponse response) throws…. Este primer método, nos permite redirigir la petición a otro componente y delegar en él la respuesta. Es decir, el control de lo que se responde al cliente por parte del Servidor Java EE queda delegado. void include(ServletRequest request, ServletResponse response) throws…. Este segundo método nos permite incluir como parte de nuestra respuesta, la respuesta de otro componente. Es decir, dicho componente colabora con la respuesta, pero el control lo seguimos teniendo nosotros. Es el mismo resultado que la directiva JSP “include” que estudiamos en la unidad anterior. Los parámetros de ambos métodos son la petición y respuesta HTTP. Normalmente serán los mismos que hemos recibido. Por tanto, ya sabemos cómo redirigir la ejecución desde un Java Servlet a un JavaServer Page (JSP). Pero nos quedaría un tema pendiente. ¿Cómo le pasamos información de la lógica de negocio al siguiente? Existen varias maneras de hacerlo: A través de la HttpServletRequest. Podemos utilizar los métodos setParameter(…) o setAttribute(…) de la petición para añadir información que será utilizada por el siguiente componente de la aplicación mediante los correspondientes métodos getParameter(…) o getAttribute(…). A través de la HttpSession. Todavía no hemos estudiado este concepto. Lo trataremos en la siguiente unidad. 142 MÓDULO B – Unidad 6: Diseño de Aplicaciones Java EE A través del ServletContext. Todavía no hemos estudiado este concepto. Lo trataremos en la siguiente unidad. 6.2.2 Un ejemplo completo Veamos un ejemplo completo de una aplicación web que resta dos números y que implementa todos los roles del patrón de diseño Java EE MVC, cada uno de ellos en su sitio adecuado. Nota: Aunque un ejemplo tan sencillo se podría pensar en implementar en un solo componente como hemos hecho en las actividades anteriores, debemos ser capaces de extrapolar el ejemplo a una aplicación mucho más compleja y entenderemos las grandes ventajas de este diseño que ya utilizaremos siempre a partir de ahora en el curso. Página JSP de cabecera a ser reutilizada (Vista) cabecera.jsp: <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <TABLE border="0" width="100%"> <TR> <TD align="left"> <IMG src="/EjemplosWeb/images/banner.gif"> </TD> <TD align="center"> <H1>Ejemplo de MVC</H1> </TD> <TD align="right"> <IMG src="/EjemplosWeb/images/banner.gif"> </TD> </TR> </TABLE> <HR><BR> Página JSP inicial (Vista): <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Calculadora</TITLE> </HEAD> <BODY> <%@ include file="/cabecera.jsp" %> <FORM method="POST" action="/EjemplosWeb/RestadorServlet" > <TABLE border="0"> <TR> <TD>Primer número:</TD> <TD><INPUT name="param1"></TD> </TR> 143 <TR> <TD>Segundo número:</TD> <TD><INPUT name="param2"></TD> </TR> <TR> <TD><INPUT type="submit" value="Restar"></TD> </TR> </TABLE> </FORM> </BODY> </HTML> JavaBean o POJO (Modelo) RestadorBean.java: package es.aulamentor; public class RestadorBean { public int restar(int param1, int param2) { return param1 + param2; } } Java Servlet (Controlador) index.jsp: package es.aulamentor; import java.io.IOException; 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="RestadorServlet", urlPatterns={"/RestadorServlet"}) public class RestadorServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.procesarPeticion(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.procesarPeticion(request, response); } private void procesarPeticion(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { int param1 = Integer.parseInt(request.getParameter("param1")); 144 MÓDULO B – Unidad 6: Diseño de Aplicaciones Java EE } int param2 = Integer.parseInt(request.getParameter("param2")); RestadorBean restadorBean = new RestadorBean(); int result = restadorBean.restar(param1, param2); request.setAttribute("result", result); request.getRequestDispatcher("/result.jsp").forward(reques t, response); } catch(NumberFormatException ex) { request.setAttribute("error", "Alguno de los números no contenía dígitos válidos..."); request.getRequestDispatcher("/error.jsp").forward(request , response); } } Página JSP de resultado (Vista) result.jsp: <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Calculadora</TITLE> </HEAD> <BODY> <%@ include file="/cabecera.jsp" %> El resultado de la suma es: <%= request.getAttribute("result") %> <BR><BR> <A href="/EjemplosWeb/index.jsp">Volver atrás...</A> </BODY> </HTML> Página JSP de error (Vista) error.jsp: <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Calculadora</TITLE> </HEAD> <BODY> <%@ include file="/cabecera.jsp" %> Se ha producido el siguiente error:<BR><BR> <%= request.getAttribute("error") %> <BR><BR> 145 <A href="/EjemplosWeb/index.jsp">Volver atrás...</A> </BODY> </HTML> No es necesario utilizar un descriptor de despliegue, ya que en el Java Servlet hicimos uso de las Anotaciones Java. Si el alumno implementa este ejemplo, debería ver algo así… Página de inicio: Página de resultado: 146 MÓDULO B – Unidad 6: Diseño de Aplicaciones Java EE Página de error: 6.3 Otros patrones de diseño A lo largo de la vida de las especificaciones Java EE, se han ido definiendo distintos patrones de diseño (buenas prácticas) que han demostrado ser muy útiles por distintos motivos tanto en el desarrollo como en el mantenimiento de las aplicaciones web. Existe mucha literatura sobre el tema, aunque la guía básica es la denominada Java EE BluePrints: http://java.sun.com/blueprints/patterns/ Este curso no pretende entrar a estudiar en profundidad los distintos patrones, pero si que comentaremos y utilizaremos alguno adicional que resultan interesantes A continuación, listamos alguno sencillo de entender y de mucha utilidad: Business Delegate (o Delegación de Negocio): permite reducir el acoplamiento entre la capa web y la capa de negocio. Se trata de una capa intermedia que abstrae a la capa de presentación de la tecnología empleada en la capa de negocio, es decir, conseguiríamos que la capa de presentación no supiera si la lógica de negocio se implementa mediante entidades Java Persistence API (JPA), Enterprise JavaBeans (EJBs), POJOs, etc…y un cambio de tecnología, no afectaría para nada a la capa de presentación, solo a los Business Delegates. Más información en: http://www.oracle.com/technetwork/java/businessdelegate-142190.html 147 Data Access Object, también llamado DAO (u Objeto de Acceso a Datos): permite abstraer a la lógica de negocio del sistema de persistencia de los datos. Seguramente a muchos os sonará de cuando estudiasteis el acceso a Bases de Datos con Java: JDBC (Java DataBase Access). Es decir, la lógica de la aplicación es agnóstica de si estamos usando Bases de Datos, ficheros, o cualquier otra fórmula de persistencia y acceso a los datos. Nuevamente, un cambio en la forma de hacerlo, solo afectaría al Data Access Object pero no a la lógica de negocio. Tendremos oportunidad de utilizarlo en los ejemplos y actividades de las siguientes unidades. Más información en: http://www.oracle.com/technetwork/java/dao-138818.html Front Controller (o Controlador Frontal): se trata de una pieza controladora que centraliza todas las peticiones y va eligiendo qué lógica de negocio y presentación son necesarias para dicha petición. Sin saberlo, así es como hemos trabajado en esta unidad. Siempre hemos manejado un único Java Servlet controlador que ha gestionado las peticiones. Evidentemente nuestros ejemplos eran muy sencillos y quizás no se vea la utilidad pero imaginemos por un momento una aplicación web típica de altas, bajas y modificaciones. Una primera aproximación podría ser contar con un Java Servlet para cada petición, pero este patrón de diseño recomienda tener uno solo que sepa gestionar de manera centralizada los tres tipos de petición. Tendremos oportunidad de utilizarlo en los ejemplos y actividades de las siguientes unidades. Más información: http://www.oracle.com/technetwork/java/frontcontroller-141071.html Intercepting Filter (o Filtro Interceptor): permite implementar una lógica que se ejecute siempre previamente a recibir una petición y posteriormente a enviar la respuesta. Imaginemos por ejemplo que queremos incluir en nuestra aplicación un sistema de logging, o de autenticación, etc… Técnicamente se implementa mediante unos Java Servlets especiales denominados filtros que estudiaremos en la siguiente unidad. Más información en: http://www.oracle.com/technetwork/java/interceptingfilter-136585.html Service Locator (o Localizador de Servicios): permite simplificar el acceso a servicios de la aplicación (por ejemplo Enterprise JavaBeans) o del Servidor de Aplicaciones Java EE (por ejemplo Pool de Conexiones a Bases de Datos o Conectores a Sistemas Empresariales) encapsulando dicha búsqueda y permitiendo su reutilización. 148 MÓDULO B – Unidad 6: Diseño de Aplicaciones Java EE Hablaremos de nuevo de este patrón en la siguiente unidad, cuando tratemos el acceso a Bases de Datos y los Pool de Conexiones (conocidos también con el nombre de Data Sources). Más información en: http://www.oracle.com/technetwork/java/servicelocator-138528.html Transfer Object (u Objeto de Transferencia): permite encapsular la información que viaja entre las distintas capas de la aplicación. Tiene mucho sentido cuando ambas capas están separadas físicamente y por tanto implica una comunicación por red en cada petición. Suele ser mejor desde un punto de vista de rendimiento, pedir una vez y recibir mucha información, que pedir más de una vez. Se utiliza frecuentemente en las invocaciones desde la capa de presentación y la capa de lógica de negocio cuando esta es distribuida, por ejemplo utilizando Enterprise JavaBeans (EJBs). En nuestro caso que utilizamos JavaBeans locales, no tiene un impacto tan grande. Más información: http://www.oracle.com/technetwork/java/transferobject-139870.html 149 PARA RECORDAR En esta unidad hemos visto detalladamente que componentes son los adecuados a utilizar siguiendo el patrón de diseño MVC: El Modelo: Representa los datos y cualquier lógica de negocio relacionada con ellos. Implementado mediante POJOS (también EJBs). La Vista: Renderiza el contenido de los modelos dependiendo de la tipología de cliente (navegador web, teléfono móvil, etc…). Implementado mediante JavaServer Pages (JSP). El Controlador: Define el comportamiento general de la aplicación coordinando a las otras dos partes (Modelo y Vista). Implementado mediante Java Servlets. La conexión entre un Java Servlet y un POJO se realiza mediante las llamadas a los métodos de dicho componente. La conexión entre un Java Servlet y un JavaServer Page (JSP) se realiza mediante los métodos forward o include (dependiendo de las necesidades) de la clase RequestDispatcher. Para pasarle los datos necesarios del Java Servlet y un JavaServer Page (JSP) se pueden utilizar los siguiente mecanismos: HttpServletRequest. Podemos setAttribute(…) de la petición utilizar los métodos setParameter(…) o HttpSession. ServletContext. Otros patrones de diseño Java EE son: Business Delegate: permite reducir el acoplamiento entre la capa web y la capa de negocio. Data Access Object: permite abstraer a la lógica de negocio del sistema de 150 Front Controller: se trata de una pieza controladora que centraliza todas las peticiones y va eligiendo qué lógica de negocio y presentación son necesarias para dicha petición. Intercepting Filter: permite implementar una lógica que se ejecute siempre previamente a recibir una petición y posteriormente a enviar la respuesta. Service Locator: permite simplificar el acceso a servicios de la aplicación o del Servidor de Aplicaciones Java encapsulando dicha búsqueda y permitiendo su reutilización. Transfer Object: permite encapsular la información que viaja entre las distintas capas de la aplicación 151 152 Unidad de Aprendizaje 7 JAVA SERVLETS AVANZADO ÍNDICE 7.1 Introducción ................................................................. 155 7.2 Configuración de un Java Servlet ............................. 155 7.2.1 Mediante descriptor de despliegue ...........................155 7.2.2 Mediante anotaciones..................................................156 7.3 Concurrencia en un Java Servlet .............................. 158 7.4 Acceso a Bases de Datos........................................... 160 7.4.1 Acceso directo desde el código.................................160 7.4.2 Acceso a través de un Pool de Conexiones.............166 7.5 Filtros............................................................................. 175 7.6 Mantenimiento del estado del cliente ...................... 179 7.6.1 Cookies ..........................................................................179 7.6.2 Sesión HTTP..................................................................188 7.7 Gestión de eventos...................................................... 193 PARA RECORDAR.................................................................. 201 MÓDULO C – Unidad 7: Java Servlets Avanzado 7.1 Introducción A estas alturas del curso, ya hemos aprendido las tecnologías básicas sobre las que se apoya el desarrollo de aplicaciones web Java EE y cuándo usar unas u otras. Incluso ya hemos desarrollado algunas aplicaciones sencillas. El objetivo de las siguientes unidades, es profundizar en estas tecnologías para poderle sacar más provecho y llegar a desarrollar aplicaciones Java EE reales. 7.2 Configuración de un Java Servlet Como bien recordarás de las unidades anteriores, el ciclo de vida de un Java Servlet tenía un método de inicialización: public void init(ServletConfig config); que se ejecutaba una sola vez al inicio de la vida del Java Servlet y que se solía utilizar para llevar a cabo todas las labores de inicialización y configuración que necesitáramos para su posterior ejecución. Como se puede observar, javax.servlet.ServletConfig, que recibe como representa parámetro la una información de instancia de configuración la clase que el administrador del Servidor de Aplicaciones Java EE haya podido añadir. Dicha clase tiene distintos métodos para facilitar el acceso a su información: java.lang.String getInitParameter(java.lang.String name) Devuelve el parámetro de configuración que coincide con el nombre que se envía. java.util.Enumeration<java.lang.String> getInitParameterNames() Devuelve una colección con los nombres de todos los parámetros de configuración que tiene. ¿Y cómo añadimos información de configuración a un Java Servlet? Pues bien, el desarrollador tiene dos modos de añadir dicha información: Mediante el descriptor de despliegue. Mediante anotaciones en el código (a partir de Java EE 6.0). 7.2.1 Mediante descriptor de despliegue Consiste en añadir un bloque “init-param” por cada parámetro, con parejas de bloques “param-name” y “param-value”, dentro del bloque “servlet” que ya conocemos y hemos utilizado anteriormente. Veamos un ejemplo de un Java Servlet que recibe dos parámetros de configuración mediante el descriptor de despliegue: 155 <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <servlet> <servlet‐name>ConfigServlet</servlet‐name> <servlet‐class>es.aulamentor.ConfigServlet</servlet‐class> <init‐param> <param‐name>param1</param‐name> <param‐value>Hola</param‐value> </init‐param> <init‐param> <param‐name>param2</param‐name> <param‐value>Mundo</param‐value> </init‐param> </servlet> <servlet‐mapping> <servlet‐name>ConfigServlet</servlet‐name> <url‐pattern>/ConfigServlet</url‐pattern> </servlet‐mapping> </web‐app> 7.2.2 Mediante anotaciones Se realiza añadiendo el atributo “initParams” a la anotación @WebServlet que ya hemos utilizado anteriormente. Dicho atributo, contiene a su vez, anotaciones @WebInitParam por cada parámetro, la cual nuevamente, contiene parejas de atributos “name” y “value”. Se entiende mucho más fácil con un ejemplo: package es.aulamentor; import java.io.IOException; import java.util.Enumeration; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(name="ConfigServlet",urlPatterns={"/ConfigServlet"}, initParams={@WebInitParam(name="param1", value="Hola"), @WebInitParam(name="param2", value="Mundo")}) public class ConfigServlet extends HttpServlet { public void init(ServletConfig config) throws ServletException { Enumeration<String> parameters = config.getInitParameterNames(); while(parameters.hasMoreElements()) { String name = parameters.nextElement(); 156 MÓDULO C – Unidad 7: Java Servlets Avanzado System.out.println("Nombre: " + name + " Valor: " + config.getInitParameter(name)); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } } Este código de ejemplo en ejecución dentro del entorno de desarrollo mostraría algo así: Ambos dos métodos no son excluyentes, aunque no tiene mucho sentido utilizar ambos para un mismo Java Servlet. No obstante, si así fuera y en el hipotético caso de que en ambos dos sitios existiera un parámetro con el mismo nombre, tendría prioridad el del descriptor de despliegue. 157 7.3 Concurrencia en un Java Servlet Nota: En esta sección se asume que el alumno tiene conceptos de programación avanzada en Java, en concreto, en la programación concurrente con Java Threads. Se dice que una aplicación soporta concurrencia cuando permite ejecuciones en paralelo de su código. Es decir, en el caso de las aplicaciones Java EE que estamos estudiando, significaría que varios usuarios puedan conectarse y ejecutar la aplicación a la vez. Las aplicaciones Java EE son concurrentes por naturaleza. El Servidor de Aplicaciones Java EE se encarga de controlar y permitir la concurrencia de la aplicación permitiendo al desarrollador prácticamente despreocuparse de esta casuística. Teniendo en cuenta que solamente hay una instancia de un Java Servlet por cada JVM, no debemos olvidarnos nunca del tema y tenerlo siempre presente en nuestros desarrollos para evitar problemas y comportamientos no deseados. Por ejemplo, imaginemos lo que puede ocurrir si utilizamos variables de instancia en el Java Servlet para manejar una información temporal durante la ejecución de un método, en el que hemos sustituido un posible código que tarda cierto tiempo en ejecutar por un Thread.sleep(5000); package es.aulamentor; import java.io.IOException; 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("/ConcurrenciaServlet") public class ConcurrenciaServlet extends HttpServlet { private int val = 0; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { val = Integer.parseInt(request.getParameter("param")); System.out.println("Thread: " + Thread.currentThread().getName() + " usa el valor: " + val); try { //Simulamos que el servlet está con alguna operativa antes de //volver a utilizar el valor de la variable val. Thread.sleep(5000); } catch(InterruptedException ex) { } System.out.println("Thread: " + Thread.currentThread().getName() + " usa el valor: " + val); } } E invocamos desde el navegador a este Java Servlet dos veces consecutivas (en menos de 5 segundos) primero con el parámetro 5 y luego con el parámetro 2: 158 MÓDULO C – Unidad 7: Java Servlets Avanzado http://localhost:8080/EjemplosWeb/ConcurrenciaServlet?param=5 http://localhost:8080/EjemplosWeb/ConcurrenciaServlet?param=2 Observemos en la salida por consola lo que ocurre: La primera petición la sirve un thread controlado por el Servidor de Aplicaciones Java EE que se llama “http-bio-8080-exec3” y el inicio de la ejecución del Java Servlet muestra que el parámetro con el que tiene que trabajar vale 5. Entonces, se duerme durante 5 segundos (ha sido la forma de simular una tarea costosa) y cuando vuelve a por el parámetro, resulta que vale 2. ¿Qué ha pasado? Lo que ha pasado es que antes de que el primer Java Servlet terminara de ejecutarse, el Servidor de Aplicaciones Java EE recibió una segunda petición al mismo Java Servlet con un valor del parámetro distinto. Como ambos dos threads, en este caso “http-bio8080-exec-4”, ejecutan el mismo código, el segundo ha machacado el valor al primero. Se trata de un ejemplo muy sencillo, pero el problema que demuestra puede ser muy complicado de detectar. Existen distintas soluciones: 159 Evitar en la medida de lo posible utilizar variables de instancia cuando su valor es de escritura/lectura. Implementar el interface javax.servlet.SingleThreadModel que le indica al Servidor de Aplicaciones Java EE, que no puede paralelizar las ejecuciones de este Java Servlet. Esta solución no es recomendable porque por regla general implica un tiempo de respuesta pésimo a los usuarios. Imaginad cien usuarios pidiendo este servicio a la vez y teniendo que esperar en cola a ser respondidos. 7.4 Utilizar técnicas de sincronización del lenguaje Java (synchronized). Acceso a Bases de Datos Nota: En esta sección se asume que el alumno tiene conceptos de programación avanzada en Java, en concreto, en la programación de acceso a Bases de Datos con JDBC. Existen distintas alternativas para el acceso a Bases de Datos desde aplicaciones web. Vamos a comentar dos de ellas: mediante el acceso directo a la Base de Datos desde el código y mediante un servicio que nos proveen los Servidores de Aplicaciones Java EE denominado Pool de Conexiones (o Data Source). La primera alternativa tiene bastantes problemas (uno de ellos es que tiene peor rendimiento que las otras alternativas) y no es para nada recomendada. Pero, no obstante, la veremos brevemente para entender mejor la naturaleza de una aplicación web, el por qué esta es una mala alternativa y cómo la segunda es una opción mucho mejor. 7.4.1 Acceso directo desde el código Este método supone ejecutar todos los pasos típicos del API JDBC en el código de nuestra aplicación: Cargar el Driver JDBC en la JVM. Crear una conexión a la Base de Datos. Crear una sentencia (de cualquiera de los tipos) y ejecutarla. Analizar el resultado de la sentencia. Cerrar y liberar los recursos. Con lo que conocemos ya del desarrollo de Java Servlets, podríamos pensar en colocar los distintos pasos en distintos lugares del código. Por ejemplo, una opción sería realizar la carga del Driver JDBC, la creación de la conexión a la Base de Datos y de la sentencia a ejecutar en el método de inicialización de Java Servlet (init). En los métodos de proceso de HTTP GET o HTTP POST (doGet o doPost) la ejecución 160 MÓDULO C – Unidad 7: Java Servlets Avanzado de la sentencia y análisis del resultado. Y por último, en el método de finalización del Java Servlet (destroy) incluiríamos el código de limpieza, cerrando y liberando los recursos. Ahora bien, esta opción tiene un inconveniente, y es que la sentencia será una variable de instancia (o atributo), y como ya hemos tratado en la sección anterior, debido a que las aplicaciones web son multiusuario (multithread) por naturaleza, tendríamos que tener en cuenta el acceso sincronizado a la variable. Podríamos pensar entonces en otra opción, y sería pasar todos los pasos a los métodos de proceso de HTTP GET y HTTP POST (doGet y doPost) dejando únicamente la carga del Driver JDBC en el método de inicialización (init). En este caso es cierto que no tendremos que estar pendientes de sincronizar el acceso a ninguna variable de instancia (o atributo), pero penalizamos enormemente el rendimiento (tiempo de respuesta) de la aplicación al estar conectando y desconectando constantemente con la Base de Datos. Vamos a ver un ejemplo de código para que se visualice mejor esta primera alternativa de acceso a Bases de Datos aunque como ya dijimos no es la recomendada. El ejemplo consiste en una aplicación web de consulta de títulos de libros mediante el código ISBN: Página HTML de inicio: <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Acceso a Bases de Datos</TITLE> </HEAD> <BODY> <FORM method="POST" action="/EjemplosWeb/BaseDatosServlet"> <TABLE border="0"> <TR> <TD>Introduzca el ISBN:</TD> <TD><INPUT name="isbn"></TD> </TR> <TR> <TD><INPUT type="submit" value="Buscar"></TD> </TR> </TABLE> </FORM> </BODY> </HTML> Java Servlet con la lógica: package es.aulamentor; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; 161 import java.sql.SQLException; import javax.servlet.ServletConfig; 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("/BaseDatosServlet") public class BaseDatosServlet extends HttpServlet { public void init(ServletConfig config) throws ServletException { try { Class.forName("org.apache.derby.jdbc.EmbeddedDriver"); } catch(ClassNotFoundException ex) { System.out.println("Driver JDBC no encontrado..."); ex.printStackTrace(); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; String isbn = request.getParameter("isbn"); try { con = DriverManager.getConnection("jdbc:derby:D:/biblioteca", "derbyuser","derbyuser"); pstmt = con.prepareStatement("SELECT * FROM PRESTAMOS WHERE ISBN = ?"); pstmt.setString(1,isbn); rs = pstmt.executeQuery(); if(rs.next()) { request.setAttribute("titulo", rs.getString(3)); } request.getRequestDispatcher("/resultDB.jsp").forward(request, response); } catch(SQLException ex) { System.out.println("Problemas en el acceso a la BD..."); ex.printStackTrace(); } finally { try { if(rs != null) rs.close(); } catch(SQLException ex) 162 MÓDULO C – Unidad 7: Java Servlets Avanzado { ex.printStackTrace(); } try { if(pstmt != null) pstmt.close(); } catch(SQLException ex) { ex.printStackTrace(); } try { if(con != null) con.close(); } catch(SQLException ex) { ex.printStackTrace(); } } } } Página JSP de resultado: <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Acceso a Bases de Datos</TITLE> </HEAD> <BODY> <% if(request.getAttribute("titulo") != null) { %> El título del libro es: <%= request.getAttribute("titulo") %> <% } else { %> Código ISBN no encontrado. <% } %> <BR><BR> <A href="/EjemplosWeb/indexDB.html">Volver atrás...</A> </BODY> </HTML> 163 En esta ocasión, nuestra aplicación está haciendo uso de los Drivers JDBC de una Base de Datos (en concreto de Apache Derby) por lo que tendremos que tener en cuenta añadir el fichero JAR que contiene dichas clases Java en nuestra aplicación. La estructura de un módulo Web según las especificaciones Java EE dictaba que este tipo de ficheros debían ubicarse en el directorio /WEB-INF/lib, que en un proyecto web de Eclipse IDE for Java EE Developers corresponde a /WebContent/WEB-INF/lib: Otra opción sería añadir el fichero a la instalación del Servidor de Aplicaciones Java EE en vez de añadirlo a cada módulo web que acceda a la Base de Datos. La configuración de esta opción es dependiente del servidor, es decir, dependiendo del servidor que estemos utilizando se hará de una manera u otra. En nuestro caso, que estamos utilizando Apache Tomcat 7.0.x, el fichero JAR se debe copiar en el directorio /lib de la instalación: 164 MÓDULO C – Unidad 7: Java Servlets Avanzado Independientemente de la opción de configuración elegida, la ejecución del ejemplo debería mostrar algo así… Página de inicio: Página de resultado: 165 No obstante, como ya hemos comentado, esta forma de acceder a Bases de Datos tiene graves problemas de rendimiento en el caso de tener muchos usuarios y además, si decidimos compartir la conexión y la sentencia a través de una variable de instancia (o atributo) para mejorar algo el rendimiento, tenemos que introducir en la programación técnicas de sincronización que no son triviales. Por este motivo, las especificaciones Java EE definieron el concepto de Pool de Conexiones (Data Source) que todo Servidor de Aplicaciones Java EE debe implementar y que estudiaremos a continuación. 7.4.2 Acceso a través de un Pool de Conexiones Todo Servidor de Aplicaciones Java EE, debe implementar un servicio de Pool de Conexiones a Bases de Datos que implemente el siguiente interface: javax.sql.DataSource Dicho interface tiene solo dos métodos que permiten el acceso a una conexión: Connection getConnection() throws SQLException; Devuelve una conexión a Base de Datos disponible del pool. Connection getConnection(String username, String password) throws SQLException; Devuelve una conexión a Base de Datos disponible del pool pero usando un usuario y contraseña específico (no necesariamente el que use por defecto el Data Source). Al tratarse de un servicio del Servidor de Aplicaciones Java EE, tendremos que crearlo y configurarlo en el servidor para que sea accesible desde nuestra aplicación web. Y evidentemente, esto no se hace en el código sino en el propio servidor. Cada Servidor de Aplicaciones Java EE tiene su propio mecanismo de administración, es decir, las especificaciones Java EE dictan qué servicios e interfaces deben implementar, 166 MÓDULO C – Unidad 7: Java Servlets Avanzado pero indican en cómo lo tienen que hacer. Por tanto, la forma de configurar el servidor dependerá del que estemos usando. A continuación, comentaremos cómo se configura un Pool de Conexiones en Apache Tomcat 7.0.x que es el Servidor de Aplicaciones Java EE que estamos utilizando a lo largo de este curso. Apache Tomcat 7.0.x basa toda su administración y configuración en ficheros XML que residen en el directorio /conf. Pero como en nuestro caso lo estamos usando integrado en Eclipse IDE for Java EE Developers, existe un proyecto especial adicional a los dedicados a nuestros desarrollos que se llama “Servers”. Ahí es donde residen los ficheros XML de configuración del Apache Tomcat 7.0.x que estamos utilizando. En concreto, para configurar un Pool de Conexiones nuevo, debemos editar el fichero de configuración context.xml, y a la sección ya existente <Context> </Context> añadirle el siguiente bloque “Resource”: <Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="derbyuser" password="derbyuser" 167 driverClassName="org.apache.derby.jdbc.EmbeddedDriver" url="jdbc:derby:D:/biblioteca" /> Este bloque “Resource” define y configura un Pool de Conexiones a la Base de Datos Apache Derby que estamos usando en nuestro ejemplo. Repasemos el significado de algunas de las propiedades de la configuración: name: identificador JNDI (Java Naming and Directory Interface) que utilizaremos para acceder al pool. type: tipo de interface que implementa el recurso (en nuestro caso javax.sql.DataSource). username: identificador de usuario en el caso de que el Gestor de Base de Datos que estamos utilizando tenga la seguridad activada. password: contraseña de usuario en el caso de que el Gestor de Base de Datos que estamos utilizando tenga la seguridad activada. driverClassName: nombre del Driver JDBC de acceso al Gestor de Base de Datos que estamos utilizando. url: URL JDBC. El resto de propiedades suelen estar relacionadas con las características más funcionales del Pool de Conexiones, temas como el número máximo de conexiones que puede tener, el tiempo máximo que una conexión puede estar asignada sin ser devuelta al pool, etc… Con esto, el Servidor de Aplicaciones Java EE ya va a crear y gestionar un Pool de Conexiones con nuestra Base de Datos. Ahora, nos queda indicar que nuestra aplicación web está interesada en el acceso a dicho pool y lo haremos mediante el descriptor de despliegue del módulo web (web.xml). Para ello, añadiremos el siguiente bloque “resource-ref”: <resource‐ref> <description>Conexion BD</description> <res‐ref‐name>jdbc/TestDB</res‐ref‐name> <res‐type>javax.sql.DataSource</res‐type> <res‐auth>Container</res‐auth> </resource‐ref> Simplemente hacemos referencia al recurso creado y configurado en el servidor, identificando básicamente el nombre (res-ref-name) y tipo (res-type). De esta manera, al desplegar la aplicación web, el Servidor de Aplicaciones Java EE nos dará acceso al recurso. Ya solo nos queda modificar el código fuente de nuestra aplicación para que ahora haga uso del Data Source en vez de crear las conexiones a la Base de Datos directamente. 168 MÓDULO C – Unidad 7: Java Servlets Avanzado Para buscar y acceder a un recurso definido en el Servidor de Aplicaciones Java EE se utiliza el API JNDI (Java Naming and Directory Interface). Es el API a través del cual podemos conectarnos al directorio de recursos del servidor y acceder a ellos. Dicho API se encuentra en el paquete Java: javax.naming; Su uso es muy sencillo y normalmente consta de dos pasos: Creación del Contexto de búsqueda: dirección IP, puerto del servicio, y punto de entrada en el directorio. Realización de la búsqueda del recurso y downcasting a su tipo concreto. Veamos un ejemplo típico: try { Context initialContext = new InitialContext(); DataSource datasource = (DataSource)initialContext.lookup("java:comp/env/jdbc/TestDB"); } catch(NamingException ex) { System.out.println("Problemas en el acceso al recurso..."); ex.printStackTrace(); } En el ejemplo creamos un Contexto con los valores por defecto, es decir, acceso al servicio del propio Servidor de Aplicaciones Java EE en el que la aplicación se está ejecutando desde la raíz del directorio. Si hubiéramos querido conectar con un servicio JNDI de otro Servidor de Aplicaciones Java EE remoto, tendríamos que haber creado el Contexto con los parámetros adecuados. Y a continuación, realizamos la búsqueda del recurso con el nombre “jdbc/TestDB”, que nos es devuelto como un java.lang.Object y de ahí que debamos hacer el downcasting a javax.sql.DataSource. Si no se encontrase el recurso por el motivo que fuese (el nombre está mal, no está bien configurado en el servidor, etc…) se producirá una excepción javax.naming.NamingException que deberemos tratar según la lógica de nuestra aplicación. El lugar dentro del servicio JNDI donde se ubican siempre todos los recursos accesibles a las aplicaciones web es: java:comp/env, de ahí que la búsqueda no haya sido solo de “jdbc/TestDB” sino que ha sido de “java:comp/env/jdbc/TestDB”. Por último, ya solo quedaría pedirle al Data Source una conexión disponible a la Base de Datos a través de uno de los métodos comentados al inicio de este apartado. En este punto se encuentra la clave de esta alternativa de programación de acceso a Bases de Datos. En este caso, al contrario que ocurría cuando la conexión se la pedíamos al DriverManager, la conexión ya está establecida con el Gestor de Base de Datos con lo que el tiempo de respuesta es mucho mayor. Además, varias ejecuciones paralelas de nuestra aplicación 169 pueden estar accediendo a la Base de Datos en paralelo, no como antes que el acceso era sincronizado (y por tanto serializado) a una única conexión. No obstante, al igual que cuando accedíamos directamente a la Base de Datos desde el código, es importante liberar los recursos cuando se han dejado de utilizar. En este caso, la diferencia es que la llamada al método “close” de la conexión, en vez de cerrarla lo que hace es devolverla al Pool de Conexiones como disponible para poder ser utilizada por otra petición. Veamos la aplicación de ejemplo de antes, pero esta vez utilizando un Pool de Conexiones. Descriptor de despliegue: <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <servlet> <servlet‐name>DataSourceServlet</servlet‐name> <servlet‐class>es.aulamentor.DataSourceServlet</servlet‐class> </servlet> <servlet‐mapping> <servlet‐name>DataSourceServlet</servlet‐name> <url‐pattern>/DataSourceServlet</url‐pattern> </servlet‐mapping> <resource‐ref> <description>Conexion BD</description> <res‐ref‐name>jdbc/TestDB</res‐ref‐name> <res‐type>javax.sql.DataSource</res‐type> <res‐auth>Container</res‐auth> </resource‐ref> </web‐app> Página HTML de inicio: <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Acceso a Bases de Datos</TITLE> </HEAD> <BODY> <FORM method="POST" action="/EjemplosWeb/DataSourceServlet"> <TABLE border="0"> <TR> <TD>Introduzca el ISBN:</TD> <TD><INPUT name="isbn"></TD> </TR> <TR> <TD><INPUT type="submit" value="Buscar"></TD> </TR> </TABLE> </FORM> 170 MÓDULO C – Unidad 7: Java Servlets Avanzado </BODY> </HTML> Java Servlet: package es.aulamentor; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.sql.DataSource; public class DataSourceServlet extends HttpServlet { DataSource datasource = null; public void init(ServletConfig config) throws ServletException { try { Context initialContext = new InitialContext(); datasource = (DataSource)initialContext.lookup("java:comp/env/jdbc/TestDB"); } catch(NamingException ex) { System.out.println("Problemas en el acceso a la Base de Datos..."); ex.printStackTrace(); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; String isbn = request.getParameter("isbn"); try { con = datasource.getConnection(); pstmt = con.prepareStatement("SELECT * FROM PRESTAMOS WHERE ISBN = ?"); pstmt.setString(1,isbn); rs = pstmt.executeQuery(); if(rs.next()) { request.setAttribute("titulo", rs.getString(3)); 171 } request.getRequestDispatcher("/resultDS.jsp").forward(request, response); } catch(SQLException ex) { System.out.println("Problemas en el acceso a la Base de Datos..."); ex.printStackTrace(); } finally { try { if(rs != null) rs.close(); } catch(SQLException ex) { ex.printStackTrace(); } try { if(pstmt != null) pstmt.close(); } catch(SQLException ex) { ex.printStackTrace(); } try { if(con != null) con.close(); } catch(SQLException ex) { ex.printStackTrace(); } } } } Página JSP de resultado: <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Acceso a Bases de Datos</TITLE> </HEAD> <BODY> <% if(request.getAttribute("titulo") != null) { %> El título del libro es: <%= request.getAttribute("titulo") %> 172 MÓDULO C – Unidad 7: Java Servlets Avanzado <% } else { %> Código ISBN no encontrado. <% } %> <BR><BR> <A href="/EjemplosWeb/indexDS.html">Volver atrás...</A> </BODY> </HTML> La ejecución del ejemplo debería mostrar exactamente los mismos resultados que antes. Es decir, el cómo accedamos a la Base de Datos no afecta en nada a la presentación de la aplicación aunque seguramente si se notará mejor rendimiento (la búsqueda se hará más rápida). Página de inicio: Página de resultado: 173 Ahora bien, como ya hemos comentado en otras ocasiones, a partir de Java EE 6.0 existe otra alternativa al descriptor de despliegue y código Java. Son las anotaciones Java. Veamos cómo quedaría el mismo ejemplo usando la anotación @Resource. En vez de escribir las líneas de código necesarias para realizar la búsqueda: public class DataSourceServlet extends HttpServlet { DataSource datasource = null; public void init(ServletConfig config) throws ServletException { try { Context initialContext = new InitialContext(); datasource = (DataSource)initialContext.lookup("java:comp/env/jdbc/TestDB"); } catch(NamingException ex) { System.out.println("Problemas en el acceso a la BD..."); ex.printStackTrace(); } } podríamos simplemente usar esta anotación: public class DataSourceServlet extends HttpServlet { @Resource(name="java:comp/env/jdbc/TestDB") DataSource datasource; teniendo en cuenta importar el paquete: javax.annotation; Cualquiera de las dos opciones es válida. Adicionalmente, existe otra anotación muy útil sobre todo para los entornos de desarrollo locales que permite realizar la definición de los DataSources sin necesidad de tocar los ficheros de configuración de los Servidores de Aplicación Java EE. Es mediante la anotación @DataSourceDefinition que requiere importar el paquete: javax.annotation.sql; Nota: esta opción no está disponible en el servidor Apache Tomcat 7.0.x que utilizamos en este curso (que como recordaremos, no implementaba el 100% de las especificaciones Java EE, sola las relacionadas con la capa web), pero no obstante la comentamos: @DataSourceDefinition(name = "java:comp/env/jdbc/TestDB", className = "org.apache.derby.jdbc.EmbeddedDriver", url = "jdbc:derby:D:/biblioteca") public class DataSourceServlet extends HttpServlet { @Resource(name=" java:comp/env/jdbc/TestDB ") DataSource datasource; 174 MÓDULO C – Unidad 7: Java Servlets Avanzado En este ultimo ejemplo, vemos como en el propio código realizamos la definición del DataSource. Los Servidores de Aplicación Java EE, al encontrar este código, dinámicamente crean el DataSource sin necesidad de haberlo definido en la configuración del servidor. Esta manera de definirlos está recomendada únicamente en los entornos locales de los desarrolladores, para facilitar y agilizar las pruebas. Pero no es la manera recomendada de configurarlos en los entornos reales de ejecución donde deberán ser los administradores los que decidan cómo configurarlos. El uso de Pool de Conexiones, siempre lleva relacionado temas de “tunning” (ajustes de la configuración para mejorar el rendimiento), pero este es un tema que trasciende el ámbito de este curso y que además es muy dependiente del Servidor de Aplicaciones Java EE que se esté utilizando. Pero si es interesante que recuerdes que es importante que los administradores del servidor realicen pruebas de carga y analicen cómo afecta al rendimiento de la aplicación el número máximo de conexiones disponible en el pool entre otros parámetros, decidiendo el valor óptimo en cada caso. 7.5 Filtros Un filtro es un componente web más, que a diferencia de los que hemos visto hasta ahora no son invocados directamente. Son unos componentes que se despliegan como parte de la aplicación Web, y cuya misión es interceptar las peticiones y respuesta de un componente concreto para por ejemplo, realizar algún tipo de validación, algún tipo de auditoria, algún tipo de transformación, etc… Es decir, sin que el usuario lo sepa, antes y después de la ejecución de un componente web se ejecuta este nuevo componente. Un filtro de implementar el interface: javax.servlet.Filter que incluye tres métodos: void init(FilterConfig filterConfig) throws ServletException; Se invoca una única vez en la vida del filtro y se utiliza para realizar las labores de inicialización. El parámetro del tipo javax.servlet.FilterConfig da acceso a cualquier atributo de configuración del filtro que se haya especificado en el descriptor de despliegue o en las anotaciones. void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException; Es el método que realiza la lógica específica del filtro: auditoria, transformaciones, etc… A través del último parámetro del tipo javax.servlet.FilterChain podremos renviar la petición al siguiente filtro en la cadena si lo hubiera, o al componente web destino que estábamos filtrando. void destroy(); 175 Nuevamente, se invoca una única vez en la vida del filtro y se utiliza para realizar las labores de limpieza y liberación de recursos si fuese necesario. Como vemos, es muy parecido a un Java Servlet con la diferencia de que no son invocados expresamente por la presentación, sino que se ejecutan cuando el componente web al que filtran es invocado. Desde la aparición de Java EE 6.0 existen dos maneras de declarar los filtros y asociarlos a unos componentes web determinados: el descriptor de despliegue y las anotaciones Java. Veamos primero cómo se definen y configuran en el descriptor de despliegue, el fichero web.xml Definimos el filtro a través del bloque “filter” con sus bloques “filter-name” y “filter-class”. Y configuramos qué componentes web filtra mediante el bloque “filter-mapping” con sus bloques “filter-name” y “url-pattern”. Por ejemplo: <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <servlet> <servlet‐name>DataSourceServlet</servlet‐name> <servlet‐class>es.aulamentor.DataSourceServlet</servlet‐class> </servlet> <servlet‐mapping> <servlet‐name>DataSourceServlet</servlet‐name> <url‐pattern>/DataSourceServlet</url‐pattern> </servlet‐mapping> <filter> <filter‐name>AuditoriaFilter</filter‐name> <filter‐class>es.aulamentor.AuditoriaFilter</filter‐class> </filter> <filter‐mapping> <filter‐name>AuditoriaFilter</filter‐name> <url‐pattern>/*</url‐pattern> </filter‐mapping> </web‐app> En este ejemplo, además de un Java Servlet de un ejemplo anterior, tenemos un Filtro que se llama AuditoriaFilter implementado mediante la clase es.aulamentor.AuditoriaFilter que filtra (es decir, intercepta las peticiones) cualquier componente web de la aplicación. A través del patrón de URL, podríamos afinar un poco más el filtrado. Por ejemplo, si queremos que solo filtre los ficheros HTML, podríamos poner *.html en vez de /*, o /RequestServlet si lo que queremos es filtrar a ese Java Servlet o JavaServer Page concreta. Hay muchas posibilidades. 176 MÓDULO C – Unidad 7: Java Servlets Avanzado A continuación, vamos a ver el código fuente del filtro de este ejemplo, que se limita a escribir en consola la dirección IP que realiza la petición y a qué hora, y la hora en la que se envía la respuesta: package es.aulamentor; import java.io.IOException; import java.util.Calendar; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class AuditoriaFilter implements Filter { public void init(FilterConfig fConfig) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Calendar time = Calendar.getInstance(); System.out.println("Acceso desde IP: " + request.getRemoteAddr() + " a las " + time.get(Calendar.HOUR) + ":" + + time.get(Calendar.MINUTE)); chain.doFilter(request, response); System.out.println("Contestado a las " + time.get(Calendar.HOUR) + ":" + + time.get(Calendar.MINUTE)); } public void destroy() { } } Es muy importante siempre realizar la llamada al siguiente miembro de la cadena (que puede ser otro filtro o el componente web final) mediante el método doFilter() del parámetro FilterChain. Aunque también existe la posibilidad de desviar la ejecución, invocando a otro componente web (por ejemplo de error) a través del RequestDispatcher como ya sabemos hacer. Es decir, que un filtro puede decidir en un momento dado interrumpir la ejecución normal de la petición y desviarla por el motivo que sea a otro componente. La ejecución en el entorno de desarrollo debería mostrar algo así: 177 Como mencionamos, desde Java EE 6.0 también existe la opción de obviar el descriptor de despliegue y utilizar anotaciones Java para las definiciones y configuraciones. En este caso, usaremos la anotación @WebFilter con sus atributos “filterName” y “urlPatterns”: package es.aulamentor; import java.io.IOException; import java.util.Calendar; 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; @WebFilter(filterName="AuditoriaFilter", urlPatterns={"/*"}) public class AuditoriaFilter implements Filter { public void init(FilterConfig fConfig) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse 178 MÓDULO C – Unidad 7: Java Servlets Avanzado response, FilterChain chain) throws IOException, ServletException { Calendar time = Calendar.getInstance(); System.out.println("Acceso desde IP: " + request.getRemoteAddr() + " a las " + time.get(Calendar.HOUR) + ":" + + time.get(Calendar.MINUTE)); chain.doFilter(request, response); System.out.println("Contestado a las " + time.get(Calendar.HOUR) + ":" + + time.get(Calendar.MINUTE)); } public void destroy() { } } El resultado debería ser el mismo. En casos de que existiese más de un filtro, y que además filtrasen al mismo componente web, su ejecución se realizaría en el orden en el que fueron definidos. 7.6 Mantenimiento del estado del cliente El protocolo HTTP es un protocolo sin estado, es decir, cada petición que se recibe en el servidor de un mismo cliente se recibe como si fuese la primera petición que hace. Es decir, no hay constancia de que ya se hubiese conectado antes y hubiera solicitado una acción. Este es un inconveniente importante. Supone por ejemplo, que aunque un cliente ya se hubiera autenticado con el sistema, como la próxima petición parece una petición nueva pediría autenticarse de nuevo. O que un cliente que mediante una petición anterior movió un producto al carrito de la compra en una aplicación de comercio electrónico, en la siguiente petición el carrito aparece vacío. Por suerte, el protocolo HTTP contempla la posibilidad de almacenar en el lado cliente información del servidor, de manera que dicha información viaje a partir de entonces en cada nueva petición, y tanto la aplicación Web como el Servidor de Aplicaciones Java EE puedan usarla para recordar al cliente. Esta información que se puede almacenar en el lado cliente recibe el nombre de Cookie. 7.6.1 Cookies Básicamente, una Cookie es una pareja formada por una clave y un valor que contiene información textual. Es decir, tiene un nombre que identifica la Cookie y un valor asociado, junto con alguna información relacionada adicional que ahora comentaremos sobre cuándo caduca, quién la creó, quién la puede ver, etc… 179 El hecho de que se puedan crear desde el servidor, es decir, desde una aplicación web, las hace muy útiles para poder “recordar” los clientes y evitar interacciones redundantes, pérdidas de información y similares. Normalmente (a no ser que tengamos el navegador configurado para evitarlas) en nuestro navegador tendremos decenas de Cookies guardadas sin que fuéramos conscientes de ello. Por ejemplo, si utilizas Mozilla Firefox ves a la opción de menú “Firefox” -> “Options” -> “Options”: En la opción “Privacy”, selecciona “Use custom settings for history” en la sección “history”, y entonces pulsa el botón “Show Cookies…”: 180 MÓDULO C – Unidad 7: Java Servlets Avanzado Te sorprenderá ver todas las que tienes (incluyendo Cookies de Aula Mentor): 181 En el caso de Internet Explorer, es un poco más complicado. Ir a la opción de menú “tools” > “Internet Options”: Pulsar el botón “Settings” de la sección “Browsing history”: 182 MÓDULO C – Unidad 7: Java Servlets Avanzado Y en la nueva ventana, pulsar el botón “View files”: 183 Se abrirá el explorador de archivos, y tendremos que buscar todos aquellos que empiezan por Cookie: 184 MÓDULO C – Unidad 7: Java Servlets Avanzado Bien, veamos qué información guarda una Cookie antes de meternos con el API Java para crearlas y acceder a ellas desde nuestras aplicaciones: Nombre: Identificador de la Cookie para poder ser accedida. Contenido: La información en modo texto que guarda. Servidor (o Host): Servidor que creo la Cookie. Ruta (o Path): Dentro de ese Servidor, a qué rango de URLs les mandará el navegador la Cookie (un / significa que a todas). Segura: Indica si debe ser enviada exclusivamente a través de conexiones seguras (HTTPS). Caducidad: Indica la fecha en la que caduca y por tanto es borrada. Se puede indicar en vez de una fecha, que se borre cuando se cierre el navegador. 185 Las Cookies están implementadas en Java por la clase: javax.servlet.http.Cookie; Las Cookies ya existentes son accesibles a través de la petición (javax.servlet.http.HttpServletRequest) mediante el método: Cookie[] getCookies(); Y las nuevas, una vez creadas se deben incluir en la respuesta (javax.servlet.http.HttpServletResponse) mediante el método: void addCookie(Cookie cookie); Para crear una Cookie nueva utilizaremos su constructor, pasándole la información que ya hemos comentado: public Cookie(java.lang.String name, java.lang.String value); A su vez, la clase Cookie tiene una serie de métodos para acceder o establecer los distintos valores que hemos comentado. Lo más sencillo es verlo en un ejemplo. Vamos a desarrollar una aplicación web, que implementa un contador de visitas por parte de un mismo cliente independientemente de que cerremos el navegador mediante el uso de una Cookie: <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Contador</TITLE> </HEAD> <BODY> <% Cookie cookie = null; Cookie[] cookies = request.getCookies(); if(cookies != null) { for(int i=0; i<cookies.length; i++) { if(cookies[i].getName().equals("CONTADOR")) { cookie = cookies[i]; break; } } } if(cookie == null) { cookie = new Cookie("CONTADOR","0"); } int cont = Integer.parseInt(cookie.getValue()); cookie.setValue(Integer.toString(cont+1)); cookie.setMaxAge(604800); // Una semana en segundos. 186 MÓDULO C – Unidad 7: Java Servlets Avanzado response.addCookie(cookie); %> Número de veces visitada esta página: <%= cookie.getValue() %> </BODY> </HTML> Al ejecutar la aplicación desde un navegador y recargar (o refrescar) la página varias veces, veremos que el contador sigue contando donde se quedó. Es mas, si paramos el servidor y cerramos el navegador y volvemos a rearrancar todo, el contador debería seguir por donde iba también. Sin embargo, si utilizamos otro navegador distinto o el mismo pero desde otra máquina de nuestra red, veremos que el contador comienza en cero porque ese navegador no tiene la Cookie guardada. Y si le pedimos al navegador que nos muestre todas las Cookies que tiene (filtramos por CONTADOR) veremos todos los datos de nuestra Cookie. 187 Hay varios puntos importantes que tener presentes de este ejemplo: Por defecto, el tiempo de expiración de la Cookie es el de la vida del navegador (tiene el mismo efecto asignarle un valor negativo), es decir, cuando este se cierra se borra. Asignarle un valor de cero borraría inmediatamente la Cookie. Y asignarle cualquier otro valor positivo. es la nueva vida de la Cookie en segundos. En nuestro ejemplo, hemos establecido que sea una semana tras cada uso. Cada vez que se modifica algo de la Cookie, hay que añadirla de nuevo a la respuesta. Si no se añade, la Cookie sigue con los valores antiguos porque la respuesta contiene una copia de la Cookie recibida y no una referencia. Por defecto, la ruta (o path) del servidor al que el navegador mandará la Cookie es el de la aplicación que creo la Cookie. En nuestro caso, /EjemplosWeb/. Si el navegador conecta con otra aplicación en otra ruta o en otro servidor, la Cookie no se enviará. Aunque el ejemplo que hemos utilizado para presentar las Cookies es muy simple, la potencia de esta tecnología es inmensa cuando pensamos en empresas de comercio electrónico, de marketing, etc… Todas ellas llenan nuestro navegador de Cookies con identificadores que les permiten a nuestro regreso recordar nuestros gustos, lo último que hicimos en su aplicación, etc… 7.6.2 Sesión HTTP Las Cookies por tanto, son un mecanismo muy potente para poder recordar al usuario y actuar en consecuencia. Pero tiene algunos inconvenientes técnicos como el hecho de solo 188 MÓDULO C – Unidad 7: Java Servlets Avanzado poder albergar 4Kb de información y que dicha información solo puede ser textual no muy estructurada (recordemos que el valor en una Cookie es una simple cadena de caracteres). Adicionalmente, no son muy seguras. Los usuarios pueden ver el contenido, pueden borrarlas e incluso pueden modificarlas con mínimos conocimientos de informática. Por este motivo, aunque basándose en la existencia de las Cookies, las especificaciones Java EE definieron un mecanismo de persistencia en el servidor denominado Sesión HTTP (o HTTP Session). Todos los Servidores de Aplicación Java EE implementan este servicio. La sesión HTTP es una zona de almacenaje en el servidor exclusiva de cada cliente, donde las aplicaciones pueden dejar información para ser utilizada en sucesivas peticiones del mismo cliente. Cada vez que se recibe una petición, el servidor analiza las cabeceras HTTP del mensaje en busca de una Cookie que incluya un identificador de sesión para ponerla a disposición de la aplicación. Normalmente dicha Cookie se llama JSESSIONID y es gestionada automáticamente por el servidor. Las aplicaciones manejarán la Sesión HTTP desde un punto de vista funcional, los detalles técnicos de su mantenimiento quedan cubiertos por el servicio ofrecido por el servidor. La Sesión HTTP implementa el interface: javax.servlet.http.HttpSession; Es accesible desde la petición HTTP mediante el método: HttpSession getSession(); que nos devolverá una referencia a la sesión de este cliente si ya existiese o a una nueva creada en este instante y asignada a este cliente. Existe otra opción, que es el método: HttpSession getSession(boolean create); La diferencia con el anterior, es que este recibe un boolean como parámetro. Si le mandamos “true”, en caso de no existir sesión para este cliente nos devuelve la referencia a una nueva. Si le mandamos “false”, nuevamente en caso de no existir, esta vez no la crea y devuelve null. Técnicamente la implementación de la Sesión HTTP es una especia de colección al estilo de las Hashtables y HashMaps. Es decir, es una colección en la que se guardan parejas de clave y valor, teniendo que ser ambas del tipo java.lang.Object (es decir, tipos complejos). Tiene distintos métodos, pero los más usados son: java.lang.Object getAttribute(java.lang.String name); Devuelve el valor de un atributo concreto. java.util.Enumeration<java.lang.String> getAttributeNames(); 189 Devuelve una colección con el nombre de todos los atributos que contiene. void setAttribute(java.lang.String name, java.lang.Object value); Guarda un atributo con su valor. void removeAttribute(java.lang.String name); Elimina un atributo. void invalidate(); Elimina la sesión del servidor. Recordemos que su creación no se realiza a través de un constructor sino simplemente pidiéndosela a la petición HTTP. Los Servidores de Aplicación Java EE, implementan un sistema de expiración de sesiones inactivas. El motivo es que la sesión consume recursos y no se puede garantizar ni que la aplicación web implemente un sistema de limpieza (invocando el método invalidate() cuando un usuario termine de trabajar con la aplicación), ni que el usuario de las aplicación web siempre termine de manera ordenada la aplicación invocando la petición de limpieza (por ejemplo, un logout) en vez de cerrar el navegador directamente. Sin este sistema de expiración, podríamos terminar con “infinitas” sesiones huérfanas en el servidor. Este tiempo de expiración, es un parámetro de configuración propio de cada servidor. En el caso de Apache Tomcat 7.0.x se realiza en el fichero de configuración /conf/web.xml que por defecto viene con media hora de tiempo: <session‐config> <session‐timeout>30</session‐timeout> </session‐config> Todos los objetos que se guarden en la sesión, deben ser serializables (es decir, implementar java.io.Serializable). El motivo es que los Servidores de Aplicación Java EE, normalmente implementan mecanismos de persistencia de las sesiones para facilitar mecanismos de balanceo de carga y tolerancia a fallos, para liberar recursos de memoria, etc.. y si el contenido no es serializable no lo puede guardar. Como buena práctica, la información guardada en la Sesión HTTP debe de ser lo más pequeña posible. Veamos un ejemplo similar al que mostramos con las Cookies, pero en esta ocasión añade un botón para invalidar la sesión y comprobar como se pierde la información del contador y comienza de nuevo en uno. <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Contador</TITLE> 190 MÓDULO C – Unidad 7: Java Servlets Avanzado </HEAD> <BODY> <% HttpSession sesion = request.getSession(true); int cont = 1; boolean isInvalidate = false; if(request.getParameter("invalidate") != null && request.getParameter("invalidate").equals("true")) { sesion.invalidate(); isInvalidate = true; } else { if(sesion.getAttribute("CONTADOR") != null) { cont = ((Integer)sesion.getAttribute("CONTADOR")).intValue(); } sesion.setAttribute("CONTADOR", new Integer(cont+1)); } %> <FORM method="POST" action="/EjemplosWeb/sesion.jsp" > <TABLE border="0"> <% if(!isInvalidate) { %> <TR> <TD>Número de veces visitada esta página: <%= cont %></TD> </TR> <% } %> <TR> <TD>Invalidar sesión: <INPUT type="checkbox" name="invalidate" value="true"></TD> </TR> <TR> <TD><INPUT type="submit" value="Refrescar"></TD> </TR> </TABLE> </FORM> </BODY> </HTML> Al ejecutar el ejemplo desde un navegador externo, deberíamos ver algo así: 191 Y si le pedimos al navegador que nos muestre todas las Cookies que tiene (filtramos por localhost) veremos la Cookie JSESSIONID que el Servidor de Aplicaciones Java EE ha creado para gestionar la Sesión HTTP. Si invalidamos la sesión mediante la checkbox, además de comprobar que el contador se resetea porque su valor se pierde al destruirse la sesión, podremos comprobar como la 192 MÓDULO C – Unidad 7: Java Servlets Avanzado Cookie JSESSIONID desaparece. De igual modo, si seguimos ejecutando el ejemplo creando una nueva sesión, el valor de la Cookie (el identificador de la sesión en el servidor) cambiará. Otro tema importante a tener en cuenta, es que la Cookie con el identificador de la Sesión HTTP caduca con la vida del navegador, es decir, si cerramos el navegador, al abrirlo de nuevo y solicitar la aplicación, se crea una sesión nueva. Da igual que no la hubiéramos invalidado antes de cerrar. Posiblemente cuando entremos de nuevo en el servidor, siga existiendo la anterior que todavía no habrá caducado por el tiempo máximo de inactividad pero ya no está relacionada con ningún cliente (está huérfana). Esta es una desventaja importante frente a las Cookies, ya que en las Cookies controladas por nuestra aplicación, la expiración era controlable. Por eso, normalmente un carrito de la compra, se implementa mediante la Sesión HTTP porque no tiene sentido que permanezca tras haber realizado una compra. Mientras que los gustos o preferencias de un cliente se controlan con una Cookie para que perdure entre compras (normalmente se mantiene un identificador de algún registro en Base de Datos donde se han ido almacenando todas la navegaciones del cliente por la tienda). Indicar que en ambos dos casos, las Cookies son utilizadas. En el primer caso la Cookie guarda toda la información del estado del cliente que queremos mantener, mientras que en la segunda guarda solamente el identificador de la Sesión HTTP del servidor que guarda la información que queremos mantener de cada cliente. 7.7 Gestión de eventos Las especificaciones Java EE definen una serie de notificación de eventos a los que las aplicaciones web se pueden suscribir para ser notificadas de cuándo dichos eventos ocurren. El funcionamiento de la gestión de eventos en las aplicaciones web funciona igual que en las aplicaciones visuales de escritorio (AWT/Swing). Es decir, siguen más o menos el siguiente diagrama: 193 Básicamente, contamos con dos piezas fundamentales. Un notificador (Notifier), que es quién generará los eventos, y un escuchador (Listener) que es quien está interesado en dichos eventos para reaccionar y actuar en consecuencia. Para que un escuchador, pueda escuchar los eventos de un notificador, ha de subscribirse a estos. En caso de estar subscrito, el notificador, cuando ocurra el evento, se lo notificará al escuchador. En Java, esto significa enviar un mensaje concreto al escuchador (ejecutar un método concreto). El mecanismo que tenemos en Java de poder garantizar que el escuchador implementa dichos métodos, es forzándole a implementar un interfaz. Y esto lo consigue mediante el API del método de subscripción, que solo permite recibir parámetros de un tipo concreto. Luego el escuchador interesado ha de ser de dicho tipo para poder subscribirse. Un escuchador, igual que se subscribe, puede finalizar la subscripción. Los notificadores pasan la información a los escuchadores invocando un método concreto como ya hemos comentado, pasándole como parámetro un evento que encapsula toda la información necesaria para conocer lo que ha ocurrido. Los eventos contienen información sobre lo que ha ocurrido, accesible a través de distintos métodos. Ahora bien, existe una diferencia muy importante de operativa con las aplicaciones Java SE (visuales de escritorio), y es que en Java EE no hay que invocar un método para suscribirse una familia de eventos (addXXXListener), sino que basta con: 194 O informar de quién es el escuchador en el descriptor de despliegue. O utilizar una anotación Java en el código del escuchador. MÓDULO C – Unidad 7: Java Servlets Avanzado Adicionalmente, no hay forma de finalizar la subscripción (removeXXXListener) como ocurre con las aplicaciones visuales. Pero por lo demás, el mecanismo de funcionamiento es el mismo. Antes de ver código, vamos a revisar las familias de eventos que existen con sus métodos. En concreto, existen tres familias de eventos: Relacionadas con el Contexto del Java Servlet: por ejemplo, cuándo se arranca o se para la aplicación. Relacionadas con la petición: por ejemplo, cuándo se comienza a procesar una petición. Relacionadas con la Sesión HTTP: por ejemplo, cuándo se invalida una sesión. Los tipos de eventos que existen son los siguientes: javax.servlet.ServletContextEvent Cuando se arranca o para una aplicación web. javax.servlet.ServletContextAttributeEvent Cuando se crea, modifica o elimina un atributo del contexto del Java Servlet. javax.servlet.http.HttpSessionEvent Cuando se crea o se destruye una sesión. También cuando se persiste y elimina de la memoria, y cuando se restaura de nuevo en memoria. javax.servlet.http.HttpSessionBindingEvent Cuando se añade, modifica o eliminan atributos de la sesión. También cuando un objeto ha sido añadido o eliminado de la sesión. javax.servlet.ServletRequestEvent Cuando se inicializa o destruye una petición. javax.servlet.ServletRequestAttributeEvent Cuando se añade, modifica o elimina un atributo de la petición. A continuación añadiremos una tabla con un listado de todos los interfaces que hay para cada una de las familias de eventos que hemos visto, y sus métodos: javax.servlet.ServletContextListener contextDestroyed contextInitialized attributeAdded javax.servlet.ServletContextAttribute attributeRemoved attributeReplaced javax.servlet.http.HttpSessionActivationListener javax.servlet.http.HttpSessionBindingListener sessionDidActivate sessionWillPassivate valueBound valueUnbound 195 attributeAdded javax.servlet.http.HttpSessionAttributeListener attributeRemoved attributeReplaced javax.servlet.ServletRequestListener requestDestroyed requestInitialized attributeAdded javax.servlet.ServletRequestAttributeListener attributeRemoved attributeReplaced No nos detendremos en explicar uno a uno cada método. El API Java EE esta disponible en la web, así que no lo reproduciremos por completo aquí. Veamos eso si, cómo se suscribe una clase a un notificador mediante el descriptor de despliegue y mediante anotaciones Java: Mediante descriptor de despliegue. Utilizaremos el bloque “listener” con su bloque “listener-class”: <listener> <listener‐class> es.aulamentor.EjemploListener </listener‐class> </listener> Y la clase es.aulamentor.EjemploListener ya implementará los interfaces necesarios para las acciones que quiera escuchar. Mediante anotaciones Java (recordemos que esta opción solo está disponible desde Java EE 6.0). Utilizaremos la anotación @Weblistener import javax.servlet.annotation.WebListener; @WebListener public class EjemploListener implements Y la clase es.aulamentor.EjemploListener ya implementará los interfaces necesarios para las acciones que quiera escuchar. Veamos un ejemplo que escucha todos los eventos posibles, y muestra por consola una línea por cada evento que ha ocurrido. Lo ejecutaremos junto con la aplicación que demostraba el uso de la Sesión HTTP para ver qué eventos se van lanzando: package es.aulamentor; import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletRequestAttributeEvent; import javax.servlet.ServletRequestAttributeListener; import javax.servlet.ServletRequestEvent; 196 MÓDULO C – Unidad 7: Java Servlets Avanzado import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; @WebListener public class EjemploListener implements ServletContextListener, ServletContextAttributeListener, HttpSessionListener, HttpSessionAttributeListener, HttpSessionActivationListener, HttpSessionBindingListener, ServletRequestListener, ServletRequestAttributeListener { public void requestDestroyed(ServletRequestEvent ev) { System.out.println("Se ha invocado requestDestroyed..."); } public void attributeAdded(HttpSessionBindingEvent ev) { System.out.println("Se ha invocado attributeAdded..."); } public void contextInitialized(ServletContextEvent ev) { System.out.println("Se ha invocado contextInitialized..."); } public void sessionDidActivate(HttpSessionEvent ev) { System.out.println("Se ha invocado sessionDidActivate..."); } public void valueBound(HttpSessionBindingEvent ev) { System.out.println("Se ha invocado valueBound..."); } public void attributeAdded(ServletContextAttributeEvent ev) { System.out.println("Se ha invocado attributeAdded..."); } public void attributeRemoved(ServletContextAttributeEvent ev) { System.out.println("Se ha invocado attributeRemoved..."); } public void sessionDestroyed(HttpSessionEvent ev) { System.out.println("Se ha invocado sessionDestroyed..."); } public void attributeRemoved(HttpSessionBindingEvent ev) { System.out.println("Se ha invocado attributeRemoved..."); } 197 public void attributeAdded(ServletRequestAttributeEvent ev) { System.out.println("Se ha invocado attributeAdded..."); } public void valueUnbound(HttpSessionBindingEvent ev) { System.out.println("Se ha invocado valueUnbound..."); } public void sessionWillPassivate(HttpSessionEvent ev) { System.out.println("Se ha invocado sessionWillPassivate..."); } public void sessionCreated(HttpSessionEvent ev) { System.out.println("Se ha invocado sessionCreated..."); } public void attributeReplaced(HttpSessionBindingEvent ev) { System.out.println("Se ha invocado attributeReplaced..."); } public void attributeReplaced(ServletContextAttributeEvent ev) { System.out.println("Se ha invocado attributeReplaced..."); } public void attributeRemoved(ServletRequestAttributeEvent ev) { System.out.println("Se ha invocado attributeRemoved..."); } public void contextDestroyed(ServletContextEvent ev) { System.out.println("Se ha invocado contextDestroyed..."); } public void attributeReplaced(ServletRequestAttributeEvent ev) { System.out.println("Se ha invocado attributeReplaced..."); } public void requestInitialized(ServletRequestEvent ev) { System.out.println("Se ha invocado requestInitialized..."); } } La ejecución en el entorno de desarrollo debería mostrar algo así: 198 MÓDULO C – Unidad 7: Java Servlets Avanzado 199 200 PARA RECORDAR La información de configuración de un Java Servlet que es recogida mediante el método getInitParameter del parámetro javax.servlet.ServletConfig, que es pasado al método init del Java Servlet. Para pasar dicha información, el administrador puede hacerlo en el despliegue de la aplicación o el desarrollador puede hacerlo en el descriptor de despliegue o mediante anotaciones. Aunque el servidor de aplicaciones Java EE permite y posibilita la concurrencia en las llamadas a un Java Servlet, hay que tener cuidado con la definición de atributos o código que no se quiera que sea multithread. Hemos visto dos alternativas de acceso a Bases de datos: Acceso directo desde el código. Su principal problema es su pobre rendimiento. Acceso a través de un Pool de conexión, siendo esta la recomendada para las aplicaciones Java EE. El método utilizado es el getConnection de javax.sql.DataSource Los filtros son unos componentes que se despliegan como parte de la aplicación Web, y cuya misión es interceptar las peticiones y respuesta de un componente concreto para por ejemplo, realizar algún tipo de validación, algún tipo de auditoria, algún tipo de transformación, etc… Un filtro de implementar el interface: javax.servlet.Filter que incluye tres métodos: init: Se invoca una única vez en la vida del filtro y se utiliza para realizar las labores de inicialización. doFilter: Es el método que realiza la lógica específica del filtro: auditoria, transformaciones, etc… destroy: Se invoca una única vez en la vida del filtro y se utiliza para realizar las labores de limpieza y liberación de recursos si fuese necesario. 201 Para el mantenimiento del estado del cliente en el servidor, se pueden utilizar: Cookies: es información formada por una clave y un valor que contiene información textual. Esta cookie es creada en el servidor de aplicaciones y almacenada en el navegador cliente utilizado en las llamadas a la aplicación web. Sesión HTTP: Es una zona de almacenaje en el servidor exclusiva de cada cliente, donde las aplicaciones pueden dejar información para ser utilizada en sucesivas peticiones del mismo cliente. Las especificaciones Java EE definen una serie de notificación de eventos a los que las aplicaciones web se pueden suscribir para ser notificadas de cuándo dichos eventos ocurren. Contamos con dos piezas fundamentales: Un notificador (Notifier), que es quién generará los eventos Un escuchador (Listener) que es quien está interesado en dichos eventos para reaccionar y actuar en consecuencia. En concreto, existen tres familias de eventos: Relacionadas con el Contexto del Java Servlet Relacionadas con la petición Relacionadas con la Sesión HTTP 202 Unidad de Aprendizaje 8 JAVASERVER PAGES AVANZADO ÍNDICE 8.1 Introducción ................................................................. 205 8.2 Etiquetas JSP estándar .............................................. 205 8.3 Lenguaje de Expresiones JSP (EL) ........................... 208 8.4 Librerías de etiquetas JSP estándar......................... 211 8.4.1 Core ................................................................................214 8.4.2 XML.................................................................................215 8.4.3 I18N.................................................................................215 8.4.4 Database ........................................................................216 8.4.5 Functions........................................................................217 8.5 Librerías de etiquetas JSP personalizadas.............. 218 PARA RECORDAR.................................................................. 224 MÓDULO C – Unidad 8: JavaServer Pages Avanzado 8.1 Introducción A menudo, en el desarrollo de aplicaciones web de una cierta envergadura, existen equipos de desarrollo con roles y skills diferentes. Así, nos encontraremos con desarrolladores expertos en Java que se encargarán normalmente del modelo y controlador, mientras que tendremos otro grupo de desarrolladores expertos en diseño que se encargarán de la presentación. Estos expertos en diseño, no suelen conocer ni manejar el lenguaje Java, centrando sus skills más en diseño gráfico, tratamiento de imágenes, etc… por lo que al desarrollar la presentación emplearán herramientas más orientadas a etiquetas que a programar con el lenguaje Java. Por este motivo, la especificación de JavaServer Pages (JSP) define una serie de mecanismos para seguir permitiendo el desarrollo de la presentación sin necesidad de escribir líneas de lenguaje Java. Estos mecanismos son: Las acciones JSP estándar. Las librerías de etiquetas JSP Esta unidad se centra en el estudio de ambos mecanismos. 8.2 Etiquetas JSP estándar Las etiquetas JSP estándar, son etiquetas del estilo de las empleadas en las páginas HTML o en los descriptores de despliegue (XML) que permiten realizar acciones en el momento de ejecución de la página. Cualquier etiqueta JSP podría ser sustituida sin ningún problema por código Java equivalente como ya hicimos en unidades anteriores. La sintaxis de una etiqueta JSP consiste en el prefijo “jsp:” seguido del identificador de la acción y opcionalmente atributos (parejas de nombre y valor). Las especificaciones definen múltiples etiquetas JSP estándar, a saber: <jsp:forward>: redirige la petición a otra página JSP, HTML o Java Servlet. <jsp:include>: incluye el resultado de otra página JSP, Java Servlet u otro componente web estático. <jsp:param>: se utiliza dentro de un bloque de <jsp:forward> o <jsp:include> para indicar los parámetros que opcionalmente se pueden enviar. <jsp:useBean>: referencia o instancia una clase Java y la asocia un nombre y un ámbito concreto. <jsp:setProperty>: inserta un valor a un atributo del bean (o clase Java). <jsp:getProperty>: recoge el valor de un atributo del bean (o clase Java) y lo incluye en la respuesta 205 <jsp:text>: inserta texto en la respuesta. <jsp:element>: inserta un elemento XML en la respuesta. <jsp:plugin>: inserta un Applet Java en la respuesta. <jsp:output>: inserta la etiqueta XML DOCTYPE de toda JSP en la respuesta. <jsp:body>: identifica el comienzo del cuerpo del documento HTML (viene a sustituir a <BODY>). Se ha de utilizar siempre que en la página se utilice la acción <jsp:attribute>. Otras: <jsp:root>, <jsp:invoke>, <jsp:doBody>, <jsp:attribute>, … Son muchas las etiquetas pero trataremos solo algunas de ellas porque en general, a excepción de unas pocas, su uso es muy raro. La etiqueta <jsp:include> produce un efecto exactamente igual que el de la directiva <%@ include file=”xxx” %> que ya hemos visto y practicado en alguna actividad durante el curso. La diferencia es que el atributo que necesita se llama “page”. Algunos ejemplos: <jsp:include page="copyright.html" /> <jsp:include page="/index.html" /> <jsp:include page="/login.jsp"> <jsp:param name="usuario" value="pepe" /> </jsp:include> Y la etiqueta <jsp:forward> es parecida a <jsp:include> con la diferencia de que no incluye el resultado, sino que delega en ese nuevo recurso la respuesta. Algunos ejemplos: <jsp:forward page="/servlet/login" /> <jsp:forward page="/servlet/login"> <jsp:param name="usuario" value="pepe" /> </jsp:forward> Otra etiqueta típica es la de referenciar o instanciar una clase Java para trabajar con ella. Como vimos en la enumeración de etiquetas existentes, la etiqueta a emplear es <jsp:useBean>. Esta etiqueta busca un objeto con el identificador dado en el ámbito seleccionado y de no encontrarlo, lo crea con dicho identificador en dicho ámbito. Utiliza por tanto estos tres atributos: 206 id: identificador del objeto. scope: ámbito donde buscar o crearlo. Existen cuatro valores posibles: o request: la petición. o page: la página: o session: la sesión HTTP. MÓDULO C – Unidad 8: JavaServer Pages Avanzado o application: la aplicación. class: la clase Java (el tipo). Por ejemplo: <jsp:useBean id="cesta" scope="session" class="edu.aulamentor.Cesta"/> Esta etiqueta busca en la sesión HTTP un objeto de la clase edu.aulamentor.Cesta que se llama cesta. Si no existiera lo crea. ¿Y esto para qué? Pues normalmente va a acompañado de las etiquetas <jsp:setProperty> y <jsp:getProperty> para utilizar el objeto en cuestión. <jsp:setProperty> nos permite establecer el valor de alguno de los atributos del objeto. Para ello utiliza tres atributos: name: identificador del objeto. attribute: atributo del objeto. value: valor del atributo del objeto. Por ejemplo: <jsp:useBean id="cesta" scope="session" class="edu.aulamentor.Cesta"/> <jsp:setProperty name="cesta" property="total" value="0.0" /> De igual forma, podemos acceder al valor de un atributo del objeto mediante la acción <jsp:getAttribute>. Para ello utiliza dos atributos: name: identificador del objeto. attribute: atributo del objeto. Por ejemplo: Total de la cesta es: <jsp:getProperty name="cesta" property="total"/> Para que estas etiquetas funcionen bien, la clase Java debe seguir las especificaciones JavaBean. Esto significa que, entre otras cosas, la clase debe tener un constructor por defecto (constructor sin parámetros) y un getter/setter por cada atributo (siguiendo la norma de utilizar get y set seguido del nombre del atributo con la primera letra en mayúsculas). Es decir, en nuestro ejemplo: package es.aulamentor; public class Cesta { private double total; public Cesta() { } 207 public double getTotal() { return this.total; } public void setTotal(double param) { this.total = param; } } Simplemente por comparar, este mismo ejemplo pero con código Java habría sido algo así: <% es.aulamentor.Cesta cesta = (es.aulamentor.Cesta)session.getAttribute(“cesta”); cesta.setTotal(0.0); %> Total de la cesta es: <%= cesta.getTotal() %> Pero la etiqueta <jsp:setProperty> puede tener un uso más potente todavía, y es el de transferir el valor de los parámetros de un formulario que se reciba en una petición a los atributos de un objeto Java sin necesidad de ir uno a uno. Basta con eliminar el atributo value: <jsp:useBean id="cesta" scope="session" class="es.aulamentor.Cesta" /> <jsp:setProperty name="cesta" property="*" /> En este caso, el servidor va a buscar todos (porque hemos usado el carácter * en vez de un nombre concreto) aquellos parámetros que vengan en la petición que se llamen igual que algún atributo de la clase Cesta y ponerle su valor. 8.3 Lenguaje de Expresiones JSP (EL) Conocido con el acrónimo EL (Expression Language). EL es un pseudolenguaje para identificar y acceder de una manera muy sencilla a las variables. Veamos primero un ejemplo con el que se verá rápidamente la idea. Mediante código Java, para acceder por ejemplo al atributo nombre, de un objeto de tipo Perro guardado en un atributo llamado perro en un objeto de tipo Persona que está guardado en la Sesión HTTP con el identificador de persona escribiríamos algo así: Se llama: <%= ((es.aulamentor.Persona)session.getAttribute(“persona”)).getPerro().getN ombre() %> Sin embargo, utilizando EL sería algo así: Se llama: ${persona.perro.nombre} Sencillo, ¿verdad? 208 MÓDULO C – Unidad 8: JavaServer Pages Avanzado La sintaxis y opciones de EL son realmente sencillas. Ahora, es muy importante no pensar en Java cuando usemos EL y viceversa, porque aunque se parecen son distintos y nos llevarán a equivocaciones. Las expresiones EL siempre van entre llaves ({ }) precedidas del símbolo del dólar ($): ${ } El contenido de las llaves siempre será uno o varios identificadores separados por puntos. El primer identificador puede ser dos cosas: Una referencia a un objeto implícito: que existe en el contenedor web y es accesible directamente. Un atributo existente en uno de los distintos ámbitos: página, petición, sesión o aplicación. Es importante tener en cuenta que se busca en ese orden, por si existiese más de un atributo con el mismo nombre en ámbitos diferentes. Todos los objetos implícitos son una colección del tipo Map, es decir, contiene parejas de clave y valor. Estos son los que hay: pageScope: ámbito de la página. requestScope: ámbito de la petición. sessionScope: ámbito de la sesión. applicationScope: ámbito de la aplicación. param: parámetros de la petición. paramValues: parámetros de la petición en caso de que el parámetro sea a su vez una colección. header: cabeceras de la petición. headerValues: cabeceras de la petición en caso de que la cabecera sea a su vez una colección. cookie: cookies de la petición. initParam: parámetros del contexto. pageContext: es el único que no es un Map. Directamente es una referencia a una instancia de la clase javax.servlet.jsp.PageContext Por tanto, si queremos referenciar al atributo persona que está en la Sesión HTTP tenemos dos opciones: ${persona} o para evitar colisiones en el caso de que existiese otro atributo con el mismo nombre en ámbitos con mayor precedencia: 209 ${sessionScope.persona} Ahora, si queremos seguir profundizando en el atributo, accediendo a otros atributos más internos en su estructura (como el ejemplo anterior donde accedimos al nombre del perro de la persona), es cuando utilizaremos una secuencia de nombres de atributos separados por puntos: ${sessionScope.persona.perro.nombre} Ahora bien, llegados a este punto es importante volver a recordar, que esto solo funciona si las clases Java accedidas siguen las especificaciones JavaBean, que entre otras cosas obligan a que: La clase tenga un constructor por defecto (sin parámetros). La clase tenga un método getter por cada atributo siguiendo la regla de que se llamara get seguido del nombre del atributo con la primera letra en mayúscula. La clase tenga un método setter por cada atributo siguiendo la regla de que se llamara set seguido del nombre del atributo con la primera letra en mayúscula. Por tanto, para que funcione el ejemplo: ${sessionScope.persona.perro.nombre} significa que la clase Persona tiene un método que se llama getPerro() y que la clase Perro tiene un método que se llama getNombre(). Si esto no fuese así, la compilación de la página JSP fallaría. Veamos otro ejemplo de cómo sería el acceso al valor de una Cookie. Usando Java sería algo así: <% Cookie[] cookies = request.getCookies(); for(int i=0; i<cookies.lenght; i++) { if((cookies[i].getName()).equals(“nombreUsuario”)) { %> <%= cookies[i].getValue() %> <% } } %> Con EL sería simplemente esta línea: ${cookie.nombreUsuario.value} Las expresiones EL también admiten el uso de corchetes ([]). Su uso vale para dos cosas: 210 MÓDULO C – Unidad 8: JavaServer Pages Avanzado Lo mismo que el punto, es decir, ${perro.nombre} es equivalente a ${perro[“nombre”]} usando las comillas dobles para especificar el nombre del atributo. Para profundizar en atributos que son de algún tipo de colección (array, Map o List), por ejemplo: ${perros[3].nombre} Las expresiones EL también permiten implementar una mínima lógica, aunque no está recomendado su uso para ello. Soporta los operadores aritméticos (+, -, *, / y %), los operadores lógicos (&&, || y !) y los operadores relacionales (==, ¡=, <, >, <= y >=). Otro dato a tener muy en cuenta es que las expresiones EL pueden ser usadas como valores de los atributos de las etiquetas JSP que ya hemos comentado en la sección anterior y en las que comentaremos a continuación. 8.4 Librerías de etiquetas JSP estándar Al igual que existen las etiquetas JSP estándar definidas en las especificaciones JSP, con el tiempo surgió otra especificación denominada JSTL (JavaServer Page Standard Tag Library o Librería de etiquetas JSP estándar) que aglutina una serie de librerías de etiquetas o acciones adicionales que persiguen el mismo objetivo que las anteriores: facilitar la programación de la presentación de las aplicaciones web a diseñadores gráficos sin conocimientos del lenguaje Java mediante herramientas visuales orientadas al manejo de etiquetas. En este curso no pretendemos cubrir todas y cada una de las etiquetas, pero si comentar cómo se especifica el uso de una librería, qué librerías hay, y algún ejemplo. La guía de referencia completa de JSTL está disponible en: http://docs.oracle.com/javaee/5/jstl/1.1/docs/tlddocs/ Actualmente existen cinco librerías de etiquetas presentes en cualquier servidor de aplicaciones Java EE completo: Core (en castellano principal): Su prefijo es c y su URI http://java.sun.com/jsp/jstl/core. Cubre funcionalidades básicas como el manejo de variables, el control del flujo, la gestión de URLs y otras acciones. XML: Su prefijo es x y su URI http://java.sun.com/jsp/jstl/xml. Cubre funcionalidades de gestión de ficheros XML. I18N (en castellano internacionalización): Su prefijo es fmt y su URI http://java.sun.com/jsp/jstl/fmt. Cubre funcionalidades de formateo de mensajes, números, fechas, horas, etc… teniendo en cuenta la localización de cada cliente. Database (en castellano, Base de Datos): Su prefijo es sql y su URI http://java.sun.com/jsp/jstl/sql. Cubre funcionalidades de acceso a Bases de Datos. 211 Functions (en castellano funciones): Su prefijo es fn y su URI http://java.sun.com/jsp/jstl/functions. Cubre funcionalidades relacionadas con el manejo de cadenas de caracteres. Antes de entrar más en detalle en cada librería, vamos a ver cómo se indica en una página JSP el uso de una librería. Para ello se utiliza una directiva que ya mencionamos en una unidad anterior pero que no explicamos en detalle “taglib”: <%@ taglib uri=”xxx” prefix=”xxx” %> Recibe dos atributos: URI: identificador del espacio de nombres (viene a significar algo parecido a los paquetes Java para evitar colisiones de dos etiquetas de mismo nombre). prefix: identificador de la librería a utilizar. Por tanto, si en nuestra página JSP queremos utilizar por ejemplo las librerías Core y Database deberíamos añadir estas dos líneas al inicio de la página: <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> El uso de una acción concreta de una de estas librerías, sigue la sintaxis de las acciones JSP estándar: pero cambiando “jsp” por el prefijo de la librería, seguido de los dos puntos, seguidos de la acción en cuestión, seguida de sus atributos. Por ejemplo, el uso de la acción “set” de la librería Core sería algo así: <c:set var="titulo" value="Don Quijote"/> Pero ¿qué pasa cuando añadimos una de estas directivas en Eclipse IDE for Java EE Developers? Salen errores. El motivo, es que Apache Tomcat no cumple el 100% de las especificaciones Java EE, y en concreto JSTL 1.2 no lo cumple. Pero es muy sencillo añadir la implementación de referencia de la especificación a nuestra aplicación web, añadiendo a /WebContent/WEB-INF/lib estos dos ficheros JAR: http://search.maven.org/remotecontent?filepath=javax/servlet/jsp/jstl/javax.servlet.jsp.j stl-api/1.2.1/javax.servlet.jsp.jstl-api-1.2.1.jar http://search.maven.org/remotecontent?filepath=org/glassfish/web/javax.servlet.jsp.jst l/1.2.1/javax.servlet.jsp.jstl-1.2.1.jar 212 MÓDULO C – Unidad 8: JavaServer Pages Avanzado Una vez hemos copiado ambos dos ficheros en el directorio /WebContent/WEB-INF/lib del proyecto, si no se fuese el error forzamos una recompilación mediante la opción de menú: “Project” -> “Clean…”: Revisemos a continuación brevemente las etiquetas de cada una de las librerías estándar. Como ya comentamos, no va a ser una explicación exhaustiva. Simplemente pretendemos que se conozcan qué posibilidades hay y dónde poder consultar en caso de llegar a necesitarse alguna. 213 8.4.1 Core Implementa acciones básicas para el tratamiento de variables, control de flujo, gestión de URLs, y otras: Gestión de variables: o set: estable el valor de una variable EL o del atributo de una variable EL en uno de los ámbitos posibles. Si dicha variable no existiese la crea. <c:set var="titulo" scope="session" value="${param.titulo}"/> o remove: elimina una variable EL. <c:remove var="cart" scope="session"/> Control de flujo: o choose junto con when y otherwise: viene a ser una especie de estructura switch, case, default de Java. Evalúa las condiciones EL de cada etiqueta when y entra por la que sea true. Si ninguna fuese true, ejecuta la otherwise. <c:choose> <c:when test="${cont == 0}" > No se ha encontrado ningún elemento. </c:when> <c:otherwise> Se han encontrado ${cont} elementos. </c:otherwise> </c:choose> o forEach: permite iterar por una colección de elementos. <c:forEach var="libro" items="${sessionScope.cesta.libros}"> <TR> <TD align="right" bgcolor="#ffffff"> ${libro.titulo} </TD> </TR> </c:forEach> o forTokens: permite iterar por una colección de tokens separados por un delimitador. o if: evalúa una condición EL y si es true sigue ejecutando el contenido del bloque. <c:if test="${param.nombre == ‘Pepe’}"> <H1>El nombre es Pepe.</H1> </c:if> Gestión de URLs: o import: importa el contenido de un recurso de Internet en una variable EL. Puede utilizar param para especificar parámetros. <c:import url="/introduccion.txt" var="texto" /> o redirect: envía al cliente un código HTTP de redirección. Puede utilizar param para especificar parámetros. 214 MÓDULO C – Unidad 8: JavaServer Pages Avanzado o url: guarda URLs en una variable EL. Puede utilizar param para especificar parámetros. Otras: o catch: captura una excepción. La excepción no se propaga y por tanto no se invocará la página JSP de error si la hubiera. o out: evalúa una expresión EL y devuelve su valor. Más detalles y ejemplos: http://docs.oracle.com/javaee/5/jstl/1.1/docs/tlddocs/ 8.4.2 XML Implementa acciones básicas para el parseo de documentos XML, control de flujo recorriendo estructuras XML y transformaciones: Parseo: o out: evalúa una expresión XPath y su resultado lo devuelve como parte de la respuesta. o parse: parsea un documento XMl y guarda el contenido en una variable identificada mediante EL. o set: evalúa una expresión XPath y su resultado lo guarda en una variable identificada mediante EL. Control de flujo: o choose junto con when y otherwise: similar a la librería Core pero itera sobre el resultado de evaluar una expresión XPath sobre un documento XML. o forEach: similar a la librería Core pero itera sobre el resultado de evaluar una expresión XPath sobre un documento XML. o if: similar a la librería Core pero itera sobre el resultado de evaluar una expresión XPath sobre un documento XML. Transformación: o transform: aplica una transformación a un documento XML especificado en el atributo doc definida en una plantilla XSLT especificada en el atributo xslt. Más detalles y ejemplos: http://docs.oracle.com/javaee/5/jstl/1.1/docs/tlddocs/ 8.4.3 I18N Implementa acciones básicas para la gestión de la localización, seleccionar los mensajes a mostrar en base a dicha localización y el formateo de números y fechas: Localización: o setLocale: fuerza una localización concreta independientemente de la que enviara el cliente web. o requestEncoding: especifica la codificación a utilizar para interpretar los parámetros de una petición. Mensajes: 215 o bundle: especifica el conjunto de recursos “localizados” a utilizar. o setBundle: similar a bundle pero para un contexto específico. o message: sirve para añadir a la respuesta un texto “localizado” especificando en el atributo key la clave de dicho texto dentro del bundle. Formateo: o formatNumber: formatea un número teniendo en cuenta la localización. o formatDate: formatea una fecha teniendo en cuenta la localización. o parseNumber: convierte una cadena de texto en un número teniendo en cuenta la localización. o parseDate: convierte una cadena de texto en una fecha teniendo en cuenta la localización. o setTimeZone: especifica la zona horaria. Más detalles y ejemplos: http://docs.oracle.com/javaee/5/jstl/1.1/docs/tlddocs/ 8.4.4 Database Implementa acciones básicas para el acceso a las Bases de Datos: Configuración: o setDataSource: sirve para especificar el identificador JNDI del Data Source a utilizar. <sql:setDataSource dataSource="jdbc/TestDB" /> SQL: o query: ejecuta una query que devuelve un resultado que guarda en el atributo var. Dicho resultado es un Java Bean con los siguientes atributos (a tener en cuenta para poder explotar la información resultante mediante EL): public String[] columnNames public int rowCount public Map[] rows() public Object[][] rowsByIndex public boolean limitedByMaxRows Un ejemplo imaginando que tenemos una tabla LIBROS con columnas como AUTOR, TITULO y PAGINAS: <sql:query var="libros" sql="select * from LIBROS where AUTOR = ?" > <sql:param value="${autor}" /> </sql:query> <c:forEach var="libro" begin="0" items="${libros.rows}"> <H2>${libro.titulo}</H2><BR> Núm. De Páginas: ${libro.paginas}<BR><BR> </c:forEach> 216 MÓDULO C – Unidad 8: JavaServer Pages Avanzado o update: ejecuta una actualización en la Base de Datos. o transaction: sirve para aglutinar varias queries y/o updates en una unidad transaccional. Más detalles y ejemplos: http://docs.oracle.com/javaee/5/jstl/1.1/docs/tlddocs/ 8.4.5 Functions Implementa acciones básicas de manejo de cadenas de caracteres pero al contrario que todas las librerías anteriores, estás no son etiquetas como tal sino funciones a emplear en una EL: length: devuelve la longitud de una colección. <c:if test="${fn:length(parametro.nombre) > 0}" > Longitud del nombre mayor de cero. Correcto! </c:if> toUpperCase: devuelve la cadena en mayúsculas. toLowerCase: devuelve la cadena en minúsculas. substring: devuelve una parte de una cadena. substringAfter: devuelve una parte de una cadena a partir de un carácter especificado. substringBefore: devuelve una parte de una cadena antes de un carácter especificado. trim: devuelve la cadena sin espacios en blanco por delante ni por detrás. replace: devuelve una cadena con un carácter/cadena remplazado por otro. indexOf: devuelve la posición de un carácter/cadena especificado. startsWith: chequea si una cadena comienza con un carácter/cadena especificado. endsWith: chequea si una cadena termina con un carácter/cadena especificado. contains: chequea si una cadena contiene un carácter/cadena especificado. containsIgnoreCase: chequea si una cadena contiene un carácter/cadena especificado ignorando las mayúsculas y minúsculas. split: divide una cadena devolviendo un array de cadenas. join: devuelve la unión de varias cadenas en una sola cadena. escapeXml: devuelve la cadena habiendo cambiado los caracteres especiales por sus valores codificados para poder ser usados en una URL. Más detalles y ejemplos: http://docs.oracle.com/javaee/5/jstl/1.1/docs/tlddocs/ 217 8.5 Librerías de etiquetas JSP personalizadas Al igual que existen las librerías de etiquetas JSP estándar estudiadas en la sección anterior, las especificaciones Java EE contemplan la posibilidad de que podamos definir e implementar nuestra propia librería de etiquetas. De esta forma, en caso de haber decidido minimizar el código Java que aparecerá en las páginas JSP podemos implementar etiquetas que cubran necesidades particulares de nuestra aplicación. De igual forma, encontraremos en Internet cantidad de librerías adicionales de etiquetas desarrolladas por terceras personas. El API necesario para la implementación de etiquetas personalizadas se encuentra en el paquete: javax.servlet.jsp.tagext; La implementación de una etiqueta personalizada siempre ha de implementar el interface: javax.servlet.jsp.tagext.Tag o extender la clase: javax.servlet.jsp.tagext.TagSupport que es una clase que contiene implementaciones por defecto de los métodos del interface Tag. El interface Tag tiene varios métodos, pero aquí solo comentaremos: int doStartTag() throws JspException Este método es invocado cuando el contenedor web detecta el inicio de la etiqueta. Lo normal, es usar este método para implementar la lógica asociada a la etiqueta. Debe devolver un número entero que corresponde a una de estas constantes definidas en el interface Tag: o SKIP_BODY: significa que la etiqueta ya ha procesado el resto de etiquetas o contenido incluido dentro de su bloque (si lo hubiera) así que el contenedor web puede proceder con el final de la etiqueta. o EVAL_BODY_INCLUDE: significa que la etiqueta no ha procesado el resto de etiquetas o contenido incluido dentro de su bloque (si lo hubiera) así que el contendor web debe procesar ese contenido antes de proceder con el final de la etiqueta. int doEndTag() throws JspException Este método es invocado cuando el contenedor web detecta el final de la etiqueta. Debe devolver un número entero que corresponde a una de estas constantes definidas en el interface Tag: o EVAL_PAGE: indica al contendor web que continúe con el proceso de ejecución normal de la página JSP. 218 MÓDULO C – Unidad 8: JavaServer Pages Avanzado o SKIP_PAGE: indica al contendor web que interrumpa el proceso de ejecución normal de la página JSP. Si la nueva etiqueta va a soportar atributos, entonces la implementación Java debe incluir un atributo con un método setter con idéntico nombre por cada uno. El método setter debe seguir las especificaciones JavaBean, es decir, debe llamarse set seguido del nombre del atributo con la primera letra en mayúsculas. El contenedor web, al encontrarse con la nueva etiqueta, instanciará la implementación y especificará los valores de todos los atributos basado en los valores que se encuentran en la etiqueta en la página JSP. Al extender la clase TagSupport, tenemos acceso a una variable con nombre pageContext de tipo: javax.servlet.jsp.PageContext que nos permite acceder al writer (java.io.Writer) de la respuesta, para poder escribir el resultado de la ejecución de la etiqueta mediante el método: public JspWriter getOut() Veamos un ejemplo. Vamos a definir e implementar una etiqueta que añada por delante y por detrás del contenido de su cuerpo (el bloque definido por la etiqueta) un carácter un número concreto de veces. Para ello, tendrá dos atributos donde especifiquemos el carácter y el número de veces llamados “caracter” y “veces”. Esta sería la implementación: package es.aulamentor; import java.io.IOException; import java.io.Writer; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.Tag; import javax.servlet.jsp.tagext.TagSupport; public class RepiteTag extends TagSupport { private char caracter; private int veces; public void setCaracter(char carcater) { this.caracter = carcater; } public void setVeces(int veces) { this.veces = veces; } public int doStartTag() throws JspException { 219 Writer out = pageContext.getOut(); for(int i=0; i<veces; i++) { try { out.write(caracter); } catch(IOException ex) { ex.printStackTrace(); } } return Tag.EVAL_BODY_INCLUDE; } public int doEndTag() throws JspException { Writer out = pageContext.getOut(); for(int i=0; i<veces; i++) { try { out.write(caracter); } catch(IOException ex) { ex.printStackTrace(); } } return Tag.EVAL_PAGE; } } A continuación, necesitamos crear un fichero de configuración XML donde se le indica al Servidor Java EE la existencia de una librería personalizada y los detalles. Este fichero de extensión *.tld debe formar parte de la aplicación web y su ubicación es un directorio tlds en /WEB-INF En nuestro caso lo llamamos AulaMentorTagLib.tld: 220 MÓDULO C – Unidad 8: JavaServer Pages Avanzado Este fichero, sigue una sintaxis concreta. Veamos el contenido para nuestro ejemplo y repasaremos las etiquetas más relevantes: <?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>Aula_Mentor_Tag_Library</short‐name> <uri>http://www.aulamentor.es/jsp/jstl/tags</uri> <tag> <name>repite</name> <tag‐class>es.aulamentor.RepiteTag</tag‐class> <body‐content>scriptless</body‐content> <attribute> <name>caracter</name> <required>true</required> </attribute> <attribute> <name>veces</name> <required>false</required> </attribute> </tag> </taglib> La primera línea indica que se trata de un archivo XML. La segunda indica que se trata de la definición de una librería de etiquetas. Como bloques o etiquetas relevantes tenemos: uri: a la que luego haremos referencia desde la página JSP para indicar que usamos esta librería. 221 tag: incluye la definición de una etiqueta personalizada. name: el identificador de la etiqueta cuando la usemos en una página JSP. tag-class: la clase Java que implementa el comportamiento de la etiqueta. body-content: indica si la etiqueta puede albergar contenido dentro de su bloque o no (entre la etiqueta de inicio y la de cerrado). Puede tomar varios valores: o empty: no puede tener contenido. o scriptless: puede tener contenido pero solo otras etiquetas, no código de script. o tagdependent: puede contener cualquier cosa. attribute: incluye la definición de un atributo de la etiqueta. Indica su nombre que usaremos luego en la página JSP y si es obligatorio o no. Por último, ya solo nos queda utilizar la nueva etiqueta en la aplicación. Para ello vamos a crear una página JSP muy sencilla: <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <%@ taglib uri="http://www.aulamentor.es/jsp/jstl/tags" prefix="am" %> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Prueba de Tag Personalizada</TITLE> </HEAD> <BODY> <am:repite caracter="*" veces="5"> PRUEBA </am:repite> <BODY> </HTML> Primero, fijémonos en cómo decimos que vamos a utilizar etiquetas de la nueva librería: <%@ taglib uri="http://www.aulamentor.es/jsp/jstl/tags" prefix="am" %> La URI debe coincidir exactamente con la que especificamos en le fichero de configuración de la librería. Y el prefijo, puede ser cualquiera. En este caso hemos decidido que sea: am. Ahora, veamos dónde y cómo hemos usado la etiqueta: <am:repite caracter="*" veces="5"> PRUEBA </am:repite> Identificada con el prefijo y utilizando exactamente el mismo nombre que usamos en el fichero de configuración. Hemos usado también sus dos atributos (solo carácter era obligatorio). 222 MÓDULO C – Unidad 8: JavaServer Pages Avanzado El resultado de su ejecución, debería ser la palabra PRUEBA rodeada de cinco asteriscos por delante y detrás: El API para la creación de etiquetas personalizadas es más extenso de lo que hemos visto en esta sección. Aquí hemos repasado las bases para la creación de etiquetas sencillas, pero el API ofrece más posibilidades que si el alumno está interesado puede profundizar consultando la guía de referencia en Internet: http://docs.oracle.com/javaee/6/api/javax/servlet/jsp/tagext/packagesummary.html#package_description 223 PARA RECORDAR Las etiquetas JSP estándar, son etiquetas del estilo de las empleadas en las páginas HTML o en los descriptores de despliegue (XML) que permiten realizar acciones en el momento de ejecución de la página. Su sintaxis consiste en el prefijo “jsp:”. Las Expression Language (EL) son un pseudolenguaje para identificar y acceder de una manera muy sencilla a las variables. Su sintaxis es ${…} La especificación JSTL (JavaServer Page Standard Tag Library o Librería de etiquetas JSP estándar) aglutina una serie de librerías de etiquetas o acciones adicionales. Se especifican mediante la directiva “taglib”. Las librerías existentes en un servidor de aplicaciones Java EE son: Core: Cubre funcionalidades básicas como el manejo de variables, el control del flujo, la gestión de URLs y otras acciones. XML: Cubre funcionalidades de gestión de ficheros XML. I18: Cubre funcionalidades de formateo de mensajes, números, fechas, horas, etc… teniendo en cuenta la localización de cada cliente. Database: Cubre funcionalidades de acceso a Bases de Datos. Functions: Cubre funcionalidades relacionadas con el manejo de cadenas de caracteres. Las especificaciones Java EE contemplan la posibilidad de que podamos definir implementar nuestra propia librería de etiquetas. La implementación de una etiqueta personalizada siempre ha de implementar el interface, javax.servlet.jsp.tagext.Tag o extender la clase javax.servlet.jsp.tagext.TagSupport 224 Unidad de Aprendizaje 9 OTROS CONCEPTOS AVANZADO ÍNDICE 9.1 Introducción ................................................................. 227 9.2 Gestión de errores....................................................... 227 9.3 Gestión de la seguridad.............................................. 232 9.4 Frameworks de desarrollo web................................. 244 9.4.1 JavaServer Faces .........................................................244 9.4.2 Apache Struts................................................................245 9.4.3 Spring Source................................................................245 9.4.4 Apache Wicket ..............................................................246 PARA RECORDAR.................................................................. 247 MÓDULO C – Unidad 9: Otros conceptos 9.1 Introducción Esta es la última unidad del curso. En ella trataremos algunos temas no menos importantes que no han tenido cabida en las unidades anteriores como son la gestión de errores y la seguridad en Java EE. Por último, daremos una breve pincelada de algunos frameworks de desarrollo de aplicaciones web, que construidos sobre los conceptos que hemos visto en este curso pretenden acelerar y facilitar el desarrollo de aplicaciones web. La idea es simplemente que el alumno conozca de su existencia y profundice en alguno de ellos si los encuentra de interés. Cada uno de ellos por si solo, daría lugar a un curso completo. 9.2 Gestión de errores Según las especificaciones Java EE, existen dos alternativas no excluyentes para la gestión de errores: Programática: Toda la gestión se lleva dentro de la aplicación mediante código fuente. Es el modo que hemos estado empleando hasta ahora en todos los ejemplos de las distintas unidades. Declarativa: La gestión se externaliza en configuraciones en el descriptor de despliegue. Evidentemente cada una tiene sus ventajas e inconvenientes. Por un lado, la programática es la gestión más potente y flexible, sin embargo el código es más complejo y la lógica más complicada de seguir. Por otro lado, la declarativa es la gestión más sencilla pero por otro lado tiene más restricciones. La gestión programática ya la conocemos. Mediante las estructura try & catch o cualquier otro medio de detección de errores, realizamos mediante código redirecciones a páginas concretas de tratamiento y muestra del error. Por tanto, en esta unidad nos vamos a ceñir a la gestión declarativa. Mediante la gestión declarativa podemos controlar dos aspectos: Errores HTTP: Qué eran y qué tipos de errores HTTP existían ya lo estudiamos en la unidad dedicada al protocolo HTTP en este mismo curso. Es el propio Servidor de Aplicaciones Java EE el que habitualmente gestiona este tipo de errores mostrando una página de error. Pero las especificaciones Java EE nos permiten definir en el descriptor de despliegue un componente web (página HTML, página JSP, Java Servlet, etc…) que se invocará cuando ocurra dicho error en vez de mostrar la página por defecto del servidor. Por ejemplo, la página por defecto para un error 404 (recurso no encontrado) de Apache Tomcat 7.0.x es la siguiente: 227 Sin embargo, mediante los bloques “error-page” y sus bloques “error-code” (para especificar el código de error) y “location” (para especificar qué componente web invocar) podemos cambiar este comportamiento por defecto. Veamos un ejemplo de cómo cambiar la página que se muestra para el error HTTP 404: <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <error‐page> <error‐code>404</error‐code> <location>/Error404.jsp</location> </error‐page> </web‐app> Y esta es la página JSP: <%@ page language="java" contentType="text/html; charset=ISO‐ 8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐8859‐1"> <TITLE>Página no encontrada. Disculpe las molestías...</TITLE> </HEAD> <BODY> Página no encontrada. Disculpe las molestías...<BR/> Si el problemea persiste póngase en contacto con el administrador.<BR/> <%= Calendar.getInstance().getTime() %> </BODY> </HTML> 228 MÓDULO C – Unidad 9: Otros conceptos Este sería el resultado ahora: Y así podríamos hacer con todos los tipos de errores HTTP más habituales, consiguiendo dar una imagen más corporativa y cercana al usuario (evidentemente la página de nuestro ejemplo no es ninguna de las dos cosas). Excepciones Java: También se pueden gestionar de manera declarativa las excepciones Java que se produzcan. Lo más habitual, es hacerlo con aquellas excepciones Java no esperadas o con difícil solución. Por ejemplo, java.lang.NullPointerException. Pero podemos gestionar cualquiera que queramos. Veamos primero cómo contesta por defecto Apache Tomcat 7.0.x a una excepción del tipo java.lang.NullPointerException no capturada. 229 El mecanismo para declarar qué componente invocar cuando se produzca una excepción concreta, es similar al visto anteriormente. La única diferencia es que en vez del bloque “error-code” usaremos “exception-type”. Otra diferencia, es que el Servidor de Aplicaciones Java EE nos pasará en la petición dos atributos (ojo, no parámetros): o javax.servlet.error.exception: Conteniendo la excepción que se ha producido (no el nombre, sino la instancia como tal). o javax.servlet.error.request_uri: Indicando el componente que produjo el error. Veamos un ejemplo de cómo cambiar la página que se muestra para la excepción java.lang.Exception no capturada y tratada en el código: <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <error‐page> <exception-type>java.lang.NullPointerException</exception-type> <location>/NullPointerException.jsp</location> </error‐page> </web‐app> Y esta es la página JSP: <%@page import="java.util.Calendar"%> <%@ page language="java" contentType="text/html; charset=ISO‐ 8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐8859‐1"> <TITLE>Error no esperado.</TITLE> </HEAD> <BODY> <% Exception ex = (Exception)request.getAttribute ("javax.servlet.error.exception"); StackTraceElement[] stack = ex.getStackTrace(); %> Se produjo un error no esperado. Disculpen las molestias...<BR/> Contacte con el administrador con la siguiente información:<BR/> <UL> <LI>Componente defectuoso: <%= request.getAttribute("javax.servlet.error.request_uri") %> 230 MÓDULO C – Unidad 9: Otros conceptos </LI> <LI>Error producido: NullPointerException</LI> <LI>Más detalle:<BR/> <% for(int i=0; i<stack.length; i++) { %> <%= stack[i].toString() %><BR/> <% } %> </LI> </UL> <%= Calendar.getInstance().getTime() %> </BODY> </HTML> Este sería el resultado ahora: Y así podríamos hacer con todos los tipos de excepciones más habituales no controlados o que se pueden escapar mas fácilmente en el código, consiguiendo dar una imagen más corporativa y cercana al usuario (evidentemente la página de nuestro ejemplo no es ninguna de las dos cosas). 231 9.3 Gestión de la seguridad La seguridad, es un tema que no podemos descuidar cuando hablamos de aplicaciones web. Y las especificaciones Java EE tampoco la descuidan para nada. Imaginemos lo que podría pasar con aplicaciones web típicas de hoy en día sin una buena gestión de la seguridad: home banking, bróker online, tiendas virtuales, etc… En general, desde un punto de vista teórico, una buena gestión de la seguridad debe cubrir los siguientes puntos: Autenticación: Asegurarse que un cliente es quien dice ser. Por ejemplo, al acceder a una aplicación web se nos puede solicitar identificador y contraseña para validar que somos quien decimos ser. Existen otras opciones además del identificador y contraseña, como son los certificados digitales. Autorización: Asegurarse que un cliente accede solo a los recursos a los que puede/debe tener acceso y no a otros. Por ejemplo, si no somos administradores de una aplicación web, al entrar en el sistema no deberíamos poder acceder a opciones de configuración de la aplicación. Integridad de datos: Asegurarse que los mensajes llegan sin haber sido alterados por el camino. Por ejemplo, evitar que alguien pueda estar “pinchado” a la red y modifique el contenido de los mensajes que enviamos a un servidor. Confidencialidad: Asegurarse que solo las personas autorizadas pueden ver y acceder a información sensible. Por ejemplo, evitar que alguien pueda estar “pinchado” a la red y lea información sensible como contraseñas, números de cuenta y similares que fluye entre nuestro navegador y el servidor. No repudio: Asegurarse que quedan evidencias que eviten que un cliente pueda negar haber realizado una transacción contra el servidor. Por ejemplo, evitar que alguien pueda decir que no hizo algo en nuestro sistema. Auditoria: Asegurarse que queda registrado todos los eventos relacionados con la seguridad para posteriores estudios y análisis. Por ejemplo, registrar todos los intentos de acceso fallidos al sistema. No trataremos en profundidad todos los puntos pero si nos centraremos en algunos imprescindibles como son la autenticación y autorización. Otros como la integridad de datos o la confidencialidad los comentaremos brevemente ya que no dependen tanto de la aplicación sino de una correcta configuración de la infraestructura. Las especificaciones Java EE, nos ofrecen dos aproximaciones distintas al tema de la seguridad: 232 MÓDULO C – Unidad 9: Otros conceptos Programática: es decir, define una serie de APIs para que la gestión de la seguridad se realice directamente desde el código. APIs como Java Authentication and Authorization Service (JAAS) para realizar autenticación y autorización, Java Cryptography Extension (JCE) para realizar cifrados, Java Security Socket Extension (JSSE) para realizar comunicaciones seguras, etc… Declarativa: es decir, la posibilidad de delegar en los contenedores Java EE (Servidor de Aplicaciones Java EE) algunos de los temas relacionados con la gestión de la seguridad, permitiendo a los desarrolladores centrarse en la lógica de negocio de la aplicación y no en estos temas colaterales pero no menos importantes. La aproximación declarativa se realiza mediante el uso de los descriptores de despliegue o las anotaciones Java (a partir de Java EE 6.0). Ambas dos aproximaciones tienes pros y contras. La aproximación declarativa no cubre el 100% de las áreas ni es tan flexible como la programática, pero es mucho más sencilla, está sujeta a menos errores y cubre las necesidades habituales de autenticación y autorización. En este curso vamos a tratar solamente la aproximación declarativa, ya que no tiene sentido dedicar más unidades a estudiar APIs tan específicas. No obstante, todas están documentadas en la web: http://docs.oracle.com/javase/7/docs/technotes/guides/security/ Antes de entrar en los detalles Java EE, vamos a revisar unos conceptos teóricos previos imprescindibles como son: Realm, Usuario, Grupo y Rol. Realm: Recibe el nombre en inglés de Realm, el conjunto de usuarios y grupos identificados como válidos para una aplicación web junto con las políticas de seguridad asociadas. Podríamos decir que se trata de toda la configuración de seguridad de una aplicación web concreta. Usuario: Es el identificador unívoco de una persona o entidad de software que puede interactuar con la aplicación. Grupo: Es el identificador de un conjunto de usuarios que tienen algo en común. Rol: Es el identificador de un concepto abstracto que agrupa a varios usuarios individuales o grupos. Se utiliza para realizar la autorización de acceso a los recursos. ¿Cómo encajan estos conceptos juntos y cómo se utilizan para securizar una aplicación web? En el Servidor de Aplicaciones Java EE gestionado por los administradores de sistemas existen definidos usuarios y grupos. Dependiendo del Servidor de Aplicaciones Java EE y de la solución corporativa que haya instalada en cada caso, esto usuarios y grupos estarán definidos directamente en el servidor o están delegados en el Sistema Operativo, o en un Servidor de Directorio LDAP. Lo importante en nuestro caso es que ambos dos conceptos son un tema ajeno a la aplicación web y está delegado en la infraestructura. 233 Los roles sin embargo, si que son algo que definimos en la aplicación. Es un concepto abstracto de quién puede hacer qué. Es decir, en la aplicación diremos que a un recurso concreto solo puede acceder el rol A y B. Y a otro recurso distinto solo el rol A o por el contrario el rol C. El “truco” llega en el momento de la instalación de la aplicación donde el administrador encargado de dicho proceso mapeará el concepto abstracto de rol, con los conceptos reales de usuario y/o grupo. Y el conjunto de esas definiciones para una aplicación concreta, es lo que llamamos un Realm. Pero vayamos por partes. Ha quedado claro que primero necesitamos contar con una serie de usuarios y opcionalmente grupos en nuestro sistema. En el caso de este curso, estamos usando el Servidor de Aplicaciones Java EE Apache Tomcat 7.0.x que ofrece distintas alternativas de configuración. Optaremos por la más sencilla, que consiste en el uso de un fichero de configuración de usuarios: /conf/tomcatusers.xml La primera “dificultad” viene porque Apache Tomcat denomina rol lo que en la teoría de seguridad hemos llamado grupo. No pasa nada, pero evidentemente crea confusión. Así que es importante recordarlo a lo largo del resto de esta sección: lo que en la configuración de Apache Tomcat se denomina rol, realmente corresponde al concepto que hemos comentado de grupo. No tiene nada que ver con el concepto abstracto de rol que aplica a las aplicaciones web y que veremos mas adelante. Vamos a trabajar sobre un ejemplo donde tendremos seis usuarios: usuario1, usuario2, usuario3, usuario4, usuario5 y usuarios6, y tres grupos: clientes (representa usuarios normales de nuestra aplicación), operadores (representa usuarios especiales que pertenecen a nuestra empresa y que tienen más permisos que los usuarios cliente) y administradores (representa usuarios nuevamente de nuestra empresa que tienen todos los permisos). Los dos primeros usuarios pertenecen al primer grupo, los dos siguientes al segundo grupo y los dos últimos al tercero. Las contraseñas de cada usuario son su propio identificador, es decir, usuario1 tendrá como contraseña usuario1, y así sucesivamente. Vamos a editar el fichero de configuración en el entorno de desarrollo Eclipse IDE for Java EE Developers (recordemos que los ficheros de configuración de Apache Tomcat en este caso estarán en un proyecto denominado Servers): 234 MÓDULO C – Unidad 9: Otros conceptos El contenido del fichero debería ser algo así para nuestro ejemplo: <?xml version="1.0" encoding="UTF‐8"?> <tomcat‐users> <role rolename="clientes"/> <role rolename="operadores"/> <role rolename="administradores"/> <user username="usuario1" password="usuario1" roles="clientes"/> <user username="usuario2" password="usuario2" roles="clientes"/> <user username="usuario3" password="usuario3" roles="operadores"/> <user username="usuario4" password="usuario4" roles="operadores"/> <user username="usuario5" password="usuario5" roles="administradores"/> <user username="usuario6" password="usuario6" roles="administradores"/> </tomcat‐users> De esta manera, nuestro sistema ya cuenta con los usuarios y grupos definidos. Nota: Un usuario puede pertenecer a más de un grupo. En ese caso, simplemente se añadiría un grupo separado por coma a la definición del usuario. A continuación, vamos a desarrollar nuestra aplicación de ejemplo antes de seguir avanzando en la configuración de la seguridad. La aplicación va a ser muy sencilla, va a consistir en una página HTML de entrada con tres botones: clientes, operadores y administradores. Esta página es pública, es decir, será accesible por todo el mundo esté dado de alta en los sistemas como usuario o no. Cada uno de los tres botones, invocará a una JavaServer Page (JSP) distinta accesible por cada uno de los tres grupos. Y aquí vendrá el grueso del ejemplo porque los clientes solo podrán acceder a la página de clientes, los operadores podrán acceder tanto a la de clientes 235 como a la de operadores y los administradores a todas. Para ello, el navegador nos pedirá un usuario y una contraseña, para saber quién somos y si nos puede autorizar o no. Pero la configuración para que esto ocurra la veremos después de escribir el código. Página HTML (publica.html): <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Ejemplo de Seguridad</TITLE> </HEAD> <BODY> Esta página es pública y accesible por todo el mundo. <FORM method="POST" action="/EjemplosWeb/clientes.jsp"> <INPUT type="submit" value="Clientes"> </FORM> <FORM method="POST" action="/EjemplosWeb/operadores.jsp"> <INPUT type="submit" value="Operadores"> </FORM> <FORM method="POST" action="/EjemplosWeb/administradores.jsp"> <INPUT type="submit" value="Adminitradores"> </FORM> </BODY> </HTML> Página JSP (clientes.jsp): <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Ejemplo de Seguridad</TITLE> </HEAD> <BODY> Página de Clientes accesible por usuarios: <UL> <LI>Usuario1</LI> <LI>Usuario2</LI> <LI>Usuario3</LI> <LI>Usuario4</LI> <LI>Usuario5</LI> <LI>Usuario6</LI> </UL> </BODY> </HTML> Página JSP (operadores.jsp): <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 236 MÓDULO C – Unidad 9: Otros conceptos <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Ejemplo de Seguridad</TITLE> </HEAD> <BODY> Página de Clientes accesible por usuarios: <UL> <LI>Usuario3</LI> <LI>Usuario4</LI> <LI>Usuario5</LI> <LI>Usuario6</LI> </UL> </BODY> </HTML> Página JSP (administradores.jsp): <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Ejemplo de Seguridad</TITLE> </HEAD> <BODY> Página de Clientes accesible por usuarios: <UL> <LI>Usuario5</LI> <LI>Usuario6</LI> </UL> </BODY> </HTML> La clave viene ahora con el descriptor de despliegue, donde definiremos los roles que existen en nuestra aplicación, las restricciones de seguridad de los componentes de la aplicación, y el modo de autenticación al acceder a la aplicación. Los roles existentes en una aplicación web, se definen mediante los bloques “security-role” y “role-name”. En nuestro ejemplo: <security‐role> <role‐name>clientes</role‐name> </security‐role> <security‐role> <role‐name>operadores</role‐name> </security‐role> <security‐role> <role‐name>administradores</role‐name> </security‐role> Como ya comentamos en los conceptos teóricos, Grupo y Rol no tienen por qué ser lo mismo. Los roles son un concepto abstracto independiente de los sistemas de usuarios/grupos que 237 haya definidos en una empresa De esta manera se consigue una independencia entre la aplicación y los sistemas donde se vaya a desplegar. Es a la hora de instalar la aplicación, donde se hace un mapeo de roles con usuarios/grupos. Pero cómo se hace este mapeo es dependiente de cada Servidor de Aplicaciones Java EE. Cada uno lo realiza en un fichero de configuración distinto y con una sintaxis distinta. Pero es más, en el caso de Apache Tomcat 7.0.x dicho mapeo no existe y nos obliga a que los roles correspondan con nombres de grupos idénticos lo cual es una restricción importante. Las restricciones de acceso a los componentes de la aplicación web se definen mediante el bloque “security-constraint”, el cual a su vez contiene múltiples bloques. Veamos un ejemplo: <security‐constraint> <web‐resource‐collection> <web‐resource‐name>Area de Operadores</web‐resource‐name> <url‐pattern>/operadores.jsp</url‐pattern> <http‐method>GET</http‐method> <http‐method>POST</http‐method> </web‐resource‐collection> <auth‐constraint> <role‐name>administradores</role‐name> <role‐name>operadores</role‐name> </auth‐constraint> <user‐data‐constraint> <transport‐guarantee>NONE</transport‐guarantee> </user‐data‐constraint> </security‐constraint> Como se puede observar, primero se define el recurso o conjunto de recursos a proteger, identificados por una URL que podría usar comodines tipo: /*, o /*.jsp, etc… especificando además los protocolos HTTP de acceso a tener en consideración. Después de especifican los roles que tienen autorización a acceder a los recursos definidos anteriormente. Y por último, especifica si las comunicaciones deben ser seguras o no (utilizando HTTPS). Este bloque puede tomar tres valores: NONE: no existe requerimiento de comunicaciones seguras para acceder a los recursos definidos, es decir, HTTP. CONFIDENTIAL: las comunicaciones deben no ser legibles, por tanto se debe usar HTTPS para acceder a los recursos definidos. INTEGRAL: las comunicaciones deben asegurar su integridad, es decir, que nadie pueda haber cambiado el contenido por en medio entre el cliente y el servidor. A efectos prácticos implica lo mismo que CONFIDENTIAL, se debe usar HTTPS para acceder a los recursos definidos. De esta manera, definiríamos todas las posibles autorizaciones de acceso a los componentes de la aplicación web. 238 MÓDULO C – Unidad 9: Otros conceptos El modo de autenticación se define mediante los bloques “login-config” y “auth-method”. Por ejemplo, para que sea el navegador el que nos muestre una ventana de autenticación de usuario y contraseña: <login‐config> <auth‐method>BASIC</auth‐method> </login‐config> Existen múltiples métodos de autenticación. Brevemente son: BASIC: el servidor pedirá al cliente que solicite el usuario y contraseña. Estas viajaran codificadas en Base64 en claro (a no ser que las comunicaciones sean seguras). FORM: la aplicación web implementa la página de solicitud de usuario y contraseña, en vez de ser una ventana del navegador. La configuración en el descriptor de despliegue requiere algún parámetro adicional que veremos más tarde. CLIENT-CERT: requiere que el navegador envíe un certificado digital con la información del usuario. Este es el mecanismo que se utiliza por ejemplo en aplicaciones como la declaración de Hacienda en la AEAT. DIGEST: la información del usuario y la contraseña van cifradas utilizando alguno de los algoritmos soportados por ambos lados, cliente y servidor. Con todo esto, veamos cómo sería el descriptor de despliegue de nuestro ejemplo: <?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web‐app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web‐app_3_0.xsd" id="WebApp_ID" version="3.0"> <security‐role> <role‐name>clientes</role‐name> </security‐role> <security‐role> <role‐name>operadores</role‐name> </security‐role> <security‐role> <role‐name>administradores</role‐name> </security‐role> <security‐constraint> <web‐resource‐collection> <web‐resource‐name>Area de Adminitradores</web‐resource‐name> <url‐pattern>/administradores.jsp</url‐pattern> <http‐method>GET</http‐method> <http‐method>POST</http‐method> </web‐resource‐collection> <auth‐constraint> <role‐name>administradores</role‐name> </auth‐constraint> <user‐data‐constraint> <transport‐guarantee>NONE</transport‐guarantee> </user‐data‐constraint> </security‐constraint> 239 <security‐constraint> <web‐resource‐collection> <web‐resource‐name>Area de Operadores</web‐resource‐name> <url‐pattern>/operadores.jsp</url‐pattern> <http‐method>GET</http‐method> <http‐method>POST</http‐method> </web‐resource‐collection> <auth‐constraint> <role‐name>administradores</role‐name> <role‐name>operadores</role‐name> </auth‐constraint> <user‐data‐constraint> <transport‐guarantee>NONE</transport‐guarantee> </user‐data‐constraint> </security‐constraint> <security‐constraint> <web‐resource‐collection> <web‐resource‐name>Area de Clientes</web‐resource‐name> <url‐pattern>/clientes.jsp</url‐pattern> <http‐method>GET</http‐method> <http‐method>POST</http‐method> </web‐resource‐collection> <auth‐constraint> <role‐name>administradores</role‐name> <role‐name>operadores</role‐name> <role‐name>clientes</role‐name> </auth‐constraint> <user‐data‐constraint> <transport‐guarantee>NONE</transport‐guarantee> </user‐data‐constraint> </security‐constraint> <login‐config> <auth‐method>BASIC</auth‐method> </login‐config> </web‐app> Como se puede ver, definimos tres roles, tres colecciones de recursos con sus definiciones de acceso y el método de autenticación BASIC. Si ejecutamos el ejemplo siguiendo la siguiente secuencia lógica: 1. Accedemos a la página HTML principal pública que no está protegida. Deberíamos poder acceder y ver su contenido sin ningún tipo de problema. 240 MÓDULO C – Unidad 9: Otros conceptos 2. Solicitamos el acceso a la zona de Clientes ya protegida. Y el navegador debería mostrarnos una ventanita de autenticación. Nos autenticamos con usuario3/usuario3, es decir, con un miembro del grupo de operadores. Y deberíamos poder ver la zona de clientes sin problemas. 3. Navegamos hacia atrás y solicitamos el acceso a la zona de Operadores. Deberíamos poder acceder nuevamente sin problemas ya que el usuario3 pertenece al grupo de operadores. 241 4. Por último, navegamos hacia atrás y solicitamos el acceso a la zona de Administradores. En este caso deberíamos recibir un error HTTP 403 porque no tenemos autorización para acceder a esa zona. Se trata de un ejemplo muy sencillo pero bastante ilustrativo de la gestión de la seguridad en Java EE de forma declarativa. Antes de dar por finalizada esta sección, vamos a mostrar el uso del mecanismo de autenticación FORM (el del ejemplo anterior ha sido BASIC). Como indicábamos, FORM implica que la ventana de petición de usuario y contraseña es proporcionada por la aplicación web y no es una ventanita del navegador. El primer paso es modificar el descriptor de despliegue. En este caso debería ser algo así: <login‐config> <auth‐method>FORM</auth‐method> <form‐login‐config> <form‐login‐page>/login.jsp</form‐login‐page> <form‐error‐page>/loginerroneo.jsp</form‐error‐page> </form‐login‐config> </login‐config> Como configuración adicional, especificamos los componentes web que muestran la pantalla de autenticación, y la pantalla de error en caso de que la autenticación sea errónea. El formulario de la pantalla de autenticación debe tener en cuenta tres cosas: Debe invocar mediante HTTP POST un recurso denominado: j_security_check El usuario lo debe enviar en una variable denominada: j_username La contraseña debe enviarla en una variable denominada: j_password Por lo demás, la página puede mostrar la información como quiera. Por ejemplo: <%@ page language="java" contentType="text/html; charset=ISO‐8859‐1" pageEncoding="ISO‐8859‐1"%> 242 MÓDULO C – Unidad 9: Otros conceptos <!DOCTYPE html PUBLIC "‐//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD> <META http‐equiv="Content‐Type" content="text/html; charset=ISO‐ 8859‐1"> <TITLE>Ejemplo de Seguridad</TITLE> </HEAD> <BODY> <FORM method="POST" action="j_security_check"> <TABLE> <TR> <TD colspan="2">Página de ejemplo para login:</TD> </TR> <TR> <TD>Usuario:</td> <TD><INPUT type="text" name="j_username"></TD> </TR> <TR> <TD>Contraseña:</td> <TD><INPUT type="password" name="j_password"></TD> </TR> <TR> <TD colspan="2"><INPUT type="submit" value="Login"></TD> </TR> </TABLE> </FORM> </BODY> </HTML> En esta ocasión, cuando el servidor detecte que ha de pedir autenticación al cliente, en vez de solicitar al navegador que muestre una ventana devolverá nuestra página: Hemos mencionado en varias ocasiones las comunicaciones seguras basadas en el protocolo HTTPS (HTTP con SSL). Dado que se trata de un tema puramente de configuración y administración de los servidores, y que cada servidor lo hace de una manera distinta no vamos a detenernos en los detalles en este curso. 243 No obstante, la información de cómo se configuran las comunicaciones HTTPS en Apache Tomcat 7.0.x se encuentra en la siguiente URL: http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html 9.4 Frameworks de desarrollo web Uno de los objetivos habituales en el mundo de la informática es el de mejorar constantemente la productividad de los desarrolladores. Una manera es mejorando día a día las herramientas de desarrollo disponibles. Y otra, facilitando el desarrollo a través de frameworks que escondan las complejidades y eviten los trabajos repetitivos a los desarrolladores. Esta última es la que comentaremos brevemente a continuación. Un framework no es más que una librería de código junto con una metodología o forma de trabajar. Su objetivo, no es reinventar la rueda sino basándose en las tecnologías ya existentes intentar facilitar la vida al desarrollador. Durante este curso hemos estudiado las tecnologías, especificaciones y APIs en las que se basa el desarrollo web Java EE. Los frameworks que comentaremos brevemente a continuación no son algo totalmente distinto, sino que basándose en lo que ya hemos aprendido intentan mejorar la experiencia del desarrollador facilitando el mantenimiento del código, mejorando los interfaces visuales, evitando trabajos repetitivos, etc… pero siempre por debajo, lleva todo lo aprendido en este curso. Frameworks hay muchos, simplemente comentaremos cuatro de los más extendidos en el mercado laboral actualmente. 9.4.1 JavaServer Faces JavaServer Faces es una especificación más de Java EE. Nació como un framework separado y con el tiempo se adhesionó a las especificaciones. Siguiendo el estándar Java EE, esta debería ser la vía de evolución de los programadores web. ¿Y por qué no hemos estudiado esta opción entonces en este curso? Dos motivos: 1. Se basa en las especificaciones Java Servlet y JavaServer Page. Sin ese conocimiento adquirido en este curso, no podríamos entender JavaServer Faces. Sería como comenzar la casa por el tejado. 2. Adicionalmente, en el mercado siguen existiendo millones de aplicaciones basadas en Java Servlets y JavaServer Pages. La necesidad de estos conocimientos sigue estando ahí, es algo que no podemos obviar. 244 MÓDULO C – Unidad 9: Otros conceptos La idea que persigue JavaServer Faces es definir una serie de conceptos por encima de las APIs básicas como son: componentes visuales (controles con lógica avanzada reutilizables a utilizar en las páginas), beans de gestión (mecanismo que facilita la transmisión de información por el flujo de la aplicación), validadores (un sistema de control de errores en los valores de los campos), proveer de un controlador que ya implementa el control del flujo de la aplicación mediante un fichero de configuración, etc… Para profundizar en este framework: http://docs.oracle.com/javaee/6/tutorial/doc/bnaph.html 9.4.2 Apache Struts Fue el primer framework de este estilo que surgió en la Fundación Apache. Era muy sencillo de utilizar, y básicamente añadía la posibilidad de definir el flujo de la aplicación en un fichero de propiedades que un controlador del propio framework se encargaba de interpretar. Todas las peticiones pasaban por este controlador y él se encargaba de direccionar a la página que el fichero dijera dependiendo de los valores de distintos parámetros y/o condiciones. También añadía validadores para facilitar el control de errores de los formularios. Con la aparición de JavaServer Faces y otros frameworks como Spring se fue quedando algo obsoleto y cada vez se usa menos. Para profundizar en este framework: http://struts.apache.org/ 9.4.3 Spring Source Junto con JavaServer Faces, es uno de los frameworks mas extendidos. Nació como una ayuda mas a la programación web pero con el tiempo se fue extendiendo a otras muchas áreas como la programación transaccional, la persistencia de datos, la programación de batch, para móviles, etc… Para profundizar en este framework: http://www.springsource.org/documentation 245 9.4.4 Apache Wicket Este es un nuevo framework junto con Struts también perteneciente a la Fundación Apache. Su objetivo es conseguir separar lo máximo posible la presentación de la lógica, y definir un modelo de lógica de negocio basado en POJOs muy sencillo. No está muy extendido su uso. Para profundizar en este framework: http://wicket.apache.org/ 246 PARA RECORDAR Existen dos alternativas, según las especificaciones Java EE para la gestión de errores: Programática: Toda la gestión se lleva dentro de la aplicación mediante código fuente (por ejemplo mediante bloques try/catch). Declarativa: La gestión se externaliza en configuraciones en el descriptor de despliegue. Permite controlar: o Errores HTTP o Excepciones Java Una buena gestión de la seguridad debe cubrir los siguientes puntos: Autenticación: Asegurarse que un cliente es quien dice ser. Autorización: Asegurarse que un cliente accede solo a los recursos a los que puede/debe tener acceso y no a otros. Integridad de datos: Asegurarse que los mensajes llegan sin haber sido alterados por el camino. Confidencialidad: Asegurarse que solo las personas autorizadas pueden ver y acceder a información sensible. No repudio: Asegurarse que quedan evidencias que eviten que un cliente pueda negar haber realizado una transacción contra el servidor. Auditoria: Asegurarse que queda registrado todos los eventos relacionados con la seguridad para posteriores estudios y análisis. Las especificaciones Java EE, nos ofrecen dos aproximaciones distintas al tema de la seguridad: Programática: Define una serie de APIs para que la gestión de la seguridad se realice directamente desde el código. Declarativa: Delega en los contenedores Java EE (Servidor de Aplicaciones Java EE) algunos de los temas relacionados con la gestión de la seguridad. 247 Los métodos de autenticación´ comúnmente más usados son: BASIC: el servidor pedirá al cliente que solicite el usuario y contraseña FORM: la aplicación web implementa la página de solicitud de usuario y contraseña, en vez de ser una ventana del navegador. CLIENT-CERT: requiere que el navegador envíe un certificado digital con la información del usuario. DIGEST: la información del usuario y la contraseña van cifradas utilizando alguno de los algoritmos soportados por ambos lados, cliente y servidor. Los Frameworks más extendidos para el desarrollo de aplicaciones web son: JavaServer Faces es una especificación más de Java EE basada en Java Servlets y en JavaServer Pages (JSP). Apache Struts fue el primer framework de este estilo que surgió en la Fundación Apache. Spring Source nació como una ayuda mas a la programación web pero con el tiempo se fue extendiendo a otras muchas áreas como la programación transaccional, la persistencia de datos, la programación de batch, para móviles, etc… Apache Wicket. Su objetivo es conseguir separar lo máximo posible la presentación de la lógica, y definir un modelo de lógica de negocio basado en POJOs muy sencillo. 248