RED HAT® TRAINING Capacitación integral y práctica que resuelve los problemas del mundo real Red Hat Application Development I: Programming in Java EE Manual del alumno (ROLE) © 2018 Red Hat, Inc. JB183-EAP7.0-es-2-20180124 RED HAT APPLICATION DEVELOPMENT I: PROGRAMMING IN JAVA EE Red Hat Application Development I: Programming in Java EE EAP 7.0 JB183 Red Hat Application Development I: Programming in Java EE Edición 2 20180124 20180124 Autores: Editor: Jim Rigsbee, Richard Allred, Zachary Gutterman, Nancy K.A.N Seth Kenlon Copyright © 2018 Red Hat, Inc. The contents of this course and all its modules and related materials, including handouts to audience members, are Copyright © 2018 Red Hat, Inc. No part of this publication may be stored in a retrieval system, transmitted or reproduced in any way, including, but not limited to, photocopy, photograph, magnetic, electronic or other record, without the prior written permission of Red Hat, Inc. This instructional program, including all material provided herein, is supplied without any guarantees from Red Hat, Inc. Red Hat, Inc. assumes no liability for damages or legal action arising from the use or misuse of contents or details contained herein. If you believe Red Hat training materials are being used, copied, or otherwise improperly distributed please e-mail training@redhat.com or phone toll-free (USA) +1 (866) 626-2994 or +1 (919) 754-3700. Red Hat, Red Hat Enterprise Linux, the Shadowman logo, JBoss, Hibernate, Fedora, the Infinity Logo, and RHCE are trademarks of Red Hat, Inc., registered in the United States and other countries. Linux® is the registered trademark of Linus Torvalds in the United States and other countries. Java® is a registered trademark of Oracle and/or its affiliates. XFS® is a registered trademark of Silicon Graphics International Corp. or its subsidiaries in the United States and/or other countries. The OpenStack® Word Mark and OpenStack Logo are either registered trademarks/ service marks or trademarks/service marks of the OpenStack Foundation, in the United States and other countries and are used with the OpenStack Foundation's permission. We are not affiliated with, endorsed or sponsored by the OpenStack Foundation, or the OpenStack community. All other trademarks are the property of their respective owners. Colaboradores: Rob Locke, Bowe Strickland, Scott McBrien Convenciones del documento ix Notas y advertencias .............................................................................................. ix Introducción xi Red Hat Application Development I: Programming in Java EE ..................................... xi Orientación sobre el entorno del aula .................................................................... xii Internacionalización .............................................................................................. xiv 1. Transición a aplicaciones con varios niveles 1 Descripción de las aplicaciones empresariales .......................................................... 2 Cuestionario: Descripción de las aplicaciones empresariales ...................................... 4 Comparación de características de Java EE y Java SE .................................................. 8 Cuestionario: Comparación de Java EE y Java SE ...................................................... 13 Descripción del Proceso de la comunidad Java ........................................................ 17 Cuestionario: Descripción del Proceso de la comunidad Java (JCP) ............................ 20 Descripción de la arquitectura de aplicaciones con varios niveles ............................. 24 Cuestionario: Arquitectura de aplicaciones con varios niveles ................................... 29 Instalación de las herramientas de desarrollo de Java .............................................. 33 Ejercicio guiado: Ejecución de la aplicación To Do List .............................................. 38 Resumen .............................................................................................................. 45 2. Empaquetado e implementación de una aplicación de Java EE Descripción de un servidor de aplicaciones ............................................................ Cuestionario: Descripción de un servidor de aplicaciones ........................................ Identificación de recursos JNDI .............................................................................. Ejercicio guiado: Identificación de recursos JNDI ...................................................... Empaquetado e implementación de una aplicación de Java EE ................................. Ejercicio guiado: Empaquetado e implementación de una aplicación de Java EE ......... Trabajo de laboratorio: Empaquetado e implementación de aplicaciones en un servidor de aplicaciones ........................................................................................ Resumen .............................................................................................................. 47 48 52 56 59 62 66 75 83 3. Creación de Enterprise Java Beans 85 Conversión de un POJO en un EJB ......................................................................... 86 Ejercicio guiado: Creación de un EJB sin estado ....................................................... 99 Acceso local y remoto a un EJB ............................................................................ 106 Ejercicio guiado: Acceso remoto a un EJB .............................................................. 110 Descripción del ciclo de vida de un EJB ................................................................. 117 Cuestionario: El ciclo de vida de un EJB ................................................................ 121 Delimitación de transacciones implícitas y explícitas .............................................. 125 Ejercicio guiado: Delimitación de transacciones ..................................................... 131 Trabajo de laboratorio: Creación de Enterprise Java Beans ..................................... 137 Resumen ............................................................................................................. 145 4. Gestión de la persistencia Descripción de la API de persistencia ................................................................... Cuestionario: Descripción de la API de persistencia ............................................... Persistencia de datos ........................................................................................... Ejercicio guiado: Persistencia de datos .................................................................. Anotación de clases para validar beans ................................................................ Ejercicio guiado: Validación de datos .................................................................... Creación de consultas .......................................................................................... Ejercicio guiado: Creación de consultas ................................................................. Trabajo de laboratorio: Gestión de la persistencia ................................................. JB183-EAP7.0-es-2-20180124 147 148 157 159 168 176 180 185 192 198 v Red Hat Application Development I: Programming in Java EE Resumen ............................................................................................................. 206 5. Administración de relaciones entre entidades 207 Configuración de relaciones entre entidades ......................................................... 208 Ejercicio guiado: Configuración de relaciones entre entidades ................................ 220 Descripción de relaciones de varias entidades con varias entidades ........................ 229 Cuestionario: Descripción de relaciones de varias entidades con varias entidades .... 233 Trabajo de laboratorio: Administración de relaciones entre entidades ..................... 237 Resumen ............................................................................................................. 244 6. Creación de servicios REST Descripción de conceptos de servicios web ........................................................... Cuestionario: Servicios web .................................................................................. Creación de servicios REST con JAX-RS .................................................................. Ejercicio guiado: Exposición de un servicio REST .................................................... Consumo de un servicio REST .............................................................................. Cuestionario: Consumo de un servicio REST .......................................................... Trabajo de laboratorio: Creación de servicios REST ................................................ Resumen ............................................................................................................. 245 246 250 252 260 267 273 276 286 7. Implementación de Contextos e Inyección de dependencia (CDI) 287 Contraste entre la inyección de dependencias y la inyección de recursos ................. 288 Ejercicio guiado: Inyección de dependencia ........................................................... 293 Aplicación de alcances contextuales ..................................................................... 300 Ejercicio guiado: Aplicación de alcances ................................................................ 303 Trabajo de laboratorio: Implementación de Contextos e Inyección de dependencia ....................................................................................................... 310 Resumen ............................................................................................................. 318 8. Creación de aplicaciones de mensajería con JMS 319 Descripción de conceptos de mensajería .............................................................. 320 Cuestionario: Descripción de conceptos de mensajería .......................................... 324 Descripción de la arquitectura de JMS .................................................................. 327 Cuestionario: Descripción de la arquitectura de JMS .............................................. 333 Creación de un Cliente JMS .................................................................................. 336 Ejercicio guiado: Creación de un cliente JMS .......................................................... 341 Creación de MDB ................................................................................................ 346 Ejercicio guiado: Creación de un Bean controlado por mensajes ............................. 350 Trabajo de laboratorio: Creación de aplicaciones de mensajería con JMS ................. 356 Resumen ............................................................................................................. 366 9. Protección de aplicaciones Java EE 367 Descripción de la especificación de JAAS ............................................................... 368 Cuestionario: Descripción de la especificación de JAAS ........................................... 373 Configuración de un dominio de seguridad en JBoss EAP ....................................... 376 Ejercicio guiado: Configuración de un dominio de seguridad en JBoss EAP ............... 380 Protección de una API REST ................................................................................. 385 Ejercicio guiado: Protección de una API REST ........................................................ 391 Trabajo de laboratorio: Protección de aplicaciones Java EE ..................................... 399 Resumen ............................................................................................................. 414 10. Revisión completa: Red Hat Application Development I: Programming in Java EE 415 Revisión completa .............................................................................................. 416 Trabajo de laboratorio: Creación de una API mediante JAX-RS ................................. 418 vi JB183-EAP7.0-es-2-20180124 Trabajo de laboratorio: Persistencia de datos con JPA ............................................ 436 Trabajo de laboratorio: Protección de la API REST con JAAS .................................... 458 JB183-EAP7.0-es-2-20180124 vii viii Convenciones del documento Notas y advertencias Nota Las "notas" son consejos, atajos o enfoques alternativos para una tarea determinada. Omitir una nota no debe tener consecuencias negativas, pero quizás se pase por alto algún truco que puede simplificar una tarea. Importante En las cajas (boxes) con la etiqueta "Importante", se detallan cosas que se olvidan con facilidad: cambios de configuración que solo se aplican a la sesión actual o servicios que se deben reiniciar para poder aplicar una actualización. Omitir una caja (box) con la etiqueta "Importante" no provocará pérdida de datos, pero puede causar irritación y frustración. Advertencia No se deben omitir las "advertencias". Es muy probable que omitir las advertencias provoque pérdida de datos. Referencias En "Referencias", se describe el lugar donde se puede encontrar documentación externa relevante para un tema. JB183-EAP7.0-es-2-20180124 ix x Introducción Red Hat Application Development I: Programming in Java EE Red Hat I Application Development I: Programming in Java EE (JB183) expone a los desarrolladores experimentados de Java Standard Edition (Java SE) al mundo de Java Enterprise Edition (Java EE). Los estudiantes aprenderán las diferentes especificaciones que constituyen Java EE. A través de trabajos prácticos de laboratorio, los estudiantes transformarán una aplicación de línea de comandos de Java SE simple en una aplicación empresarial de varios niveles mediante diferentes especificaciones de Java EE, incluidos Enterprise Java Beans, API de persistencia Java, Servicio de mensajería Java, JAX-RS para servicios REST, Contextos e Inyección de dependencia, y seguridad. Objetivos • Describir las diferencias entre las arquitecturas de aplicaciones de Java SE y Java EE. • Crear componentes de aplicaciones mediante las especificaciones EJB, JPA, JAX-RS, CDI, JMS y JAAS. • Desarrollar los componentes de backend necesarios para respaldar una aplicación web de tres niveles mediante herramientas Maven y JBoss. • Implementar aplicaciones en Red Hat JBoss Enterprise Application Platform. Destinatarios • Desarrolladores con experiencia en Java SE. Requisitos previos • Dominio en el desarrollo de aplicaciones de Java SE. • Dominio en el uso de un IDE como Red Hat Developer Studio o Eclipse. • Se recomienda experiencia en Maven. JB183-EAP7.0-es-2-20180124 xi Introducción Orientación sobre el entorno del aula Figura 0.1: Entorno del aula En este curso, el sistema de cómputo principal utilizado para las actividades prácticas de aprendizaje es workstation. Los estudiantes también utilizarán otra máquina, services, para estas actividades. Ambos sistemas se encuentran en el dominio DNS lab.example.com. Todos los sistemas de cómputo de los estudiantes tienen una cuenta de usuario estándar (student) con la contraseña student. La contraseña root de todos los sistemas de los estudiantes es redhat. Máquinas del aula Nombre de la máquina Direcciones IP Rol workstation.lab.example.com 172.25.250.254 Estación de trabajo gráfica utilizada para la administración del sistema services.lab.example.com Aloja el repositorio Nexus 172.25.250.10 Una función adicional de workstation es que actúa como enrutador entre la red que conecta las máquinas de los estudiantes y la red del aula. Si la workstation está apagada, otras máquinas de estudiantes solo podrán acceder a sistemas en la red de estudiantes. Hay varios sistemas en el aula que brindan servicios de soporte. Dos servidores, content.example.com y materials.example.com son fuentes de software y materiales del trabajo de laboratorio utilizados en actividades prácticas. Se proveerá información sobre cómo utilizar estos servidores en las instrucciones para estas actividades. Control de la estación En la parte superior de la consola, se describe el estado de su máquina. xii JB183-EAP7.0-es-2-20180124 Orientación sobre el entorno del aula Estados de la máquina Estado Descripción none (ninguno) Todavía no se ha iniciado su máquina. Cuando se inicie, su máquina arrancará en un estado recientemente inicializado (el disco se habrá restablecido). starting (en inicio) Su máquina está por arrancar. running (en ejecución) Su máquina se está ejecutando y está disponible (o bien, cuando arranque, pronto lo estará). stopping (en detención) Su máquina está por apagarse. stopped (detenida) Su máquina se ha apagado completamente. Al iniciarse, su máquina arrancará en el mismo estado en el que estaba cuando se apagó (el disco se preserva). impaired (afectada) No se puede realizar una conexión de red en su máquina. En general, este estado se logra cuando un estudiante ha corrompido las reglas de conexión de la red o del cortafuegos. Si se restablece la máquina y el estado permanece, o si es intermitente, deberá abrir un caso de soporte. Según el estado de su máquina, tendrá disponibles una selección de las siguientes acciones. Acciones de la máquina Acción Descripción Start Station (Iniciar estación) Iniciar ("encender") la máquina. Stop Station (Detener estación) Detener ("apagar") la máquina y preservar el contenido del disco. Reset Station (Restablecer estación) Detener ("apagar") la máquina y restablecer el disco de modo que vuelva a su estado original. Precaución: Se perderá cualquier trabajo generado en el disco. Refresh (Actualizar) Si se actualiza la página, se volverá a realizar un sondeo del estado de la máquina. Increase Timer (Aumentar temporizador) Agrega 15 minutos al temporizador para cada clic. Temporizador de la estación Su inscripción al aprendizaje en línea de Red Hat le da derecho a una cierta cantidad de tiempo de uso del equipo. Para ayudarlo a conservar su tiempo, las máquinas tienen un temporizador asociado, que se inicializa en 60 minutos cuando se inicia su máquina. El temporizador funciona como un "switch de seguridad", que disminuye mientras funciona su máquina. Si el temporizador se reduce por debajo de 0, puede optar por incrementar el temporizador. JB183-EAP7.0-es-2-20180124 xiii Introducción Internacionalización Soporte de idioma Red Hat Enterprise Linux 7 admite oficialmente 22 idiomas: inglés, asamés, bengalí, chino (simplificado), chino (tradicional), francés, alemán, guyaratí, hindi, italiano, japonés, canarés, coreano, malayalam, maratí, oriya, portugués (brasileño), panyabí, ruso, español, tamil y telugú. Selección de idioma por usuario Es posible que los usuarios prefieran usar un idioma para su entorno de escritorio distinto al predeterminado del sistema. Quizás también quieran definir su cuenta para usar una distribución del teclado o un método de entrada distinto. Configuración de idioma En el entorno de escritorio GNOME, posiblemente el usuario deba definir el idioma de su preferencia y el método de entrada la primera vez que inicie sesión. Si no es así, la manera más simple para un usuario individual de definir el idioma de su preferencia y el método de entrada es usando la aplicación Region & Language (Región e idioma). Ejecute el comando gnome-control-center region o, en la barra superior, seleccione (Usuario) > Settings(Configuración). En la ventana que se abre, seleccione Region & Language (Región e idioma). El usuario puede hacer clic en la caja (box) Language (Idioma) y seleccionar el idioma de su preferencia en la lista que aparece. Esto también actualizará la configuración de Formats (Formatos) mediante la adopción del valor predeterminado para ese idioma. La próxima vez que el usuario inicie sesión, se efectuarán los cambios. Estas configuraciones afectan el entorno de escritorio GNOME y todas las aplicaciones, incluida gnome-terminal, que se inician dentro de este. Sin embargo, no se aplican a la cuenta si el acceso a ella es mediante un inicio de sesión ssh desde un sistema remoto o desde una consola de texto local (como tty2). xiv JB183-EAP7.0-es-2-20180124 Selección de idioma por usuario nota Un usuario puede hacer que su entorno de intérprete de comandos use la misma configuración de LANG que su entorno gráfico, incluso cuando inicia sesión mediante una consola de texto o mediante ssh. Una manera de hacer esto es colocar un código similar al siguiente en el archivo ~/.bashrc del usuario. Este código de ejemplo definirá el idioma empleado en un inicio de sesión en interfaz de texto, de modo que coincida con el idioma actualmente definido en el entorno de escritorio GNOME del usuario: i=$(grep 'Language=' /var/lib/AccountService/users/${USER} \ | sed 's/Language=//') if [ "$i" != "" ]; then export LANG=$i fi Es posible que algunos idiomas, como el japonés, coreano, chino y otros con un conjunto de caracteres no latinos, no se vean correctamente en consolas de texto locales. Se pueden crear comandos individuales para utilizar otro idioma mediante la configuración de la variable LANG en la línea de comandos: [user@host ~]$ LANG=fr_FR.utf8 date jeu. avril 24 17:55:01 CDT 2014 Los comandos subsiguientes se revertirán y utilizarán el idioma de salida predeterminado del sistema. El comando locale se puede usar para comprobar el valor actual de LANG y otras variables de entorno relacionadas. Configuración del método de entrada GNOME 3 en Red Hat Enterprise Linux 7 emplea de manera automática el sistema de selección de método de entrada IBus, que permite cambiar las distribuciones del teclado y los métodos de entrada de manera rápida y sencilla. La aplicación Region & Language (Región e idioma) también se puede usar para habilitar métodos de entrada alternativos. En la ventana de la aplicación Region & Language (Región e idioma), la caja (box) Input Sources (Fuentes de entrada) muestra los métodos de entrada disponibles en este momento. De forma predeterminada, es posible que English (US) (Inglés [EE. UU.]) sea el único método disponible. Resalte English (US) (Inglés [EE. UU.]) y haga clic en el icono de keyboard (teclado) para ver la distribución actual del teclado. Para agregar otro método de entrada, haga clic en el botón +, en la parte inferior izquierda de la ventana Input Sources (Fuentes de entrada). Se abrirá la ventana Add an Input Source (Agregar una fuente de entrada). Seleccione su idioma y, luego, el método de entrada o la distribución del teclado de su preferencia. Una vez que haya más de un método de entrada configurado, el usuario puede alternar entre ellos rápidamente escribiendo Super+Space (en ocasiones denominado Windows+Space). También aparecerá un indicador de estado en la barra superior de GNOME con dos funciones: por un lado, indica el método de entrada activo; por el otro lado, funciona como un menú JB183-EAP7.0-es-2-20180124 xv Introducción que puede usarse para cambiar de un método de entrada a otro o para seleccionar funciones avanzadas de métodos de entrada más complejos. Algunos de los métodos están marcados con engranajes, que indican que tienen opciones de configuración y capacidades avanzadas. Por ejemplo, el método de entrada japonés Japanese (Kana Kanji) (japonés [Kana Kanji]) le permite al usuario editar previamente texto en latín y usar las teclas de Down Arrow (flecha hacia abajo) y Up Arrow (flecha hacia arriba) para seleccionar los caracteres correctos que se usarán. El indicador también puede ser de utilidad para los hablantes de inglés de Estados Unidos. Por ejemplo, dentro de English (United States) (Inglés [Estados Unidos]) está la configuración del teclado English (international AltGr dead keys) (Inglés [internacional, teclas inactivas AltGr]), que trata AltGr (o la tecla Alt derecha) en un teclado de 104/105 teclas de una PC como una tecla modificadora "Bloq Mayús secundaria" y tecla de activación de teclas inactivas para escribir caracteres adicionales. Hay otras distribuciones alternativas disponibles, como Dvorak. nota Cualquier carácter Unicode puede ingresarse en el entorno de escritorio GNOME, si el usuario conoce el código Unicode del carácter, escribiendo Ctrl+Shift+U, seguido por el código. Después de ingresar Ctrl+Shift+U, aparecerá una u subrayada que indicará que el sistema espera la entrada del código Unicode. Por ejemplo, la letra del alfabeto griego en minúscula lambda tiene el código U +03BB y puede ingresarse escribiendo Ctrl+Shift+U, luego 03bb y, por último, Enter. Configuración de idioma predeterminado en todo el sistema El idioma predeterminado del sistema está definido en US English (inglés de EE. UU.), que utiliza la codificación UTF-8 de Unicode como su conjunto de caracteres (en_US.utf8), pero puede cambiarse durante o después de la instalación. Desde la línea de comandos, root puede cambiar los ajustes de configuración regional de todo el sistema con el comando localectl. Si localectl se ejecuta sin argumentos, mostrará los ajustes actuales de configuración regional de todo el sistema. Para configurar el idioma de todo el sistema, ejecute el comando localectl set-locale LANG=locale, donde locale es el $LANG adecuado de la tabla "Referencia de códigos de idioma" en este capítulo. El cambio tendrá efecto para usuarios en su próximo inicio de sesión y se almacena en /etc/locale.conf. [root@host ~]# localectl set-locale LANG=fr_FR.utf8 En GNOME, un usuario administrativo puede cambiar esta configuración en Region & Language (Región e idioma) y con un clic en el botón Login Screen (Pantalla de inicio de sesión) ubicado en la esquina superior derecha de la ventana. Al cambiar la opción de Language (Idioma) de la pantalla de inicio de sesión, también ajustará el valor de idioma predeterminado de todo el sistema en el archivo de configuración /etc/locale.conf. xvi JB183-EAP7.0-es-2-20180124 Paquetes de idiomas Importante Las consolas de texto locales como tty2 están más limitadas en las fuentes que pueden mostrar que las sesiones gnome-terminal y ssh. Por ejemplo, los caracteres del japonés, coreano y chino posiblemente no se visualicen como se espera en una consola de texto local. Por este motivo, es posible que tenga sentido usar el inglés u otro idioma con un conjunto de caracteres latinos para la consola de texto del sistema. De manera similar, las consolas de texto locales admiten una cantidad de métodos de entrada más limitada y esto se administra de manera separada desde el entorno de escritorio gráfico. La configuración de entrada global disponible se puede configurar mediante localectl tanto para consolas virtuales de texto locales como para el entorno gráfico X11. Para obtener más información, consulte las páginas del manual localectl(1), kbd(4) y vconsole.conf(5). Paquetes de idiomas Si utiliza un idioma diferente al inglés, posiblemente desee instalar "paquetes de idiomas" adicionales para disponer de traducciones adicionales, diccionarios, etc. Para ver la lista de paquetes de idiomas disponibles, ejecute yum langavailable. Para ver la lista de paquetes de idiomas actualmente instalados en el sistema, ejecute yum langlist. Para agregar un paquete de idioma adicional al sistema, ejecute yum langinstall code, donde code es el código en corchetes después del nombre del idioma en la salida de yum langavailable. Referencias Páginas del manual: locale(7), localectl(1), kbd(4), locale.conf(5), vconsole.conf(5), unicode(7), utf-8(7) y yum-langpacks(8). Las conversiones entre los nombres de los diseños X11 del entorno de escritorio gráfico y sus nombres en localectl se pueden encontrar en el archivo /usr/ share/X11/xkb/rules/base.lst. JB183-EAP7.0-es-2-20180124 xvii Introducción Referencia de códigos de idioma Códigos de idioma Idioma Valor $LANG Inglés (EE. UU.) en_US.utf8 Asamés as_IN.utf8 Bengalí bn_IN.utf8 Chino (simplificado) zh_CN.utf8 Chino (tradicional) zh_TW.utf8 Francés fr_FR.utf8 Alemán de_DE.utf8 Guyaratí gu_IN.utf8 Hindi hi_IN.utf8 Italiano it_IT.utf8 Japonés ja_JP.utf8 Canarés kn_IN.utf8 Coreano ko_KR.utf8 Malayalam ml_IN.utf8 Maratí mr_IN.utf8 Odia or_IN.utf8 Portugués (brasileño) pt_BR.utf8 Panyabí pa_IN.utf8 Ruso ru_RU.utf8 Español es_ES.utf8 Tamil ta_IN.utf8 Telugú te_IN.utf8 xviii JB183-EAP7.0-es-2-20180124 TRAINING CAPÍTULO 1 TRANSICIÓN A APLICACIONES CON VARIOS NIVELES Descripción general Meta Describir características de Java EE y distinguir entre las aplicaciones Java EE y Java SE. Objetivos • Describir "aplicación empresarial" y nombrar algunos de los beneficios de las aplicaciones Java EE. • Comparar las características de Java EE y Java SE. • Describir las especificaciones y los números de versión de Java EE 7 y el proceso utilizado para introducir API nuevas y actualizadas en Java EE. • Describir las diferentes arquitecturas con varios niveles. • Instalar JBoss Developer Studio, Maven y JBoss Enterprise Application Platform. Secciones • Descripción de las aplicaciones empresariales (y cuestionario) • Comparación de características de Java EE y Java SE (y cuestionario) • Descripción del Proceso de la comunidad Java (y cuestionario) • Descripción de la arquitectura de aplicaciones con varios niveles (y cuestionario) • Instalación de las herramientas de desarrollo de Java (y ejercicio guiado) JB183-EAP7.0-es-2-20180124 1 Capítulo 1. Transición a aplicaciones con varios niveles Descripción de las aplicaciones empresariales Objetivos Después de completar esta sección, los estudiantes deberán ser capaces de describir los conceptos y beneficios básicos de las aplicaciones empresariales. Aplicaciones empresariales Una aplicación empresarial es una aplicación de software generalmente utilizada en grandes organizaciones de negocios. Las aplicaciones empresariales a menudo proporcionan las siguientes funciones: • Soporte para usuarios simultáneos y sistemas externos. • Soporte para comunicación sincrónica y asíncrona a través de diferentes protocolos. • Capacidad de manejar cargas de trabajo de transacciones que coordinan entre repositorios de datos, como colas y bases de datos. • Soporte para que la escalabilidad maneje el crecimiento futuro. • Una plataforma flexible y distribuida para garantizar alta disponibilidad. • Soporte para un control de acceso altamente seguro para diferentes tipos de usuarios. • La capacidad de integrarse con sistemas de back-end y servicios web. Ejemplos típicos de aplicaciones empresariales incluyen la Planificación de recursos de empresa (ERP), la Gestión de relaciones con el cliente (CRM), los Sistemas de gestión de contenido (CMS), sistemas de comercio electrónico, Internet y portales de intranet. Beneficios de las aplicaciones empresariales Java EE Java Enterprise Edition (Java EE) es una especificación para desarrollar aplicaciones empresariales utilizando Java. Es un estándar independiente de la plataforma, desarrollado con la orientación del Proceso de la comunidad Java (JCP). La especificación Java EE 7 consta de una serie de interfaces de programación de aplicaciones (API) de componentes implementadas por un servidor de aplicaciones. La Red Hat JBoss Enterprise Application Platform (EAP) que utilizará en este curso implementa el estándar Java EE. Los beneficios de desarrollar aplicaciones empresariales basadas en Java EE son: • Las aplicaciones independientes de la plataforma se pueden desarrollar y se ejecutarán en muchos tipos diferentes de sistemas operativos (tanto en PC pequeñas como en grandes mainframes). • Las aplicaciones son portátiles en servidores de aplicaciones que cumplen con Java EE, debido al estándar Java EE. 2 JB183-EAP7.0-es-2-20180124 Beneficios de las aplicaciones empresariales Java EE • La especificación Java EE proporciona un gran número de API, generalmente usadas por aplicaciones empresariales, como servicios web, mensajería asíncrona, transacciones, conectividad de la base de datos, conjuntos (pools) de hilos, utilidades de procesamiento por lotes y seguridad. No hay necesidad de desarrollar estos componentes en forma manual, de modo que es posible reducir el tiempo de desarrollo. • Un gran número de aplicaciones y componentes de terceros, listos para usar, dirigidos a dominios específicos, como finanzas, seguros, telecomunicaciones y otros sectores, están certificados para ejecutarse e integrarse con servidores de aplicaciones Java EE. • Un gran número de herramientas sofisticadas, como IDE, sistemas de monitoreo, marcos (frameworks) de integración de aplicaciones empresariales (EAI) y herramientas de medición de rendimiento, están disponibles para aplicaciones Java EE de terceros proveedores. Referencias JCP https://www.jcp.org/en/home/index JB183-EAP7.0-es-2-20180124 3 Capítulo 1. Transición a aplicaciones con varios niveles Cuestionario: Descripción de las aplicaciones empresariales Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuáles dos de los siguientes enunciados se pueden considerar una aplicación empresarial? (Elija dos opciones). a. b. c. d. e. 2. ¿Cuáles dos de los siguientes enunciados acerca de la especificación Java EE y los servidores de aplicaciones son correctos? (Elija dos opciones). a. b. c. d. e. 3. Existen diferentes versiones de la especificación para diferentes sistemas operativos. Las aplicaciones se deben implementar de manera diferente en cada sistema operativo, aprovechando las funciones específicas del sistema operativo. Aunque una aplicación se implementa para cumplir completamente con Java EE, necesita reimplementar ciertas funciones y recopilar la aplicación al implementarla en diferentes servidores que cumplen con Java EE. Una aplicación que cumpla completamente con Java EE se puede implementar en diferentes servidores que cumplen con Java EE, sin tener que recopilar ni reimplementar funciones. Un servidor de aplicaciones que cumple con Java EE proporciona instalaciones de mensajería asíncrona. Un servidor de aplicaciones que cumple con Java EE no proporciona funciones de agrupación de hilos de manera predeterminada. El desarrollador debe implementar esta función en forma manual. ¿Qué enunciado que describe un servidor de aplicaciones que cumple con Java EE es correcto? a. b. c. d. 4 Un sistema bancario en línea para un banco con millones de clientes. Un programa que calcula el análisis factorial de los números entre 1 y 100 000. Un sistema incorporado en tiempo real que controla un satélite remoto. Una puerta de enlace de pago en línea para una empresa de tarjetas de crédito que procesa millones de transacciones por día. Un programa que simula una representación 3D de un avión para estudiar el impacto de la turbulencia en aviones de diferentes formas y tamaños. La capacidad de crear servicios web (SOAP, REST) no se proporciona de manera predeterminada. No hay instalaciones de gestión de transacciones disponibles. El desarrollador debe gestionar todas las transacciones en forma manual. El servidor de aplicaciones proporciona una gestión de transacciones automática. De requerirse, el desarrollador puede escribir código para también gestionar transacciones en forma manual. No se proporcionan API de conectividad de base de datos (JPA) de manera predeterminada. Para la conexión a las bases de datos, se deben utilizar librerías externas de terceros. JB183-EAP7.0-es-2-20180124 4. Una empresa llamada ABC Inc está migrando una aplicación de mainframe heredada grande y compleja, escrita en COBOL™, a la plataforma Java EE. ¿Cuáles tres de las siguientes funciones se pueden aprovechar desde un servidor de aplicaciones que cumple con Java EE sin tener que implementarlas en forma manual? (Elija tres opciones). a. b. c. d. e. Conectividad de la base de datos a un RDBMS (que cumple con JDBC). Una utilidad que lee y escribe archivos codificados de EBCDIC hacia y desde un sistema de mainframe heredado con un sistema de archivos propietario. Seguridad basada en funciones. Operaciones de lotes para una ejecución programada de una aplicación de informes, que genera informes de un RDBMS diaria, mensual y trimestralmente. Un adaptador personalizado para comunicarse con un sistema de gestión de base de datos jerárquico heredado remoto que no cumple con JDBC. JB183-EAP7.0-es-2-20180124 5 Capítulo 1. Transición a aplicaciones con varios niveles Solución Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuáles dos de los siguientes enunciados se pueden considerar una aplicación empresarial? (Elija dos opciones). a. b. c. d. e. 2. ¿Cuáles dos de los siguientes enunciados acerca de la especificación Java EE y los servidores de aplicaciones son correctos? (Elija dos opciones). a. b. c. d. e. 3. b. c. d. 6 Existen diferentes versiones de la especificación para diferentes sistemas operativos. Las aplicaciones se deben implementar de manera diferente en cada sistema operativo, aprovechando las funciones específicas del sistema operativo. Aunque una aplicación se implementa para cumplir completamente con Java EE, necesita reimplementar ciertas funciones y recopilar la aplicación al implementarla en diferentes servidores que cumplen con Java EE. Una aplicación que cumpla completamente con Java EE se puede implementar en diferentes servidores que cumplen con Java EE, sin tener que recopilar ni reimplementar funciones. Un servidor de aplicaciones que cumple con Java EE proporciona instalaciones de mensajería asíncrona. Un servidor de aplicaciones que cumple con Java EE no proporciona funciones de agrupación de hilos de manera predeterminada. El desarrollador debe implementar esta función en forma manual. ¿Qué enunciado que describe un servidor de aplicaciones que cumple con Java EE es correcto? a. 4. Un sistema bancario en línea para un banco con millones de clientes. Un programa que calcula el análisis factorial de los números entre 1 y 100 000. Un sistema incorporado en tiempo real que controla un satélite remoto. Una puerta de enlace de pago en línea para una empresa de tarjetas de crédito que procesa millones de transacciones por día. Un programa que simula una representación 3D de un avión para estudiar el impacto de la turbulencia en aviones de diferentes formas y tamaños. La capacidad de crear servicios web (SOAP, REST) no se proporciona de manera predeterminada. No hay instalaciones de gestión de transacciones disponibles. El desarrollador debe gestionar todas las transacciones en forma manual. El servidor de aplicaciones proporciona una gestión de transacciones automática. De requerirse, el desarrollador puede escribir código para también gestionar transacciones en forma manual. No se proporcionan API de conectividad de base de datos (JPA) de manera predeterminada. Para la conexión a las bases de datos, se deben utilizar librerías externas de terceros. Una empresa llamada ABC Inc está migrando una aplicación de mainframe heredada grande y compleja, escrita en COBOL™, a la plataforma Java EE. ¿Cuáles tres de las siguientes funciones se pueden aprovechar desde un servidor de JB183-EAP7.0-es-2-20180124 Solución aplicaciones que cumple con Java EE sin tener que implementarlas en forma manual? (Elija tres opciones). a. b. c. d. e. Conectividad de la base de datos a un RDBMS (que cumple con JDBC). Una utilidad que lee y escribe archivos codificados de EBCDIC hacia y desde un sistema de mainframe heredado con un sistema de archivos propietario. Seguridad basada en funciones. Operaciones de lotes para una ejecución programada de una aplicación de informes, que genera informes de un RDBMS diaria, mensual y trimestralmente. Un adaptador personalizado para comunicarse con un sistema de gestión de base de datos jerárquico heredado remoto que no cumple con JDBC. JB183-EAP7.0-es-2-20180124 7 Capítulo 1. Transición a aplicaciones con varios niveles Comparación de características de Java EE y Java SE Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de realizar lo siguiente: • Describir las diferencias entre Java EE y Java SE. • Ejecutar la aplicación desde la línea de comando. • Obtener una vista previa de la aplicación web. Comparación de Java Enterprise Edition (Java EE) y Java SE Cuando instala el kit Java Development Kit (JDK) para su sistema operativo, proporciona el recopilador, el depurador, las herramientas y el entorno de tiempo de ejecución para alojar sus aplicaciones Java, la Máquina virtual Java (JVM) y un gran conjunto de clases de componentes reutilizables comúnmente usados por aplicaciones. La interfaz de programación de aplicaciones (API) proporciona paquetes y clases para redes, E/S, análisis XML, conectividad de base de datos, desarrollo de interfaces gráficas de usuario (GUI) y mucho más. Esta API se conoce comúnmente como Java Standard Edition (Java SE). Java SE generalmente se usa para desarrollar programas, herramientas y utilidades independientes, que se ejecutan principalmente desde la línea de comando, programas de GUI y procesos de servidores que necesitan ejecutarse como daemons (esto es, programas que se ejecutan continuamente en segundo plano, hasta que se detienen). La especificación Java EE es un conjunto de API basado en Java SE. Proporciona un entorno de tiempo de ejecución para ejecutar aplicaciones empresariales multiproceso, transaccionales, seguras y escalables. Es importante observar que, a diferencia de Java SE, Java EE es básicamente un conjunto de especificaciones estándares para una API, y los entornos de tiempo de ejecución que implementan estas API generalmente se denominan servidores de aplicaciones. Un servidor de aplicaciones que envía un conjunto de pruebas denominado Technology Compatibility Kit (TCK) para Java EE se conoce como un servidor de aplicaciones que cumple con Java EE. Existen diferentes versiones de Java EE. Si bien se agregan en forma incremental API y funciones nuevas en cada nueva versión, la compatibilidad con versiones anteriores se mantiene de manera estricta. Java EE incluye soporte para varios perfiles o subconjuntos de API. Por ejemplo, la especificación Java EE 7 define dos perfiles: el perfil completo y el perfil web. El perfil web de Java EE 7 está diseñado para el desarrollo de aplicaciones web y admite un subconjunto de API definido por tecnologías basadas en la Web y relacionadas con Java EE 7. El perfil completo de Java EE 7 contiene todas las API definidas por Java EE 7 (incluidos todos los ítems en el perfil web). Al desarrollar los EJB, las aplicaciones de mensajería y los servicios web (en contraste con las aplicaciones web), debe usar el perfil completo. 8 JB183-EAP7.0-es-2-20180124 Compilación, empaquetado e implementación de aplicaciones Java SE y Java EE Un servidor de aplicaciones que cumple con Java EE 7, como Red Hat JBoss Enterprise Application Platform (EAP), implementa ambos perfiles y proporciona una serie de API que generalmente se utilizan en aplicaciones empresariales, incluidas las siguientes: • Batch API (API de lote) • API de Java para el procesamiento de JSON (JSON-P) • Utilidades de simultaneidad • API para WebSocket • Servicio de mensajería Java (JMS) • API de persistencia Java (JPA) • Arquitectura de conector Java (JCA) • API de Java para servicios web RESTful (JAX-RS) • API de Java para servicios web XML (JAX-WS) • API de Servlet • Caras de servidor Java (JSF) • Páginas de servidor Java (JSP) • Contextos e Inyección de dependencia (CDI) • API de transacción Java (JTA) • Enterprise Java Beans (EJB) • API de validación de beans Compilación, empaquetado e implementación de aplicaciones Java SE y Java EE En el caso de aplicaciones Java SE independientes relativamente simples, el código se puede compilar, empaquetar y ejecutar en la línea de comando usando el compilador y las herramientas de tiempo de ejecución (java, javac, jar, jdb, etc.) que forman parte del JDK. Varios Entornos de desarrollo integrado (IDE) maduros, como Red Hat JBoss Developer Studio (JBDS) o Eclipse, se utilizan para simplificar el proceso de compilación y empaquetado. La forma preferida de enviar aplicaciones Java independientes con un enfoque neutro con respecto a la plataforma es empaquetar la aplicación como un archivo Java Archive (JAR). Los archivos JAR pueden hacerse ejecutables de manera opcional al agregar entradas de manifiestos (un archivo de texto simple empaquetado junto con las clases de Java dentro del archivo JAR) al archivo JAR para indicar la principal clase ejecutable. Las aplicaciones Java EE constan de varios componentes que dependen de un gran número de archivos JAR que se requieren durante el tiempo de ejecución. El proceso de implementación para aplicaciones Java EE es diferente. Las aplicaciones Java JB183-EAP7.0-es-2-20180124 9 Capítulo 1. Transición a aplicaciones con varios niveles EE se implementan en un servidor de aplicaciones compatible con Java EE y estas implementaciones pueden ser de diferentes tipos: • JAR files (Archivos JAR): módulos individuales de una aplicación y Enterprise Java Beans (EJB) se pueden implementar como archivos JAR separados. Las librerías y los marcos (frameworks) de terceros también se empaquetan como archivos JAR. Si su aplicación depende de estas librerías, los archivos JAR de la librería se deben implementar en el servidor de aplicaciones. Los archivos JAR tienen una ampliación .jar. • Web Archive (WAR) files (Archivos WAR): si su aplicación Java EE tiene un front-end basado en la Web o está proporcionando extremos de servicio RESTful, el código y los activos relacionados con el front-end web y los servicios se pueden empaquetar como un archivo WAR. Un archivo WAR tiene una ampliación .war y es básicamente un archivo comprimido que contiene código, HTML estático, imágenes, CSS y activos JS, así como archivos de descriptores de implementación XML, junto con archivos JAR dependientes empaquetados en su interior. • Enterprise Archive (EAR) files (Archivos EAR): un archivo EAR tiene una ampliación de .ear y, básicamente, se trata de un archivo comprimido con uno o más archivos WAR o JAR y algunos descriptores de implementación XML en su interior. Es útil en escenarios en los que la aplicación cuenta con varios archivos WAR o reutiliza algunos archivos JAR comunes en módulos. En dichos casos, es más sencillo implementar y gestionar la aplicación como una única unidad implementable. También es una práctica recomendada usar una herramienta de compilación, como Apache Maven, para simplificar la compilación, el empaquetado, las pruebas, la ejecución y la implementación de aplicaciones Java SE y Java EE. Maven cuenta con una arquitectura de complementos (plug-ins) para ampliar su funcionalidad core. Existen complementos (plug-ins) de Maven para la compilación, el empaquetado y la implementación de aplicaciones Java EE. Se admiten todos los tipos de implementación. Maven también puede implementar aplicaciones y anular su implementación hacia y desde JBoss EAP sin reiniciar el servidor de aplicaciones. Los Entornos de desarrollo integrado (IDE), como JBoss Developer Studio (JBDS), también cuentan con soporte nativo para Maven integrado de manera predeterminada. Todas las tareas de Maven se pueden ejecutar desde dentro del propio JBDS sin usar la línea de comando. Para ejecutar una aplicación independiente que usa solamente una API de Java SE, como, por ejemplo, la aplicación To Do List basada en la línea de comando que está empaquetada como un archivo JAR, puede usar el comando java -jar: [student@workstation todojse]$ java -jar todojse-1.0.jar 10 JB183-EAP7.0-es-2-20180124 Compilación, empaquetado e implementación de aplicaciones Java SE y Java EE Figura 1.1: La aplicación To Do List basada en Java SE independiente En contraste, la versión basada en la Web se implementa en el servidor de aplicaciones que cumple con Java EE. La aplicación To Do List de ejemplo está empaquetada como un archivo WAR que se implementa en un servidor de aplicaciones como EAP. Si ya se implementó una versión anterior del archivo WAR, se anula la implementación de la versión antigua y se implementa la versión nueva sin reiniciar el servidor de aplicaciones. Dicho proceso se denomina implementación en caliente y se utiliza ampliamente durante el desarrollo y las pruebas, así como en implementaciones de producción. Figura 1.2: Lista de tareas de la aplicación To Do List basada en la Web Java EE 7 JB183-EAP7.0-es-2-20180124 11 Capítulo 1. Transición a aplicaciones con varios niveles Figura 1.3: Agregue una tarea a la aplicación To Do List Referencias JSR de la Especificación Java EE 7 https://www.jcp.org/en/jsr/detail?id=342 Notas sobre la versión Red Hat JBoss EAP 7 https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/version-7.0 12 JB183-EAP7.0-es-2-20180124 Cuestionario: Comparación de Java EE y Java SE Cuestionario: Comparación de Java EE y Java SE Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuál de las siguientes API es parte de la especificación Java EE 7? a. b. c. d. e. 2. ¿Cuáles dos de los siguientes enunciados acerca de la especificación Java EE 7 son correctos? (Elija dos opciones). a. b. c. d. 3. Java EE 7 es una especificación, no una implementación que se puede utilizar directamente. Debe haber implementaciones de Java EE 7 por separado para diferentes sistemas operativos. El estándar Java EE 7 define dos perfiles: web y completo. El estándar Java EE 7 define cuatro perfiles: pequeño, medio, web y completo. ¿Cuáles dos de los siguientes enunciados acerca de Java SE son correctos? (Elija dos opciones). a. b. c. d. 4. Conectividad de base de datos Java (JDBC) Caras de servidor Java (JSF) Ampliaciones de criptografía Java (JCE) Swing Java Seguridad Java Java SE se utiliza para desarrollar aplicaciones independientes, como herramientas, utilidades y aplicaciones GUI, que se pueden ejecutar desde la línea de comando. Debe haber implementaciones de Java SE por separado para diferentes sistemas operativos. Java SE se puede utilizar para escribir una aplicación a fin de almacenar datos en la base de datos. Java SE no se puede utilizar para escribir aplicaciones multiproceso. ¿Cuáles dos de los siguientes enunciados acerca de los tipos de implementación en Java EE 7 son correctos? (Elija dos opciones). a. b. c. d. Las aplicaciones web generalmente vienen empaquetadas como archivos JAR para su implementación en un servidor de aplicaciones. Las aplicaciones web generalmente vienen empaquetadas como archivos WAR para su implementación en un servidor de aplicaciones. Un archivo WAR puede contener archivos EAR y JAR, así como descriptores de implementación. Un archivo EAR puede contener archivos WAR, archivos JAR y descriptores de implementación. JB183-EAP7.0-es-2-20180124 13 Capítulo 1. Transición a aplicaciones con varios niveles e. 5. ¿Cuáles dos de los siguientes enunciados acerca de Apache Maven son correctos? (Elija dos opciones). a. b. c. d. e. 14 Un archivo EAR no se puede implementar directamente dentro de un servidor de aplicaciones. Debe implementar los archivos WAR y JAR en su interior de manera separada. Maven se puede usar para compilar, empaquetar y probar ambas aplicaciones Java EE y Java SE. Maven se puede usar únicamente para compilar, empaquetar y probar aplicaciones Java EE. Maven no se puede utilizar para compilar, empaquetar y probar aplicaciones Java SE. Maven no puede implementar aplicaciones ni anular su implementación hacia o desde JBoss EAP. Debe reiniciar manualmente el servidor de aplicaciones después de implementar o dejar de implementar. Maven puede implementar aplicaciones y anular su implementación automáticamente desde JBoss EAP. No hay necesidad de reiniciar el servidor de aplicaciones después de implementar o dejar de implementar. No hay soporte de IDE para tareas de Maven. Todas las tareas de Maven se deben invocar desde la línea de comando. JB183-EAP7.0-es-2-20180124 Solución Solución Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuál de las siguientes API es parte de la especificación Java EE 7? a. b. c. d. e. 2. ¿Cuáles dos de los siguientes enunciados acerca de la especificación Java EE 7 son correctos? (Elija dos opciones). a. b. c. d. 3. Java EE 7 es una especificación, no una implementación que se puede utilizar directamente. Debe haber implementaciones de Java EE 7 por separado para diferentes sistemas operativos. El estándar Java EE 7 define dos perfiles: web y completo. El estándar Java EE 7 define cuatro perfiles: pequeño, medio, web y completo. ¿Cuáles dos de los siguientes enunciados acerca de Java SE son correctos? (Elija dos opciones). a. b. c. d. 4. Conectividad de base de datos Java (JDBC) Caras de servidor Java (JSF) Ampliaciones de criptografía Java (JCE) Swing Java Seguridad Java Java SE se utiliza para desarrollar aplicaciones independientes, como herramientas, utilidades y aplicaciones GUI, que se pueden ejecutar desde la línea de comando. Debe haber implementaciones de Java SE por separado para diferentes sistemas operativos. Java SE se puede utilizar para escribir una aplicación a fin de almacenar datos en la base de datos. Java SE no se puede utilizar para escribir aplicaciones multiproceso. ¿Cuáles dos de los siguientes enunciados acerca de los tipos de implementación en Java EE 7 son correctos? (Elija dos opciones). a. b. c. d. e. Las aplicaciones web generalmente vienen empaquetadas como archivos JAR para su implementación en un servidor de aplicaciones. Las aplicaciones web generalmente vienen empaquetadas como archivos WAR para su implementación en un servidor de aplicaciones. Un archivo WAR puede contener archivos EAR y JAR, así como descriptores de implementación. Un archivo EAR puede contener archivos WAR, archivos JAR y descriptores de implementación. Un archivo EAR no se puede implementar directamente dentro de un servidor de aplicaciones. Debe implementar los archivos WAR y JAR en su interior de manera separada. JB183-EAP7.0-es-2-20180124 15 Capítulo 1. Transición a aplicaciones con varios niveles 5. ¿Cuáles dos de los siguientes enunciados acerca de Apache Maven son correctos? (Elija dos opciones). a. b. c. d. e. 16 Maven se puede usar para compilar, empaquetar y probar ambas aplicaciones Java EE y Java SE. Maven se puede usar únicamente para compilar, empaquetar y probar aplicaciones Java EE. Maven no se puede utilizar para compilar, empaquetar y probar aplicaciones Java SE. Maven no puede implementar aplicaciones ni anular su implementación hacia o desde JBoss EAP. Debe reiniciar manualmente el servidor de aplicaciones después de implementar o dejar de implementar. Maven puede implementar aplicaciones y anular su implementación automáticamente desde JBoss EAP. No hay necesidad de reiniciar el servidor de aplicaciones después de implementar o dejar de implementar. No hay soporte de IDE para tareas de Maven. Todas las tareas de Maven se deben invocar desde la línea de comando. JB183-EAP7.0-es-2-20180124 Descripción del Proceso de la comunidad Java Descripción del Proceso de la comunidad Java Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de realizar lo siguiente: • Describir el propósito del Proceso de la comunidad Java (JCP). • Describir el proceso mediante el cual el JCP define, publica y mantiene las Solicitudes de especificación Java (JSR). • Explorar los números de especificación y versión de API que constituyen la especificación de Java EE 7. El Proceso de la comunidad Java (JCP) El Proceso de la comunidad Java (JCP) es un proceso abierto y de participación para desarrollar, mantener y revisar las especificaciones de tecnología Java. El JCP fomenta la evolución de la plataforma Java en colaboración con la comunidad internacional de desarrolladores de Java. Todo individuo o toda organización pueden unirse al JCP y participar en el proceso de estandarización. El JCP gestiona la especificación de un gran número de API a través de las Solicitudes de especificación Java (JSR). Los miembros pueden enviar propuestas de JSR para cualquier API de Java. La propuesta será revisada por el Comité Ejecutivo (EC) de JCP, que está compuesto de varios líderes sénior de la comunidad Java elegidos por los miembros de JCP. Una vez aprobada, la propuesta es gestionada por un equipo de miembros de JCP denominado Grupo de expertos (Expert Group, EG). El grupo de expertos es responsable de definir, finalizar y mantener la especificación de la API bajo el liderazgo de un Director de especificaciones (Specification Lead, SL). Cuando la JSR está lista para su publicación, es aprobada por el comité ejecutivo y se vuelve un estándar de JCP. Cada JSR puede evolucionar y se puede perfeccionar en forma incremental según las necesidades de los desarrolladores de Java y las tendencias de tecnologías actuales. Todas las API individuales que constituyen la especificación Java EE 7, gestionada en una JSR por separado, han sido desarrolladas bajo el proceso JCP y tienen JSR individuales gestionadas por grupos de expertos separados, que se especializan en un ámbito particular de la tecnología. El JCP, en colaboración con los grupos de expertos, también es responsable de publicar un Technology Compatibility Kit (TCK), un conjunto de prueba que verifica si una implementación cumple con la especificación. Por ejemplo, se dice que un servidor de aplicaciones es "compatible con Java EE 7" solo si aprueba el kit TCK de Java EE 7 por completo, sin errores ni fallas. La especificación Java EE 7 (JSR 342) La especificación Java EE 7 se estandarizó y se publicó como JSR 342. La propia especificación Java EE 7 combina una serie de API, cada una de las cuales está estandarizada con su propio número de JSR único. Diferentes versiones de las API individuales tienen sus JB183-EAP7.0-es-2-20180124 17 Capítulo 1. Transición a aplicaciones con varios niveles propios números de JSR. Por ejemplo, la especificación EJB 3.1 está estandarizada como JSR 318, mientras que EJB 3.0 se estandarizó como JSR 220, y la especificación EJB 2.1, como JSR 153. La especificación Java EE 7 (JSR 342) está disponible en http://download.oracle.com/otndocs/jcp/ java_ee-7-fr-eval-spec/index.html Para conocer el proceso detallado seguido por el JCP al crear y lanzar JSR, y obtener una lista completa de todas las JSR y todos los detalles de cada una, consulte los enlaces provistos en las Referencias al final de esta sección. En la siguiente tabla se enumeran las API en la Web y los perfiles completos en Java EE 7, así como su versión y sus números de JSR correspondientes: Perfil Especificación Versión N.º de JSR Web API de Java para WebSocket 1.0 356 API de Java para el procesamiento de JSON (JSON-P) 1.0 353 API de Java Servlet 3.1 340 Caras de servidor Java (JSF) 2.2 344 Lenguaje de expresión (EL) 3.0 341 Páginas de servidor Java (JSP) 2.3 245 Librería de etiquetas estándar para páginas de JavaServer (JSTL) 1.2 52 Contextos e Inyección de dependencia (CDI) 1.1 346 Inyección de dependencias para Java 1.0 330 Validación de beans 1.1 349 Beans administrados 1.0 316 Enterprise JavaBeans (EJB) (EJB) 3.2 345 Interceptores 1.2 318 API de persistencia Java (JPA) 2.1 338 Anotaciones comunes para la plataforma Java 1.2 250 API de transacción Java (JTA) 1.2 907 API de Java para servicios web RESTful (JAXRS) 2.0 339 Aplicaciones por lotes para la plataforma Java 1.0 352 Utilidades de simultaneidad para Java EE 1.0 236 Arquitectura de conector Java EE (JCA) 1.7 322 API de servicio de mensajería Java (JMS) 2.0 343 API de JavaMail 1.5 919 API de Java para servicios web basados en XML (JAX-WS) 2.2 224 Completo 18 JB183-EAP7.0-es-2-20180124 La especificación Java EE 7 (JSR 342) Perfil Especificación Versión N.º de JSR Arquitectura Java para referencias XML (JAXB) 2.2 222 API de Java para registros XML (JAXR) 1.0 93 Interfaz de proveedor de servicio de autenticación de Java para contenedores (JASPIC) 1.1 196 nota El perfil completo de Java EE 7 incluye todas las API del perfil web. Referencias El Proceso de la comunidad Java (JCP) https://jcp.org/en/home/index Solicitudes de especificación Java (JSR) https://jcp.org/en/jsr/overview El proceso JCP https://jcp.org/en/procedures/overview JB183-EAP7.0-es-2-20180124 19 Capítulo 1. Transición a aplicaciones con varios niveles Cuestionario: Descripción del Proceso de la comunidad Java (JCP) Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuál de los siguientes enunciados acerca de JCP es verdadero? a. b. c. d. e. 2. ¿Cuáles dos de los siguientes enunciados acerca del proceso JCP son correctos? (Elija dos opciones). a. b. c. d. e. 3. c. d. e. Arquitectura de conector Java (JCA) Interfaz de proveedor de servicio de autenticación de Java para contenedores (JASPIC) API de servicio de mensajería Java (JMS) API de Java para servicios web RESTful (JAX-RS) API de transacción Java (JTA) ¿Cuáles dos de los siguientes enunciados acerca del perfil completo de Java EE 7 son correctos? (Elija dos opciones). a. b. c. 20 Cualquier miembro, empresa u organización individual puede proponer una JSR. El kit Technology Compatibility Kit (TCK) de Java EE 7 es solo un conjunto de prueba para verificar la implementación de la especificación Java EE 7. No es obligatorio que se superen todas las pruebas para que una implementación sea declarada como compatible y certificada. Una implementación se puede definir como certificada y compatible si aprueba el 50 % de las pruebas en el TCK. El Comité Ejecutivo de JCP gestiona y supervisa de manera exclusiva el desarrollo y la evolución de cada JSR. Los Grupos de expertos (EG) gestionan el desarrollo y la evolución de las JSR bajo el liderazgo de un Director de especificaciones (SL). ¿Cuáles dos de las siguientes API forman parte del perfil web Java EE 7? (Elija dos opciones). a. b. 4. Solo las organizaciones con ingresos de USD 1 millón o más pueden unirse al JCP. Solo las empresas u organizaciones registradas tienen permitido ingresar al JCP. La membresía al JCP se da por invitación únicamente. No se puede unir al JCP como individuo u organización sin invitación. La membresía al JCP está abierta a organizaciones, empresas e individuos. Solo el Comité Ejecutivo (EC) del JCP puede proponer una nueva Solicitud de especificación Java (JSR). La API de persistencia Java (JPA) no forma parte del perfil completo de Java EE 7. La API de Java para servicios web basados en XML (JAX-WS) no forma parte del perfil completo de Java EE 7. La API de servicio de mensajería Java (JMS) forma parte del perfil completo de Java EE 7. JB183-EAP7.0-es-2-20180124 d. e. El perfil completo contiene todas las API en el perfil web. El perfil completo contiene solo algunas API del perfil web. JB183-EAP7.0-es-2-20180124 21 Capítulo 1. Transición a aplicaciones con varios niveles Solución Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuál de los siguientes enunciados acerca de JCP es verdadero? a. b. c. d. e. 2. ¿Cuáles dos de los siguientes enunciados acerca del proceso JCP son correctos? (Elija dos opciones). a. b. c. d. e. 3. c. d. e. Arquitectura de conector Java (JCA) Interfaz de proveedor de servicio de autenticación de Java para contenedores (JASPIC) API de servicio de mensajería Java (JMS) API de Java para servicios web RESTful (JAX-RS) API de transacción Java (JTA) ¿Cuáles dos de los siguientes enunciados acerca del perfil completo de Java EE 7 son correctos? (Elija dos opciones). a. b. c. d. 22 Cualquier miembro, empresa u organización individual puede proponer una JSR. El kit Technology Compatibility Kit (TCK) de Java EE 7 es solo un conjunto de prueba para verificar la implementación de la especificación Java EE 7. No es obligatorio que se superen todas las pruebas para que una implementación sea declarada como compatible y certificada. Una implementación se puede definir como certificada y compatible si aprueba el 50 % de las pruebas en el TCK. El Comité Ejecutivo de JCP gestiona y supervisa de manera exclusiva el desarrollo y la evolución de cada JSR. Los Grupos de expertos (EG) gestionan el desarrollo y la evolución de las JSR bajo el liderazgo de un Director de especificaciones (SL). ¿Cuáles dos de las siguientes API forman parte del perfil web Java EE 7? (Elija dos opciones). a. b. 4. Solo las organizaciones con ingresos de USD 1 millón o más pueden unirse al JCP. Solo las empresas u organizaciones registradas tienen permitido ingresar al JCP. La membresía al JCP se da por invitación únicamente. No se puede unir al JCP como individuo u organización sin invitación. La membresía al JCP está abierta a organizaciones, empresas e individuos. Solo el Comité Ejecutivo (EC) del JCP puede proponer una nueva Solicitud de especificación Java (JSR). La API de persistencia Java (JPA) no forma parte del perfil completo de Java EE 7. La API de Java para servicios web basados en XML (JAX-WS) no forma parte del perfil completo de Java EE 7. La API de servicio de mensajería Java (JMS) forma parte del perfil completo de Java EE 7. El perfil completo contiene todas las API en el perfil web. JB183-EAP7.0-es-2-20180124 Solución e. El perfil completo contiene solo algunas API del perfil web. JB183-EAP7.0-es-2-20180124 23 Capítulo 1. Transición a aplicaciones con varios niveles Descripción de la arquitectura de aplicaciones con varios niveles Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de explicar aplicaciones y arquitecturas con varios niveles. Arquitectura de aplicaciones con varios niveles Las aplicaciones Java EE fueron diseñadas teniendo en mente una arquitectura con varios niveles. La aplicación está dividida en componentes y cada uno cumple un propósito específico. Cada componente está ordenado de manera lógica en un nivel. Algunos de los niveles se ejecutan en máquinas y servidores físicos separados. La lógica de negocio de la aplicación se puede ejecutar en servidores de aplicaciones alojados en un centro de datos, mientras que los datos reales para la base de datos se pueden almacenar en un servidor separado. La ventaja de usar arquitecturas con niveles es que, a medida que la aplicación escala para gestionar más y más usuarios finales, cada uno de los niveles se puede escalar de manera independiente para gestionar la mayor carga de trabajo al agregar más servidores (un proceso conocido como "escalamiento" [scaling out]). También existe el beneficio adicional de que los componentes entre niveles se pueden actualizar en forma independiente sin afectar otros componentes. En una arquitectura clásica de aplicaciones Java EE basada en la Web, existen cuatro niveles: • Client Tier (Nivel de cliente): suele ser un explorador para reproducir la interfaz de usuario en las máquinas del usuario final o un applet incorporado en una página web (cada vez más raro). • Web Tier (Nivel web): los componentes del nivel web se ejecutan dentro de un servidor de aplicaciones y generan HTML u otro tipo de marcado que los componentes pueden reproducir o consumir en el nivel de cliente. Este nivel también puede asistir a clientes no interactivos, como otros sistemas empresariales (tanto internos como externos), a través de protocolos como Protocolo simple de acceso a objetos (SOAP) o servicios web Transferencia de estado representacional (REST). • Business Logic Tier (Nivel de lógica de negocio): los componentes del nivel de lógica de negocio contienen la lógica de negocio core para la aplicación. Por lo general, estos son una combinación de Enterprise Java Beans (EJB), Objetos comunes antiguos de Java (POJO), Beans de entidad, Beans controlados por mensajes y Objetos de acceso de datos (DAO), que se comunican con sistemas de almacenamiento persistentes, como RDBMS, LDAP y otros. • Enterprise Information Systems (EIS) Tier (Nivel de Sistemas de información empresariales [EIS]): muchas aplicaciones empresariales almacenan y manipulan datos persistentes que son consumidos por varios sistemas y aplicaciones dentro de una organización. Entre los ejemplos, se incluyen sistemas de administración de bases de datos relacionales (RDBMS), servicios de directorio del Protocolo ligero de acceso a directorios (LDAP), bases 24 JB183-EAP7.0-es-2-20180124 Tipos de arquitecturas de aplicaciones con varios niveles de datos NoSQL, bases de datos en memoria, mainframes u otros sistemas de back-end que almacenan y gestionan los datos de una organización de manera segura. Tipos de arquitecturas de aplicaciones con varios niveles La especificación Java EE fue diseñada para admitir varios tipos diferentes de aplicaciones de múltiples niveles. A continuación, se mencionan brevemente algunos de los más comunes: Arquitectura centrada en la Web Este tipo de arquitectura es para aplicaciones simples con un front-end basado en explorador y un back-end simple desarrollados por Servlets, Páginas de servidor Java (JSP) o Caras de servidor Java (JSF). No se utilizan características como transacciones, mensajería asíncrona y acceso a la base de datos. Figura 1.4: Arquitectura simple centrada en la Web Arquitectura basada en componentes que combina funciones web y lógica de negocio En esta arquitectura, un explorador con nivel de cliente se comunica con un nivel web que consta de Servlets, JSP o páginas JSF, que son responsables de visualizar la interfaz de usuario, controlar el flujo de la página y la seguridad. La lógica de negocio core se aloja en un nivel separado de lógica de negocio, que cuenta con componentes de Java EE, como EJB, Beans de entidad (JPA) y Beans controlados por mensajes (MDB). Los componentes del nivel de lógica de negocio se integran con sistemas de información empresarial, como bases de datos relacionales y aplicaciones de back-office que exponen una API para gestionar datos persistentes y proporcionan capacidades transaccionales para la aplicación. JB183-EAP7.0-es-2-20180124 25 Capítulo 1. Transición a aplicaciones con varios niveles Figura 1.5: Arquitectura basada en componentes que combina funciones web y lógica de negocio Arquitectura de negocio a negocio (Business-to-Business, B2B) En este tipo de arquitectura, el front-end generalmente no es una interfaz gráfica de usuario (GUI) interactiva a la que acceden usuarios finales, sino un sistema interno o externo que se integra con la aplicación e intercambia datos mediante un protocolo estándar mutuamente entendido, como Invocación de método remoto (RMI), HTTP, Protocolo simple de acceso a objetos (SOAP) o Transferencia de estado representacional (REST). 26 JB183-EAP7.0-es-2-20180124 Tipos de arquitecturas de aplicaciones con varios niveles Figura 1.6: Arquitectura de negocio a negocio Arquitectura de aplicaciones de los servicios web Las arquitecturas de aplicaciones modernas a menudo están diseñadas para basarse en servicios web. En esta arquitectura, la aplicación proporciona una API a la cual se accede a través de un protocolo basado en HTTP, como SOAP o REST, vía un conjunto de servicios (extremos) que corresponden a la función de negocio de la aplicación. Estos servicios son consumidos por aplicaciones no interactivas (pueden ser internas o de terceros) o un front-end HTML/JavaScript interactivo, mediante marcos (frameworks) como AngularJS, Backbone.js, React y muchos más. JB183-EAP7.0-es-2-20180124 27 Capítulo 1. Transición a aplicaciones con varios niveles Figura 1.7: Arquitectura simple de aplicaciones de los servicios web 28 JB183-EAP7.0-es-2-20180124 Cuestionario: Arquitectura de aplicaciones con varios niveles Cuestionario: Arquitectura de aplicaciones con varios niveles Elija las respuestas correctas para las siguientes preguntas: 1. Se le ha solicitado que diseñe un componente que calcule tasas de descuento para diferentes productos en una aplicación de compras en línea. ¿A qué nivel lógico pertenece este componente? a. b. c. d. e. 2. ¿Cuáles dos de las siguientes aplicaciones combinan con una arquitectura simple centrada en la Web? (Elija dos opciones). a. b. c. d. e. 3. Nivel de cliente Nivel web Nivel de lógica de negocio Nivel de datos o EIS Ninguno de los anteriores Una aplicación de servlet basada en explorador, que imprime la hora actual en tres zonas horarias diferentes de los EE. UU.: Pacífico (PST), Central (CST) y Este (EST). Una aplicación que rastrea la ubicación de una flota de automóviles mediante GPS. Una aplicación que lee datos de un mainframe grande y, luego, los almacena en una base de datos relacional. La aplicación también permite que un sistema externo de terceros acceda a los datos de la base de datos mediante servicios web SOAP. Una aplicación de comprobación de estado que se implementa en un servidor de aplicaciones, que muestra un estado "OK" (al acceder desde un explorador) si el servidor funciona normalmente. Una aplicación que proporciona información meteorológica de ciudades alrededor del mundo. La aplicación acepta un nombre de ciudad como entrada (a través de un extremo REST) y, luego, proporciona información meteorológica actual y un pronóstico de 5 días en formato XML. ¿Cuáles dos de los siguientes enunciados acerca de una arquitectura negocio a negocio (Business-to-Business, B2B) son correctos? (Elija dos opciones). a. b. c. d. e. Las aplicaciones B2B deben estar siempre basadas en la Web y deben contar con un front-end interactivo. Las aplicaciones B2B solo deben admitir un protocolo único por motivos de seguridad. Las aplicaciones B2B se comunican siempre mediante RMI. Las aplicaciones B2B se pueden comunicar a través de RMI, SOAP, REST o un protocolo mutuamente aceptado. Las aplicaciones B2B pueden admitir consumidores y usuarios tanto interactivos como no interactivos. JB183-EAP7.0-es-2-20180124 29 Capítulo 1. Transición a aplicaciones con varios niveles 4. ¿Cuáles dos de los siguientes enunciados acerca de la arquitectura basada en componentes que combina funciones web y lógica de negocio son correctos? (Elija dos opciones). a. b. c. d. 30 Las transacciones se gestionan en un nivel de lógica de negocio (en EJB). Las transacciones deben gestionarse siempre en la capa web. No se puede utilizar la mensajería asíncrona que utiliza Beans controlados por mensajes (MDB). Se puede utilizar la mensajería asíncrona que utiliza Beans controlados por mensajes (MDB). JB183-EAP7.0-es-2-20180124 Solución Solución Elija las respuestas correctas para las siguientes preguntas: 1. Se le ha solicitado que diseñe un componente que calcule tasas de descuento para diferentes productos en una aplicación de compras en línea. ¿A qué nivel lógico pertenece este componente? a. b. c. d. e. 2. ¿Cuáles dos de las siguientes aplicaciones combinan con una arquitectura simple centrada en la Web? (Elija dos opciones). a. b. c. d. e. 3. Una aplicación de servlet basada en explorador, que imprime la hora actual en tres zonas horarias diferentes de los EE. UU.: Pacífico (PST), Central (CST) y Este (EST). Una aplicación que rastrea la ubicación de una flota de automóviles mediante GPS. Una aplicación que lee datos de un mainframe grande y, luego, los almacena en una base de datos relacional. La aplicación también permite que un sistema externo de terceros acceda a los datos de la base de datos mediante servicios web SOAP. Una aplicación de comprobación de estado que se implementa en un servidor de aplicaciones, que muestra un estado "OK" (al acceder desde un explorador) si el servidor funciona normalmente. Una aplicación que proporciona información meteorológica de ciudades alrededor del mundo. La aplicación acepta un nombre de ciudad como entrada (a través de un extremo REST) y, luego, proporciona información meteorológica actual y un pronóstico de 5 días en formato XML. ¿Cuáles dos de los siguientes enunciados acerca de una arquitectura negocio a negocio (Business-to-Business, B2B) son correctos? (Elija dos opciones). a. b. c. d. e. 4. Nivel de cliente Nivel web Nivel de lógica de negocio Nivel de datos o EIS Ninguno de los anteriores Las aplicaciones B2B deben estar siempre basadas en la Web y deben contar con un front-end interactivo. Las aplicaciones B2B solo deben admitir un protocolo único por motivos de seguridad. Las aplicaciones B2B se comunican siempre mediante RMI. Las aplicaciones B2B se pueden comunicar a través de RMI, SOAP, REST o un protocolo mutuamente aceptado. Las aplicaciones B2B pueden admitir consumidores y usuarios tanto interactivos como no interactivos. ¿Cuáles dos de los siguientes enunciados acerca de la arquitectura basada en componentes que combina funciones web y lógica de negocio son correctos? (Elija dos opciones). a. Las transacciones se gestionan en un nivel de lógica de negocio (en EJB). JB183-EAP7.0-es-2-20180124 31 Capítulo 1. Transición a aplicaciones con varios niveles b. c. d. 32 Las transacciones deben gestionarse siempre en la capa web. No se puede utilizar la mensajería asíncrona que utiliza Beans controlados por mensajes (MDB). Se puede utilizar la mensajería asíncrona que utiliza Beans controlados por mensajes (MDB). JB183-EAP7.0-es-2-20180124 Instalación de las herramientas de desarrollo de Java Instalación de las herramientas de desarrollo de Java Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de realizar lo siguiente: • Describir las funciones y el proceso de instalación del editor JBoss Developer Studio. • Describir cómo usar Maven para gestionar dependencias de aplicaciones. Red Hat JBoss Developer Studio (JBDS) JBoss Developer Studio (JBDS) es un Entorno de desarrollo integrado (IDE) provisto por Red Hat para simplificar el desarrollo de aplicaciones de Java EE. Es un conjunto de complementos (plug-ins) integrados y comprobados que viene con la plataforma Eclipse™. JBDS cuenta con las siguientes funciones incorporadas: • Complementos (plug-ins) para simplificar el desarrollo de aplicaciones mediante el middleware Red Hat JBoss. • Complementos (plug-ins) y asistentes de verificación de la unidad para realizar el Desarrollo Controlado por Pruebas (TDD). • Un depurador visual para depurar aplicaciones locales y remotas de Java. • Sintaxis resaltada y completador de códigos para las API Java EE más comunes, como JPA, JSF, JSP, EL y muchas más. • Integración Maven para simplificar compilaciones de proyectos, empaquetado, pruebas e implementación. • Los adaptadores de unidades y complementos (plug-ins) para trabajar con JBoss EAP. Puede controlar el ciclo de vida (iniciar, detener, reiniciar, implementar, anular la implementación) de EAP sin dejar la IDE. Instalación de JBoss Developer Studio JBDS tiene un instalador de archivos JAR independiente de la plataforma y se ejecuta en Mac OS X, Windows y Linux (GTK), usando widgets de IU nativos. Es necesario un SDK de Java. Para JBDS 10 y versiones posteriores, se requiere Java 1.8 SDK o una versión posterior. Comience el proceso de instalación abriendo un terminal y ejecutando un comando: [student@workstation todojse]$ java -jar devstudio-10.0.0.GA-standalone-installer.jar El instalador proporciona una serie de asistentes que le solicitan la ubicación en la que desea instalar el IDE, el JVM que desea usar, si está instalado algún servidor JBoss EAP y si desea agregarlo a JBDS. Por último, le solicita aceptar el acuerdo de usuario final antes de instalar el IDE en la ubicación que brindó. JB183-EAP7.0-es-2-20180124 33 Capítulo 1. Transición a aplicaciones con varios niveles Si elije la opción de agregar de manera automática atajos al menú de inicio, debe ver estos atajos en el menú de aplicaciones de su sistema operativo. Haga doble clic en el icono JBDS para iniciar el JBDS IDE. nota El instalador de JBDS registra las opciones elegidas durante la instalación gráfica y almacena dicha información en un archivo nombrado InstallConfigRecord.xml en el directorio raíz de la instalación. De esta manera, es posible repetir la instalación en varios sistemas usando el siguiente comando: [student@workstation todojse]$ java -jar devstudio-10.0.0.GA-standaloneinstaller.jar InstallConfigRecord.xml Apache Maven La práctica recomendada actual para desarrollar, probar, compilar, empaquetar e implementar aplicaciones Java SE y Java EE es usar Apache Maven. Maven es una herramienta de administración de proyectos que usa un enfoque declarativo (en un archivo XML denominado pom.xml en la raíz de la carpeta del proyecto) para especificar cómo compilar, empaquetar, ejecutar (para aplicaciones Java SE) e implementar aplicaciones junto con información de dependencias. Maven cuenta con un núcleo (core) pequeño y un gran número de complementos (plug-ins) que amplían la funcionalidad core para proporcionar funciones como: • Ciclos de vida de compilación preestablecidos para productos finales, denominados artefactos, como WAR, EAR y JAR. • Prácticas recomendadas integradas, como ubicaciones de archivos fuente y evaluaciones de pruebas de unidades para cada compilación. • Administración de dependencias con descarga automática de dependencias faltantes. • Conjunto exhaustivo de complementos (plug-ins), que incluye complementos específicos para el desarrollo y la implementación de JBoss. • Generación de informes de proyectos, que incluye documentos de Java, cobertura de pruebas y mucho más. En esta sección, se analizan las características y construcciones operacionales de Maven que se utilizan en el presente curso. Para comenzar, se utiliza el archivo de proyecto Maven, un documento XML que describe el artefacto, sus dependencias, las propiedades del proyecto y los complementos (plug-ins) que se invocarán en cualquiera de los pasos disponibles del ciclo de vida. Este archivo se nombra siempre pom.xml. El siguiente es un ejemplo abreviado de un archivo pom.xml de un proyecto: <?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/ xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 34 JB183-EAP7.0-es-2-20180124 Apache Maven <modelVersion>4.0.0</modelVersion> <groupId>com.redhat.training</groupId> <artifactId>example</artifactId> <version>0.0.1</version> <packaging>war</packaging> <name></name> <dependencies> <dependency> <groupId>org.richfaces.ui</groupId> <artifactId>richfaces-components-ui</artifactId> <version>4.0.0.Final</version> </dependency> ... </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> ... </build> </project> group-id es como un paquete de Java. artifact-id es un nombre de proyecto. version es la versión del proyecto. packaging define la forma en que se empaquetará el proyecto. En este caso, es un tipo war. dependency describe los recursos de los que depende un proyecto. Se requieren estos recursos para compilar y ejecutar el proyecto correctamente. Maven descarga y vincula las dependencias de los repositorios especificados. plugins son los complementos del proyecto. Una de las ventajas de usar Maven consiste en el manejo automático de la compilación de códigos fuente y la inclusión de recursos en el artefacto. Maven crea una estructura de proyecto estándar. Las siguientes convenciones para la denominación de directorios son obligatorias: Estructuras del directorio de Maven Recurso Directorio Resultado Código fuente de Java src/main/java El directorio contiene clases de Java incluidas en WEBINF/classes para un WAR o raíz de un JAR. Archivos de configuración src/main/resources El directorio contiene archivos de configuración incluidos en WEB-INF/ JB183-EAP7.0-es-2-20180124 35 Capítulo 1. Transición a aplicaciones con varios niveles Recurso Directorio Resultado classes para un WAR o raíz de un JAR. Código de prueba de Java src/test/java El directorio contiene el código fuente de prueba. Archivos de configuración de prueba src/test/resources El directorio contiene los recursos de prueba. Cuando les asigna un nombre a las dependencias en el archivo pom.xml, puede otorgarles un alcance. Estos alcances controlan el lugar en el que se utiliza la dependencia dentro del ciclo de vida de compilación y si se incluyen en el artefacto. A continuación, se exponen los alcances más frecuentes: Alcances de las dependencias de Maven Alcance Resultado compile Compile (compilación) es el alcance predeterminado si no se especifica ningún otro alcance y se requiere resolver las declaraciones import. test Test (prueba) se requiere para compilar y ejecutar las pruebas de las unidades. No se incluye en el artefacto. runtime La dependencia runtime (tiempo de ejecución) no se requiere para la compilación. Se usa para cualquier ejecución y se incluye en el artefacto. provided El alcance provided (provisto) es como compile y el contenedor proporciona la dependencia en el tiempo de ejecución. Se usa durante la compilación y la prueba. Maven se integra en JBDS; pero le recomendamos invocarlo desde la línea de comando. A continuación, se exponen algunos comando habituales: • mvn package evalúa y compila el artefacto. • mvn package -Dmaven.test.skip=true compila el artefacto y omite todas las pruebas. • mvn jboss-as:deploy: Para implementar el artefacto en la instancia que se ejecuta en $JBOSS_HOME (supone que el complemento [plug-in] se configuró en pom.xml). • mvn install es similar al paquete, pero instala el artefacto en el repositorio local de Maven para que se utilice como dependencia en otros proyectos. 36 JB183-EAP7.0-es-2-20180124 Apache Maven nota Las IDE como JBoss Developer Studio son conscientes de los proyectos Maven y usted puede ejecutar las tareas Maven directamente de dentro de IDE sin requerir el uso de la línea de comando. A lo largo de este curso, estará desarrollando en forma incremental una web basada en la aplicación To Do List que se implementa en un servidor de aplicaciones JBoss EAP 7 y usa varias API de la especificación Java EE 7. La aplicación almacena los datos en una base de datos MySQL. Debe hacer un amplio uso de Maven y JBDS en este curso para gestionar el empaquetado y la implementación de la aplicación. Para compilar, empaquetar y ejecutar una aplicación independiente que usa solo la API Java SE, como la aplicación To Do List basada en la línea de comando, mediante Maven, debe ejecutar los siguientes comandos: [student@workstation todojse]$ mvn clean package [student@workstation todojse]$ java -jar target/todojse-1.0.jar El comando mvn clean package compila la aplicación como un archivo JAR ejecutable y el comando java -jar la ejecuta. Por otro lado, la aplicación To Do List basada en la Web se compila y se implementa en EAP usando el siguiente comando: [student@workstation todojse]$ mvn clean package wildfly:deploy El comando anterior elimina el antiguo archivo WAR, compila el código y compila un archivo WAR implementado en una instancia en ejecución de EAP. Si ya se implementó una versión anterior de la aplicación, se anula la implementación de la versión antigua y se implementa la versión nueva sin reiniciar el servidor de aplicaciones. Dicho proceso se denomina hot deployment (implementación en caliente) y se utiliza ampliamente durante el desarrollo y las pruebas, así como en implementaciones de producción. Referencias Eclipse https://eclipse.org Apache Maven https://maven.apache.org JB183-EAP7.0-es-2-20180124 37 Capítulo 1. Transición a aplicaciones con varios niveles Ejercicio guiado: Ejecución de la aplicación To Do List Resultados Debe poder importar el proyecto de la línea de comando de la aplicación To Do List en JBoss Developer Studio y ejecutarlo usando Maven. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos del trabajo de laboratorio necesarios para este taller. [student@workstation ~]$ lab todojse setup 1. Importe el proyecto todojse en el JBoss Developer Studio IDE (JBDS). 1.1. Haga doble clic en el icono de JBDS del escritorio de la máquina virtual workstation. 1.2. Seleccione una carpeta de espacio de trabajo. En la ventana Eclipse Launcher, ingrese /home/student/JB183/workspace en el campo Workspace (Espacio de trabajo), marque la casilla de verificación Use this as the default and do not ask again (Usar como valor predeterminado y no volver a preguntar) y, luego, haga clic en Launch (Iniciar). Figura 1.8: Seleccione el espacio de trabajo de JBDS nota Seleccione No para cerrar el cuadro de diálogo Uso de Red Hat JBoss Developer Studio. 38 JB183-EAP7.0-es-2-20180124 1.3. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). En la página Select (Seleccionar), seleccione Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta todojse y haga clic en OK (Aceptar). En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.5. Observe la barra de estado de JBDS (esquina inferior derecha) para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Explore el archivo pom.xml de Maven. 2.1. Expanda el ítem todojse en el panel Project Explorer (Explorador de proyectos) izquierdo y haga doble clic en el archivo pom.xml. La pestaña Overview (Descripción general) se puede ver en la principal ventana del editor, mostrando una vista avanzada del proyecto. En esta pestaña se muestran Group Id, Artifact Id y Version (generalmente abreviados como las coordenadas GAV de un proyecto o módulo). 2.2. Haga clic en la pestaña Dependencies (Dependencias) para ver las dependencias (librerías, marcos [frameworks] y módulos de los que depende este proyecto) del proyecto. En este caso, no tenemos dependencias en ninguna librería externa y solo utilizamos la Librería estándar de Java. 2.3. Haga clic en la pestaña pom.xml para ver el texto completo del archivo pom.xml. Repase brevemente los detalles GAV para este proyecto: <groupId>com.redhat.training</groupId> <artifactId>todojse</artifactId> <version>1.0</version> El formato del empaquetado para este proyecto como jar. Maven garantiza que, cuando se compile el proyecto, generará un archivo JAR con entradas MANIFEST adecuadas, que contienen metadatos acerca del archivo jar. <packaging>jar</packaging> El proyecto es compatible con JDK 1.8. <!-- maven-compiler-plugin --> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> JB183-EAP7.0-es-2-20180124 39 Capítulo 1. Transición a aplicaciones con varios niveles Maven es ampliable mediante una gran cantidad de plug-ins (complementos). Puede controlar diferentes aspectos de la forma en que un proyecto se compila, se empaqueta, se prueba y se implementa declarando complementos (plug-ins) adecuados. En este proyecto, está usando exec-maven-plugin para ejecutar la clase principal en este proyecto de la línea de comando o desde dentro del JBoss Developer Studio. El método principal, que sirve como punto de entrada para la aplicación cuando se ejecuta, se declara como la clase com.redhat.training.TestTodoMap. <artifactId>exec-maven-plugin</artifactId> <version>1.5.0</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <mainClass>com.redhat.training.TestTodoMap</mainClass> </configuration> También utiliza maven-assembly-plugin para compilar un archivo JAR ejecutable independiente de la plataforma, que se puede ejecutar usando un comando java -jar. Aunque este proyecto no usa ninguna dependencia externa, los proyectos con un gran número de archivos JAR dependientes se pueden empaquetar como un único fat jar grande que se puede ejecutar directamente sin agregar en forma explícita todos los archivos JAR dependientes a la CLASSPATH. <artifactId>maven-assembly-plugin</artifactId> <version>2.6</version> <executions> <execution> <id>package-jar-with-dependencies</id> <phase>package</phase> <goals> <goal>single</goal> </goals> 3. Explore el código fuente de la aplicación. 3.1. Navegue a src/main/java/com/redhat/training en el Project Explorer (Explorador de proyectos) y haga doble clic en la clase TestTodoMap.java para ver el código fuente en la principal ventana del editor. 3.2. La aplicación todojse es una aplicación de línea de comando sin interfaces gráficas de usuario. El método main invoca la función executeMenu(), que muestra un menú al usuario con una serie de opciones para gestionar la To Do List. La clase TodoMap.java contiene la lógica de negocio principal de esta aplicación. Esta clase almacena y gestiona un Map (Mapa) de objetos TodoItem. La clase TodoItem es una simple clase Java Bean que encapsula los atributos de una To Do List; a saber, un campo item (ítem), que contiene la descripción de tareas y un campo status (estado) que indica si la tarea está pendiente o completa. 40 JB183-EAP7.0-es-2-20180124 El archivo Status.java declara una enumeración con las dos opciones para el estado de un ítem, ya sea PENDING (PENDIENTE) o COMPLETED (COMPLETADO). 3.3. Revise brevemente el código fuente de los métodos addTodo(), printTodo(), completeTodo(), deleteTodo() y findItemTodo() en la clase TodoMap para comprender de qué manera las tareas se crean, se enumeran y se marcan como completadas, eliminadas o encontradas respectivamente. Estos métodos se invocan desde la sentencia switch, o case, en la clase principal ejecutable, según la opción de menú seleccionada por el usuario. Si el usuario selecciona Q, la aplicación existe. nota La aplicación todojse no es persistente con ningún dato del programa. Los ítems de tareas de To Do List se almacenan en un objeto Map en la memoria y los datos se pierden cuando el programa se cierra. 4. Compile y ejecute el todojse de la línea de comando mediante Maven. 4.1. Antes de ejecutar la aplicación desde dentro de la IDE JBDS, compile y ejecute la aplicación de la línea de comando mediante Maven. Abra una nueva ventana de terminal y diríjase a la carpeta /home/student/JB183/ labs/todojse: [student@workstation ~]$ cd /home/student/JB183/labs/todojse Ahora puede compilar y empaquetar la aplicación como archivo JAR mediante la meta package de Maven. 4.2. Compile la aplicación ejecutando el siguiente comando: [student@workstation todojse]$ mvn clean package [INFO] Scanning for projects... [INFO] [INFO] -----------------------------------------------------------------------[INFO] Building todojse 1.0 [INFO] -----------------------------------------------------------------------[INFO] ... [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ todojse --[INFO] Building jar: /home/student/JB183/labs/todojse/target/todojse-1.0.jar ... [INFO] [INFO] [INFO] [INFO] [INFO] BUILD SUCCESS -----------------------------------------------------------------------Total time: 09:48 mins Finished at: 2017-09-20T08:13:03+05:30 Final Memory: 22M/303M JB183-EAP7.0-es-2-20180124 41 Capítulo 1. Transición a aplicaciones con varios niveles Verifique que pueda ver el mensaje BUILD SUCCESS de Maven y que todojse-1.0.jar se haya compilado y copiado correctamente en la carpeta / home/student/JB183/labs/todojse/target. 4.3. Ejecute la aplicación mediante el complemento (plug-in) exec de Maven: [student@workstation todojse]$ mvn exec:java [INFO] Scanning for projects... [INFO] [INFO] -----------------------------------------------------------------------[INFO] Building todojse 1.0 [INFO] -----------------------------------------------------------------------[INFO] [INFO] --- exec-maven-plugin:1.5.0:java (default-cli) @ todojse --- [N]ew | [C]omplete | [R]ead | [D]elete | [L]ist | [Q]uit: 4.4. Explore la funcionalidad de la aplicación creando, completando, leyendo, enumerando y eliminando algunos ítems de tareas. Ingrese Q para quitar la aplicación. 5. Ejecute el todojse como archivo JAR ejecutable. 5.1. El comando mvn clean package que ejecutó anteriormente utiliza el complemento (plug-in) de ensamblaje Maven para compilar un archivo JAR ejecutable de manera independiente. Ejecute la aplicación mediante el siguiente comando: [student@workstation todojse]$ java -jar target/todojse-1.0.jar 5.2. Verifique que la aplicación se haya iniciado y que aparezca el menú principal. 6. Compile y ejecute todojse desde dentro de JBDS. 6.1. Puede compilar, empaquetar y ejecutar la aplicación desde dentro de JBDS utilizando un complemento (plug-in) Maven incorporado en el IDE. El complemento (plug-in) JBDS Maven viene con un conjunto de Configuraciones de ejecución empaquetadas previamente para limpiar y compilar proyectos. No obstante, debe crear una Configuración de ejecución personalizada y usarla para compilar, empaquetar y ejecutar el proyecto. 6.2. Haga clic con el botón derecho en el proyecto todojse en el Project Explorer (Explorador de proyectos) y haga clic en Run As (Ejecutar como) > Run Configurations (Configuraciones de ejecución) para abrir la ventana Run Configurations (Configuraciones de ejecución). Desplácese hacia abajo en la lista de opciones del panel izquierdo y seleccione la opción Maven Build: 42 JB183-EAP7.0-es-2-20180124 Figura 1.9: Configuración de ejecución de Maven 6.3. En el menú superior izquierdo de la ventana Run Configurations (Configuraciones de ejecución), haga clic en New Launch Configuration (Nueva configuración de inicio) para crear una nueva configuración de lanzamiento: Figura 1.10: Nueva configuración de ejecución 6.4. En la ventana de nueva configuración de ejecución, agregue los siguientes detalles: • Name (Nombre): maven package and exec • Base Directory (Directorio base): Haga clic en Workspace y, luego, seleccione el proyecto todojse y haga clic en OK (Aceptar). • Goals (Metas): clean package exec:java JB183-EAP7.0-es-2-20180124 43 Capítulo 1. Transición a aplicaciones con varios niveles Figura 1.11: Detalles de la configuración de ejecución para Maven Deje todos los demás campos con sus valores predeterminados y haga clic en Apply (Aplicar). 6.5. Haga clic en Run (Ejecutar) en la parte inferior de la ventana Run Configurations (Configuraciones de ejecución). El complemento (plug-in) JBDS ahora deberá iniciar, compilar, empaquetar y ejecutar la aplicación. Observe la pestaña Console (Consola) en la parte inferior para ver el procesos de compilación y verificar que la aplicación se ejecute y aparezca el menú principal. 6.6. Salga de la aplicación ingresando Q en el menú principal de la aplicación To Do List. 6.7. Haga clic con el botón derecho en el proyecto todojse en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). Esto concluye el ejercicio guiado. 44 JB183-EAP7.0-es-2-20180124 Resumen Resumen En este capítulo, aprendió lo siguiente: • Las aplicaciones empresariales se caracterizan por su capacidad de manejar cargas de trabajo de transacciones, integración de varios componentes, seguridad, arquitecturas distribuidas y escalabilidad. • Java Enterprise Edition (Java EE) es una especificación para desarrollar aplicaciones empresariales utilizando Java. Es un estándar independiente de la plataforma, desarrollado bajo los auspicios del Proceso de la comunidad Java (JCP). Un sistema de software que implementa la especificación Java EE se denomina servidor de aplicaciones. • La API Java SE proporciona un conjunto completo de componentes modulares y reutilizables para implementar aplicaciones Java. Java EE se basa en Java SE y proporciona un conjunto de API que se centran en desarrollar aplicaciones empresariales. • Las aplicaciones Java EE están desarrolladas para contar con varios niveles y pueden admitir varias arquitecturas, según el caso de uso. • Red Hat JBoss Developer Studio es un IDE basado en Eclipse™, proporcionado por Red Hat, que cuenta con un conjunto de complementos (plug-ins) y herramientas integrados para simplificar el desarrollo de aplicaciones empresariales Java EE. Admite varios servidores de aplicaciones y puede gestionar el ciclo de vida del servidor de aplicaciones desde dentro de la propia IDE. • Apache Maven es la herramienta elegida para compilar, empaquetar e implementar las aplicaciones Java SE y Java EE. JBDS proporciona soporte incorporado para Maven. Los proyectos se pueden compilar, probar, empaquetar e implementar en servidores de aplicaciones mediante complementos (plug-ins) Maven. JB183-EAP7.0-es-2-20180124 45 46 TRAINING CAPÍTULO 2 EMPAQUETADO E IMPLEMENTACIÓN DE UNA APLICACIÓN DE JAVA EE Descripción general Meta Describir la arquitectura de un servidor de aplicaciones Java EE, empaquetar una aplicación e implementar la aplicación en un servidor EAP. Objetivos • Identificar las características clave de los servidores de aplicaciones y describir el servidor de Java EE. • Enumerar los tipos de recursos JNDI más comunes y sus convenciones de nomenclatura típicas. • Empaquetar una aplicación de Java EE simple e implementarla en JBoss EAP mediante Maven. Secciones • Descripción de un servidor de aplicaciones (y cuestionario) • Identificación de recursos JNDI (y ejercicio guiado) • Empaquetado e implementación de una aplicación de Java EE (y ejercicio guiado) Trabajo de laboratorio JB183-EAP7.0-es-2-20180124 • Empaquetado e implementación de una aplicación de Java EE 47 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE Descripción de un servidor de aplicaciones Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de realizar lo siguiente: • Identificar las características clave de los servidores de aplicaciones y describir la arquitectura del servidor de Java EE. • Identificar diferentes tipos de contenedores y perfiles de servidor. Servidores de aplicaciones Un servidor de aplicaciones es un componente de software que proporciona el entorno y la infraestructura de tiempo de ejecución que se necesitan para alojar y administrar aplicaciones empresariales Java EE. El servidor de aplicaciones cuenta con funciones como simultaneidad, arquitectura de componentes distribuida, portabilidad para varias plataformas, gestión de transacciones, servicios web, asignación relacional de objetos para bases de datos (ORM), mensajería asíncrona y seguridad para aplicaciones empresariales. En una aplicación Java SE, el desarrollador debe implementar estas funciones en forma manual, lo que requiere mucho tiempo y es difícil de implementar correctamente. Figura 2.1: Aplicaciones Java SE en relación a Java EE Red Hat JBoss Enterprise Application Platform (EAP) Red Hat JBoss Enterprise Application Platform 7, JBoss EAP 7 o simplemente, EAP, es un servidor de aplicaciones para alojar y gestionar aplicaciones Java EE. EAP 7 se basa en estándares abiertos, sobre la base del software de código abierto Wildfly, y proporciona las siguientes funciones: • Una infraestructura confiable, ligera, compatible y conforme con los estándares para implementar aplicaciones. • Una estructura modular que permite a los usuarios activar servicios únicamente cuando se los requiere. Esto mejora el rendimiento y la seguridad, y reduce los tiempos de inicio y reinicio. 48 JB183-EAP7.0-es-2-20180124 Red Hat JBoss Enterprise Application Platform (EAP) • Una consola de gestión basada en la Web y la interfaz de la línea de comando (CLI) de gestión para configurar el servidor y proporcionar la capacidad de realizar scripts y automatizar tareas. • Está certificada para ambos perfiles, el Java EE 7 completo y el web. • Una gestión centralizada de varias instancias de servidores y hosts físicos. • También se proporcionan opciones preconfiguradas para funciones, como clústeres de alta disponibilidad, mensajería y almacenamiento en caché distribuido. EAP 7 facilita el desarrollo de aplicaciones empresariales, ya que brinda las API Java EE para acceder a las bases de datos, la autenticación y la mensajería. Una funcionalidad de aplicaciones común también está soportada por las API y los marcos (frameworks) Java EE, provistos por EAP, para desarrollar interfaces de usuario web, exponer servicios web, implementar criptografía y demás funciones. JBoss EAP también facilita la administración brindando métricas de tiempo de ejecución, servicios de clústeres y automatización. EAP cuenta con una arquitectura modular con una infraestructura core simple que controla el ciclo de vida útil del servidor de aplicaciones básico y proporciona capacidades de administración. La infraestructura core es responsable por cargar y descargar módulos. Los módulos implementan la mayor parte de las API Java EE 7. Cada módulo de API de componente Java EE se implementa como un subsistema, que se puede configurar, agregar o eliminar, según se requiera, a través del archivo de configuración de EAP o la interfaz de administración. Por ejemplo, para configurar el acceso a la base de datos en EAP, configure los detalles de conexión de la base de datos en el subsistema datasources. Figura 2.2: Arquitectura de EAP 7 Un concepto importante de la arquitectura EAP es el concepto de un módulo. Un módulo proporciona códigos (clases de Java) para ser utilizados por los servicios EAP o por aplicaciones. Los módulos se cargan en un Cargador de clase aislado, y las clases de otros módulos solo se pueden ver cuando se lo solicita implícitamente. Esto significa que un módulo se JB183-EAP7.0-es-2-20180124 49 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE puede implementar sin ninguna preocupación con respecto a los conflictos posibles con la implementación de otros módulos. Todos los códigos que se ejecutan en EAP, incluido el código provisto por el núcleo (core), se ejecutan dentro de módulos. Esto incluye el código de la aplicación, lo que implica que las aplicaciones se aíslan de sí mismas y de los servicios EAP. Esta arquitectura modular permite un control detallado de la visibilidad de códigos. Una aplicación puede ver un módulo que expone una versión particular de una API, mientras que otra aplicación puede ver un segundo módulo que presenta una versión diferente de la misma API. Un desarrollador de aplicaciones puede controlar esta visibilidad manualmente y puede ser muy útil en algunos escenarios. Sin embargo, para los casos más comunes, EAP 7 decide automáticamente qué módulos presentar a una aplicación, según su uso de las API Java EE. Contenedores Un contenedor en un componente lógico dentro de un servidor de aplicaciones, que proporciona un contexto de tiempo de ejecución para aplicaciones implementadas en el servidor de aplicaciones. Un contenedor actúa como interfaz entre los componentes de la aplicación y los servicios de infraestructura de bajo nivel provistos por el servidor de aplicaciones. Existen diferentes contenedores para diferentes tipos de componentes en una aplicación. Los componentes de aplicaciones se implementan en contenedores y se proporcionan para otras implementaciones. La implementación se basa en los descriptores de implementación (archivos de configuración XML que se empaquetan junto con el código) o anotaciones a nivel del código que indican la manera en que los componentes se deben implementar y configurar. Hay dos tipos principales de contenedores dentro de un servidor de aplicaciones Java EE: • Contenedores web: Implementa y configura componentes web, como Servlets, JSP, JSF y otros activos relacionados con la Web. • Contenedores EJB: Implementa y configura componentes relacionados con EJB, JPA y JMS. Estos tipos de implementaciones se describen en detalle en capítulos posteriores. Los contenedores son responsables de la seguridad, las transacciones, las búsquedas JNDI, la conectividad remota y más. Los contenedores también pueden gestionar servicios de tiempo de ejecución, como ciclos de vida útil de componentes EJB y web, agrupaciones de fuente de datos, persistencia de datos y mensajería JMS. Por ejemplo, la especificación Java EE le permite configurar de forma declarativa la seguridad, para que únicamente los usuarios autorizados puedan invocar la funcionalidad provista por un componente. Esta restricción se configura usando descriptores de implementación XML o anotaciones en código. El contenedor lee estos metadatos durante el tiempo de implementación y los componentes se configuran en consecuencia. Perfiles Java EE 7 Un perfil en el contexto de un servidor de aplicaciones Java EE es un conjunto de API de componentes dirigido a un tipo específico de aplicaciones. Los perfiles son un nuevo concepto que se introduce en Java EE 6. Actualmente, existen dos perfiles definidos en Java EE 7 y el servidor de aplicaciones JBoss EAP admite ambos perfiles por completo: 50 JB183-EAP7.0-es-2-20180124 Perfiles Java EE 7 • Perfil completo: contiene todas las tecnologías Java EE, incluidas todas las API en el perfil web, así como otras. • Perfil web: contiene una pila (stack) completa de API Java EE para desarrollar aplicaciones web dinámicas. Existen más de 30 tecnologías diferentes que comprenden el perfil completo de Java EE. Cada una de estas tecnologías tiene su propia especificación JSR y su propio número de versión. En combinación, brindan una lista impresionante de capacidades que permiten que las aplicaciones Java EE se conecten a bases de datos, publiquen y utilicen servicios web, proporcionen aplicaciones web, realicen transacciones, implementen políticas de seguridad y se conecten con una gran cantidad de recursos externos para tareas, como mensajería, asignación de nombres, envío de correos electrónicos y comunicación con aplicaciones que no pertenecen a Java. El perfil web contiene las tecnologías de Java EE basadas en la Web que comúnmente utilizan desarrolladores web, como Servlets, Páginas de servidor Java, Caras de servidor Java, CDI, JPA, JAX-RS, WebSockets y una versión reducida de Enterprise Java Beans (EJB) conocida como EJB Lite. Muchas de estas tecnologías se describen en detalle a lo largo de este curso. Referencias Para obtener más información, consulte el capítulo Introducción a JBoss EAP de la Guía de desarrollo para Red Hat JBoss EAP 7.0: https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ JB183-EAP7.0-es-2-20180124 51 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE Cuestionario: Descripción de un servidor de aplicaciones Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuál de los siguientes enunciados acerca de las aplicaciones Java SE y Java EE es verdadero? a. b. c. d. e. 2. ¿Cuáles tres de los siguientes enunciados acerca de JBoss EAP son verdaderos? (Elija tres opciones). a. b. c. d. e. f. 3. b. c. d. 52 EAP se basa en el servidor web de código abierto Tomcat. EAP se basa en el servidor de aplicaciones de código abierto Wildfly. Todas las API de Java EE se empaquetan como componentes modulares en EAP 7. Todos los módulos se activan de manera predeterminada y no se puede agregar ni eliminar ningún módulo. Todas las API de Java EE se empaquetan como componentes modulares en EAP 7. Estos módulos se puede activar solo cuando se lo requiere. En EAP 7, la agrupación en clústeres de alta disponibilidad, la mensajería y el almacenamiento en caché distribuido no están disponibles de manera predeterminada; debe usar productos de terceros para habilitar estas funciones. EAP 7 admite tanto el perfil Java EE 7 web como el completo. ¿Cuáles dos de los siguientes enunciados acerca de la arquitectura EAP 7 son verdaderos? (Elija dos opciones). a. 4. Las aplicaciones Java EE se alojan y gestionan mediante un servidor de aplicaciones. Las aplicaciones Java SE no se pueden conectar a una base de datos y realizar transacciones. Las aplicaciones Java SE son siempre uniproceso. Solo las aplicaciones Java EE son multiproceso. Las aplicaciones Java EE no pueden realizar mensajería asíncrona. En las aplicaciones Java EE, el desarrollador debe implementar manualmente el subprocesamiento múltiple y la concurrencia. El concepto de módulos se aplica únicamente a los servicios proporcionados por EAP. Las aplicaciones no se pueden ejecutar como módulos. Tanto las aplicaciones desarrolladas por el usuario como los servicios proporcionados por EAP se pueden ejecutar como módulos. Los módulos tienen un alcance de visibilidad global; es decir, todos los módulos pueden acceder a clases de otros módulos en EAP en forma implícita. EAP tiene control exclusivo de la visibilidad. Las clases de otros módulos se deben solicitar de manera explicita. ¿Cuál de los siguientes enunciados acerca de los contenedores en un servidor de aplicaciones es verdadero? JB183-EAP7.0-es-2-20180124 a. b. c. d. e. 5. ¿Cuáles dos de los siguientes enunciados acerca de los perfiles de Java EE son verdaderos? (Elija dos opciones). a. b. c. d. e. 6. Solo debe haber un contenedor en un servidor de aplicaciones. No se permiten múltiples contenedores dentro de un solo servidor de aplicaciones. En las implementaciones, los contenedores solo pueden leer descriptores de implementación XML. Las anotaciones de nivel de código no son compatibles. Los componentes de aplicaciones pueden configurar la seguridad de manera declarativa en los descriptores de implementación XML o las anotaciones a nivel de código. Un contenedor web puede implementar componentes EJB, JMS y JPA. El contenedor EJB se debe ejecutar en forma separada, fuera del servidor de aplicaciones. El servidor de aplicaciones solo admite la ejecución del contenedor web dentro de este. Un perfil es una recopilación de API enfocada en tipos de aplicación específicos. El concepto de un perfil se introdujo en Java EE 7. La especificación Java EE 7 define tres perfiles: web, ejb y completo. La especificación Java EE 7 define cuatro perfiles: web, ejb, jms y completo. La especificación Java EE 7 define dos perfiles: web y completo. ¿Cuáles dos de los siguientes enunciados acerca del perfil web son verdaderos? (Elija dos opciones). a. b. c. d. e. El perfil web cuenta con todas las API en el perfil completo, así como otras API enfocadas en tecnologías web. JBoss EAP no admite el perfil web. El perfil web admite Beans controlados por mensajes (MDB) JMS. EJB Lite forma parte del perfil web. CDI forma parte del perfil web. JB183-EAP7.0-es-2-20180124 53 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE Solución Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuál de los siguientes enunciados acerca de las aplicaciones Java SE y Java EE es verdadero? a. b. c. d. e. 2. ¿Cuáles tres de los siguientes enunciados acerca de JBoss EAP son verdaderos? (Elija tres opciones). a. b. c. d. e. f. 3. b. c. d. El concepto de módulos se aplica únicamente a los servicios proporcionados por EAP. Las aplicaciones no se pueden ejecutar como módulos. Tanto las aplicaciones desarrolladas por el usuario como los servicios proporcionados por EAP se pueden ejecutar como módulos. Los módulos tienen un alcance de visibilidad global; es decir, todos los módulos pueden acceder a clases de otros módulos en EAP en forma implícita. EAP tiene control exclusivo de la visibilidad. Las clases de otros módulos se deben solicitar de manera explicita. ¿Cuál de los siguientes enunciados acerca de los contenedores en un servidor de aplicaciones es verdadero? a. b. 54 EAP se basa en el servidor web de código abierto Tomcat. EAP se basa en el servidor de aplicaciones de código abierto Wildfly. Todas las API de Java EE se empaquetan como componentes modulares en EAP 7. Todos los módulos se activan de manera predeterminada y no se puede agregar ni eliminar ningún módulo. Todas las API de Java EE se empaquetan como componentes modulares en EAP 7. Estos módulos se puede activar solo cuando se lo requiere. En EAP 7, la agrupación en clústeres de alta disponibilidad, la mensajería y el almacenamiento en caché distribuido no están disponibles de manera predeterminada; debe usar productos de terceros para habilitar estas funciones. EAP 7 admite tanto el perfil Java EE 7 web como el completo. ¿Cuáles dos de los siguientes enunciados acerca de la arquitectura EAP 7 son verdaderos? (Elija dos opciones). a. 4. Las aplicaciones Java EE se alojan y gestionan mediante un servidor de aplicaciones. Las aplicaciones Java SE no se pueden conectar a una base de datos y realizar transacciones. Las aplicaciones Java SE son siempre uniproceso. Solo las aplicaciones Java EE son multiproceso. Las aplicaciones Java EE no pueden realizar mensajería asíncrona. En las aplicaciones Java EE, el desarrollador debe implementar manualmente el subprocesamiento múltiple y la concurrencia. Solo debe haber un contenedor en un servidor de aplicaciones. No se permiten múltiples contenedores dentro de un solo servidor de aplicaciones. En las implementaciones, los contenedores solo pueden leer descriptores de implementación XML. Las anotaciones de nivel de código no son compatibles. JB183-EAP7.0-es-2-20180124 Solución c. d. e. 5. ¿Cuáles dos de los siguientes enunciados acerca de los perfiles de Java EE son verdaderos? (Elija dos opciones). a. b. c. d. e. 6. Los componentes de aplicaciones pueden configurar la seguridad de manera declarativa en los descriptores de implementación XML o las anotaciones a nivel de código. Un contenedor web puede implementar componentes EJB, JMS y JPA. El contenedor EJB se debe ejecutar en forma separada, fuera del servidor de aplicaciones. El servidor de aplicaciones solo admite la ejecución del contenedor web dentro de este. Un perfil es una recopilación de API enfocada en tipos de aplicación específicos. El concepto de un perfil se introdujo en Java EE 7. La especificación Java EE 7 define tres perfiles: web, ejb y completo. La especificación Java EE 7 define cuatro perfiles: web, ejb, jms y completo. La especificación Java EE 7 define dos perfiles: web y completo. ¿Cuáles dos de los siguientes enunciados acerca del perfil web son verdaderos? (Elija dos opciones). a. b. c. d. e. El perfil web cuenta con todas las API en el perfil completo, así como otras API enfocadas en tecnologías web. JBoss EAP no admite el perfil web. El perfil web admite Beans controlados por mensajes (MDB) JMS. EJB Lite forma parte del perfil web. CDI forma parte del perfil web. JB183-EAP7.0-es-2-20180124 55 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE Identificación de recursos JNDI Objetivo Después de completar esta sección, los estudiantes deberán ser capaces de enumerar los tipos de recursos JNDI más comunes y sus convenciones de nomenclatura típicas. Recursos JNDI En una aplicación multinivel distribuida que ejecuta diferentes componentes en varios servidores, los componentes necesitan comunicarse entre sí. Por ejemplo, un cliente Java puede invocar métodos en un EJB implementado en una máquina separada; y el componente EJB se comunica con una base de datos para obtener datos. La Interfaz de nombrado y Directorio Java (JNDI) es una API de Java para un servicio de directorio (búsqueda de recursos) que permite a los componentes descubrir y buscar objetos a través de un nombre lógico. Un recurso es un objeto lógico que los componentes pueden buscar y utilizar en una aplicación Java EE. Cada recurso está identificado por un nombre único, denominado el nombre JNDI o una referencia de recursos JNDI. Por ejemplo, el nombre JNDI de la fuente de datos Conectividad de base de datos Java (JDBC), que se brinda de manera predeterminada (indicando una base de datos H2 incorporada) en JBoss EAP es java:jboss/datasources/ ExampleDS. Los recursos JNDI no están restrictos a fuentes de datos JDBC. Se pueden configurar varios tipos de recursos, como objetos JMS ConnectionFactory, colas y temas de mensajería, servidores de correo electrónico, conjuntos (pools) de hilos y otros. Cada una de las diferentes referencias JNDI se organizan en un namespace lógico, en una jerarquía de árbol, generalmente denominada JNDI tree (Árbol JNDI). Los siguientes son algunos de los espacios de nombre más comunes en el servidor de aplicaciones JBoss EAP: • Las fuentes de datos JDBC están registradas bajo el espacio de nombre java:jboss/ datasources/*. • Los recursos relacionados con JMS se registran con el nombre de espacio java:jboss/ jms/* (Colas JMS bajojava:jboss/jms/queue/* y Temas bajo java:jboss/jms/ topic/*). • Los recursos relacionados con el correo electrónico se registran con el nombre de espacio java:jboss/mail/*. • Los recursos relacionados con subprocesamiento múltiple y concurrencia se registran con el nombre de espacio java:jboss/ee/concurrency/*. Inyección de recursos mediante CDI Java EE 7 proporciona Contextos e Inyección de dependencia (CDI) para permitir que los componentes obtengan referencias a otros objetos de componentes, así como recursos de servidores de aplicaciones sin crear instancias de los objetos de componentes o recursos del servidor en forma manual. Esto permite una arquitectura de bajo acoplamiento, en la que el cliente no necesita estar al tanto de todos los detalles de implementación de nivel inferior del objeto invocado. 56 JB183-EAP7.0-es-2-20180124 Inyección de recursos mediante CDI Después de configurar las referencias de recursos JNDI necesarias a nivel del servidor de aplicaciones, puede inyectar los recursos en aplicaciones que requieren el recurso usando la anotación @Resource. El servidor de aplicaciones crea instancias del recurso en el tiempo de ejecución y proporciona una referencia al recurso. Por ejemplo, supongamos que ha configurado una referencia de fuente de datos JDBC como la siguiente en un archivo de configuración EAP: <subsystem xmlns="urn:jboss:domain:datasources:4.0"> <datasources> <datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true"> <connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</ connection-url> <driver>h2</driver> <security> <user-name>sa</user-name> <password>sa</password> </security> </datasource> </datasources> ... Ahora, puede inyectar la fuente de datos java:jboss/datasources/ExampleDS en la aplicación de la siguiente manera: public class TestDS { @Resource(name="java:jboss/datasources/ExampleDS") private javax.sql.DataSource ds; // Use the DataSource reference to create a Connection etc.. De forma similar, si configuró un recursos de Cola JMS como el siguiente en EAP: <jms-queue name="helloWorldQueue" entries="java:jboss/jms/queue/helloWorldQueue"/> Ahora puede enviar mensajes a esta cola inyectando el recurso en una clase de cliente JMS: @Resource(mappedName = "java:jboss/jms/queue/helloWorldQueue") private Queue helloWorldQueue; @Inject JMSContext context; // Use the Queue object to send messages... try { context.createProducer().send(helloWorldQueue, "Hello World!"); ... } JB183-EAP7.0-es-2-20180124 57 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE Referencias Para obtener más información, consulte el capítulo de Búsqueda de JNDI Remota y el capítulo Contextos e Inyección de dependencia (CDI) de la Guía de desarrollo para Red Hat JBoss EAP: https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/7.0/ 58 JB183-EAP7.0-es-2-20180124 Ejercicio guiado: Identificación de recursos JNDI Ejercicio guiado: Identificación de recursos JNDI En este ejercicio, explorará las referencias de recursos JNDI en el servidor de aplicaciones. Resultados Deberá ser capaz de iniciar EAP de una ventana de terminal y explorar las referencias de recursos JNDI en el servidor de aplicaciones. Antes de comenzar JBoss EAP ya se encuentra instalado en la carpeta /opt/jboss-eap-7.0 y se puede acceder a él con la variable de entorno JBOSS_HOME que indica esta carpeta. Abra una ventana de terminal en la máquina virtual de la estación de trabajo y ejecute el siguiente comando para verificar que EAP no se esté ejecutando actualmente: [student@workstation ~]$ lab jndi setup Pasos 1. Inicie JBoss EAP en una nueva ventana de terminal. 1.1. Abra una ventana de terminal desde la máquina virtual de la estación de trabajo (Aplicaciones > Favoritos > Terminal o haga clic con el botón derecho en el escritorio, seleccione Abrir en Terminal) y ejecute los siguientes comandos para iniciar EAP: [student@workstation ~]$ cd $JBOSS_HOME/bin [student@workstation bin]$ ./standalone.sh -c standalone-full.xml nota A lo largo de este curso, iniciará EAP en modo independiente con el perfil standalone-full, que contiene todas las API y subsistemas que admiten el perfil Java EE 7 completo. 1.2. Cuando el servidor de aplicaciones se inicia, el servidor imprime mensajes de registro en la consola en que inició EAP. Si EAP se inicia correctamente, verá una salida similar a la siguiente: 13:35:23,615 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990 13:35:23,615 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started in 3321ms - Started 319 of 604 services (393 services are lazy, passive or on-demand) 2. Vea los mensajes de registro del servidor en la consola, así como el archivo de registro del servidor. JB183-EAP7.0-es-2-20180124 59 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE 2.1. EAP imprime mensajes de registros del servidor en la consola en la que lo inició. Revise brevemente los mensajes de registro desplazándose hacia arriba y hacia abajo en la ventana del terminal. 2.2. Aunque ver los mensajes de registro en la consola es útil para solucionar problemas básicos, EAP imprime muchos mensajes cuando se implementan varias aplicaciones y ver estos mensajes en la consola se vuelve difícil de manejar. Para permitir un análisis fuera de línea, EAP también escribe mensajes de registro en un archivo. Abra el archivo /opt/jboss-eap-7.0/standalone/log/server.log en un editor de texto. Revise brevemente los mensajes en el archivo de registro. 3. Explore las referencias de recursos JNDI en el archivo de registro de servidores. 3.1. El servidor de aplicaciones mantiene una lista de referencias de recursos JNDI. Los recursos necesarios para las aplicaciones, como por ejemplo, el correo, las fuentes de datos JDBC y las fábricas y colas de conexiones JMS están vinculadas a nombres únicos identificables bajo los respectivos espacios de nombre. Las fuentes de datos JDBC están vinculadas al espacio de nombre java:jboss/ datasources/*. En el archivo /opt/jboss-eap-7.0/standalone/log/ server.log, verifique poder ver las dos siguientes referencias de fuentes de datos: WFLYJCA0001: Bound data source [java:jboss/datasources/ExampleDS] WFLYJCA0001: Bound data source [java:jboss/datasources/MySQLDS] nota Consejo: puede usar la capacidad de búsqueda de su editor de texto (generalmente Ctrl+F) o usar el comando grep para encontrar las referencias en el archivo de registro: [student@workstation ~]$ grep -i "datasources" /opt/jboss-eap-7.0/ standalone/log/server.log Los puntos de referencia ExampleDS a una base de datos H2 incorporada que se envía con EAP. Los puntos de referencia MySQLDS a una base de datos MySQL que usará la aplicación To Do List Java EE que debe compilar durante este curso. 3.2. Explore las referencias JNDI relacionadas con JMS. En el archivo /opt/jboss-eap-7.0/standalone/log/server.log, verifique poder ver las siguientes referencias relacionadas con JMS: WFLYMSGAMQ0002: Bound messaging object to jndi name java:jboss/exported/jms/ RemoteConnectionFactory ... WFLYMSGAMQ0002: Bound messaging object to jndi name java:/ConnectionFactory ... WFLYMSGAMQ0002: Bound messaging object to jndi name java:jboss/ DefaultJMSConnectionFactory 60 JB183-EAP7.0-es-2-20180124 4. Detenga EAP presionando Ctrl+C en la ventana de terminal en la que inició las instancias. Esto concluye el ejercicio guiado. JB183-EAP7.0-es-2-20180124 61 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE Empaquetado e implementación de una aplicación de Java EE Objetivo Después de completar esta sección, los estudiantes deberán ser capaces de empaquetar una aplicación Java EE simple e implementarla en JBoss EAP mediante Maven. Empaquetado e implementación de aplicaciones de Java EE Las aplicaciones Java EE se pueden empaquetar de diferentes formas para implementar en un servidor de aplicaciones compatible. Según el tipo de aplicación y los componentes que contiene, las aplicaciones se pueden empaquetar en diferentes tipos de implementación (archivos comprimidos que contienen clases, activos de aplicaciones y descriptores de implementación de XML). Los tres tipos de implementación más comunes son: • JAR files: los archivos JAR pueden contener clases de Objetos comunes antiguos de Java (POJO), Beans de entidad JPA, clases de Java de utilidad, EJB y MDB. Al implementarse en un servidor de aplicaciones, dependiendo del tipo de componentes dentro del archivo JAR, el servidor de aplicaciones busca descriptores de implementación XML o anotaciones a nivel de código, e implementa cada componente en consecuencia. Figura 2.3: Estructura del archivo EJB JAR de muestra • Archivos WAR: Un archivo WAR se utiliza para empaquetar aplicaciones web. Puede contener uno o más archivos JAR, así como archivos del descriptor de implementación XML en las carpetas WEB-INF o WEB-INF/classes/META-INF. 62 JB183-EAP7.0-es-2-20180124 Empaquetado e implementación de aplicaciones de Java EE Figura 2.4: Estructura del archivo WAR de muestra • EAR files: un archivo EAR contiene varios archivos JAR y WAR, así como descriptores de implementación XML en la carpeta META-INF. Figura 2.5: Estructura del archivo EAR de muestra JB183-EAP7.0-es-2-20180124 63 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE nota De estar presentes, los descriptores de implementación XML reemplazan a las anotaciones de nivel de código. Para un componente dado, evite duplicar la configuración en ambos lugares. Empaquetado e implementación de aplicaciones de Java EE en EAP Maven proporciona varios complementos (plug-ins) útiles para simplificar el empaquetado y la implementación en EAP durante el desarrollo del ciclo de vida útil. El maven-war-plugin crea archivos WAR de su aplicación, siempre que usted siga el diseño del código fuente Maven estándar. El maven-war-plugin se puede declarar en la sección <build> de su archivo pom.xml Maven: <build> <finalName>todo</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>${version.war.plugin}</version> <extensions>false</extensions> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> En forma similar, el maven-ear-plugin crea archivos EAR del código fuente de su aplicación. Se declara en la sección <build> de su archivo pom.xml Maven. Debe indicar los archivos WAR que se deben empaquetar dentro del archivo EAR con la etiqueta <webModule>: <build> <finalName>todo</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-ear-plugin</artifactId> <version>${version.ear.plugin}</version> <configuration> <version>6</version> <defaultLibBundleDir>lib</defaultLibBundleDir> <modules> <webModule> <groupId>com.redhat.training</groupId> <artifactId>todojee-web</artifactId> <contextRoot>/todo-ear</contextRoot> </webModule> </modules> <fileNameMapping>no-version</fileNameMapping> </configuration> </plugin> 64 JB183-EAP7.0-es-2-20180124 Empaquetado e implementación de aplicaciones de Java EE en EAP </plugins> </build> Puede usar Maven para implementar aplicaciones en JBoss EAP, mediante el wildflymaven-plugin, que proporciona funciones para implementar aplicaciones y anular su implementación en EAP. Admite la implementación de los tres tipos de unidades de implementación: JAR, WAR y EAR. Puede declarar el complemento (plug-in) en el archivo pom.xml Maven de su proyecto: <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-maven-plugin</artifactId> <version>${version.wildfly.maven.plugin}</version> </plugin> Para compilar, empaquetar e implementar una aplicación en EAP, ejecute el siguiente comando de la carpeta raíz de su proyecto: [student@workstation todojee]$ mvn clean package wildfly:deploy Para anular la implementación de una aplicación de EAP, ejecute el siguiente comando de la carpeta raíz de su proyecto: [student@workstation todojee]$ mvn wildfly:undeploy Referencias Para obtener más información, consulte el capítulo Implementación de aplicaciones mediante Maven de la Guía de desarrollo para Red Hat JBoss EAP 7.0: https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ JB183-EAP7.0-es-2-20180124 65 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE Ejercicio guiado: Empaquetado e implementación de una aplicación de Java EE Resultados Deberá ser capaz de importar un simple proyecto de la aplicación web Java EE en JBoss Developer Studio y empaquetarlo e implementarlo en EAP. Antes de comenzar Abra una ventana de terminal en la máquina virtual de la estación de trabajo y ejecute el siguiente comando para descargar los archivos del trabajo de laboratorio necesarios para este taller. [student@workstation ~]$ lab hello-web setup 1. Importe el proyecto hello-web en el JBoss Developer Studio IDE (JBDS). 1.1. Inicie JBDS haciendo doble clic en el icono del escritorio de la máquina virtual workstation. 1.2. En la ventana Eclipse Launcher, ingrese /home/student/JB183/workspace en el campo Workspace (Espacio de trabajo) y, luego, haga clic en Launch (Iniciar). Figura 2.6: Diálogo del espacio de trabajo JBDS 1.3. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.4. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta hello-web y haga clic en OK (Aceptar). 1.6. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 66 JB183-EAP7.0-es-2-20180124 1.7. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Explore el archivo pom.xml Maven del proyecto y el archivo POM principal. 2.1. Expanda el ítem hello-web en el panel Project Explorer (Explorador de proyectos) izquierdo y haga doble clic en el archivo pom.xml. La pestaña Overview (Descripción general) se puede ver en la principal ventana del editor y muestra una vista avanzada del proyecto. En esta pestaña se muestran Group Id, Artifact Id y la Version (generalmente se los conoce como las coordenadas GAV de un proyecto o módulo) del proyecto hello-web, así como su elemento principal. 2.2. Haga clic en la pestaña Dependencies (Dependencias) para ver las dependencias (librerías, marcos [frameworks] y módulos de los que depende este proyecto) del proyecto. 2.3. Haga clic en la pestaña pom.xml para ver el texto completo del archivo pom.xml. Repase brevemente las coordenadas GAV para este proyecto: <artifactId>hello-web</artifactId> <packaging>war</packaging> <name>Hello World web app Project</name> <description>This is the hello-web project</description> <parent> <groupId>com.redhat.training</groupId> <artifactId>parent-pom</artifactId> <version>1.0</version> <relativePath>../pom.xml</relativePath> </parent> ... <build> <plugins> <plugin> <artifactId>maven-war-plugin</artifactId> <version>${version.war.plugin}</version> ... </plugin> El formato de packaging (empaquetado) se declara como war. Maven se asegura de que, cuando se compile el proyecto, se generará un archivo WAR que se puede implementar en EAP. Este proyecto hereda las declaraciones y propiedades del archivo POM principal, que se ubica en /home/student/JB183/labs/pom.xml. El archivo POM principal declara muchos atributos y propiedades que pueden ser usados por varios proyectos secundarios que hacen referencia a él. Es una práctica recomendada de Maven declarar repositorios, dependencias maestras, lista de materiales (BOM) y demás atributos usados en varios proyectos para evitar la duplicación. JB183-EAP7.0-es-2-20180124 67 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE Debido a que este proyecto es una aplicación web compilada como archivo WAR, se configura el complemento (plug-in) WAR de Maven (maven-warplugin). 2.4. Abra el archivo pom principal /home/student/JB183/labs/pom.xml mediante JBDS (haga clic en File (Archivo) > Open File (Abrir archivo)) o en un editor de texto. 2.5. El archivo POM principal declara una cantidad de propiedades comúnmente usadas en todos los proyectos de este curso. Por ejemplo, el archivo declara que la versión de la lista de materiales de JBoss EAP (BOM) es 7.0.0.GA y que todos los proyectos estarán compilados en JDK 1.8. El complemento (plug-in) Wildfly Maven también se declara en el archivo POM principal. Se utiliza para implementar el archivo WAR del proyecto en EAP y depende de la variable del entorno JBOSS_HOME para identificar la instancia de JBoss EAP en la que el archivo WAR se debe implementar. 3. Configure una instancia de servidor EAP en JBDS. 3.1. Haga clic en la pestaña Servers (Servidores) en la parte inferior de JBDS, debajo del área del editor principal. Figura 2.7: La pestaña de servidores JBDS 3.2. Haga clic en Sin servidores disponibles para definir un nuevo servidor EAP. 3.3. En la ventana Define a New Server (Definir un nuevo servidor), seleccione la opción Red Hat JBoss Enterprise Application Platform 7.0 y haga clic en Next (Siguiente). 68 JB183-EAP7.0-es-2-20180124 Figura 2.8: Defina un nuevo servidor EAP 3.4. En la ventana Create a new Server Adapter (Crear un nuevo adaptador del servidor), deje los campos con sus valores predeterminados, como se muestra en la figura a continuación, y haga clic en Next (Siguiente). JB183-EAP7.0-es-2-20180124 69 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE Figura 2.9: Configuración del nuevo adaptador del servidor 3.5. En la ventana JBoss Runtime (Tiempo de ejecución de JBoss), haga clic en Browse (Explorar), junto al campo Home Directory (Directorios de inicio) y seleccione la carpeta /opt/jboss-eap-7.0 (la variable $JBOSS_HOME del entorno indica esta tarjeta). Debido a que estará ejecutando el perfil standalone-full, edite el campo Archivo de configuración y cámbielo a standalone-full.xml del standalone.xml predeterminado. Para continuar, haga clic en Next (Siguiente). 70 JB183-EAP7.0-es-2-20180124 Figura 2.10: Configuración del tiempo de ejecución de JBoss EAP 3.6. En la pantalla Add and Remove (Agregar y eliminar), haga clic en Finish (Finalizar). 3.7. Ahora debe ver una nueva entrada de servidor denominada Red Hat JBoss EAP 7.0, agregada a la pestaña Servers (Servidores) de JBDS. Haga clic en esta entrada para expandirla. Figura 2.11: Agregue un nuevo servidor JBoss EAP 4. Inicie EAP desde dentro de JBDS. 4.1. Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y haga clic en Start (Iniciar) (icono verde de reproducción) para iniciar la instancia EAP recientemente agregada. JB183-EAP7.0-es-2-20180124 71 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE Figura 2.12: Inicio de un servidor JBoss EAP 4.2. Cuando EAP se inicia, imprime mensajes en la pestaña Console (Consola) de JBDS. Verifique que no aparezcan errores en la consola. Figura 2.13: Salida del registro de la consola JBoss EAP 72 JB183-EAP7.0-es-2-20180124 5. Compile, empaquete e implemente la aplicación hello-web. 5.1. Abra una nueva ventana de terminal en la máquina virtualworkstation y ejecute los siguientes comandos para compilar, empaquetar e implementar la aplicación hello-web mediante Maven: [student@workstation ~]$ cd /home/student/JB183/labs/hello-web [student@workstation hello-web]$ mvn clean package wildfly:deploy Al ejecutar el comando anterior, verá la siguiente salida: [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] -----------------------------------------------------------------------BUILD SUCCESS -----------------------------------------------------------------------Total time: 24.160 s Finished at: 2016-11-20T01:08:05-05:00 Final Memory: 34M/248M ------------------------------------------------------------------------ 5.2. Haga clic en Console (Consola) en JBDS para el servidor EAP y observe la implementación de la aplicación hello-web: 01:08:03,664 INFO [org.jboss.as.server.deployment] (MSC service thread 1-1) WFLYSRV0027: Starting deployment of "hello-web.war" (runtime-name: "helloweb.war") … 01:08:05,624 INFO [org.wildfly.extension.undertow] (ServerService Thread Pool -- 72) WFLYUT0021: Registered web context: /hello-web 01:08:05,705 INFO [org.jboss.as.server] (management-handler-thread - 1) WFLYSRV0010: Deployed "hello-web.war" (runtime-name : "hello-web.war") 6. Acceda a la aplicación hello-web mediante un explorador. 6.1. Verifique que no aparezcan errores en la consola al implementar la aplicación. Use un explorador web en la máquina virtual workstation para dirigirse a http:// localhost:8080/hello-web para acceder a la aplicación hello-web. Figura 2.14: La aplicación hello-web. JB183-EAP7.0-es-2-20180124 73 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE 7. Ingrese John Doe en el campo Enter your name (Ingrese su nombre) y haga clic en Submit (Enviar). 8. Verifique que el servidor procese la entrada y responda con el mensaje Hola, así como la hora actual en el servidor. Figura 2.15: La respuesta de la aplicación hello-web. 9. Anule la implementación de la aplicación y detenga EAP. 9.1. En la ventana del terminal donde ejecutó el comando Maven para implementar la aplicación, ejecute el siguiente comando para anular la implementación de la aplicación de EAP: [student@workstation hello-web]$ mvn wildfly:undeploy Al ejecutar el comando anterior, se anula la implementación del archivo helloweb.war de EAP. Verá la siguiente salida en la pestaña Console (Consola) de EAP: 21:00:31,705 INFO [org.wildfly.extension.undertow] (ServerService Thread Pool -- 77) WFLYUT0022: Unregistered web context: /hello-web 21:00:31,988 INFO [org.jboss.as.server] (management-handler-thread - 13) WFLYSRV0009: Undeployed "hello-web.war" (runtime-name: "hello-web.war") 9.2. Haga clic con el botón derecho en el proyecto hello-web en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrar el proyecto. 9.3. Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y haga clic en Stop (Detener) para detener la instancia EAP. Esto concluye el ejercicio guiado. 74 JB183-EAP7.0-es-2-20180124 Trabajo de laboratorio: Empaquetado e implementación de aplicaciones en un servidor de aplicaciones Trabajo de laboratorio: Empaquetado e implementación de aplicaciones en un servidor de aplicaciones En este trabajo de laboratorio, aprenderá a empaquetar e implementar una aplicación Java EE mediante Maven y JBoss Developer Studio. Resultados Deberá ser capaz de empaquetar e implementar la aplicación To Do List Java EE en JBoss EAP mediante Maven y JBoss Developer Studio. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab todojee setup Pasos 1. Importe el proyecto todojee en JBoss Developer Studio IDE (JBDS). 2. Inicie EAP desde dentro de JBDS. 3. Compile, empaquete e implemente la aplicación todo-app en EAP. 4. Verifique la implementación correcta del archivo todo-app en EAP. 5. Navegue a http://localhost:8080/todo para acceder a la aplicación todo. JB183-EAP7.0-es-2-20180124 75 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE Aplicación To Do List 6. Explore la funcionalidad de la aplicación To Do List. 6.1. Explore las tareas existentes. Un conjunto de tareas se carga previamente en la base de datos. Revise las tareas utilizando la barra de paginación debajo de la lista de tareas. La casilla de verificación Done (Listo) se selecciona para tareas completadas y el texto Description (Descripción) aparece en tamaño de fuente tachado: Una tarea completada 6.2. Agregue una nueva tarea. Puede agregar una nueva tarea ingresando una descripción de tareas en el panel Add Task (Agregar tarea) en el lado derecho. Si la tarea ya está completa, puede seleccionar la casilla de verificación Completed (Completo). Haga clic en Save (Guardar) para agregar su tarea a la base de datos. 76 JB183-EAP7.0-es-2-20180124 Agregue una tarea Solo se muestran cinco tareas por página. Use la barra de paginación en la parte inferior de la tabla para navegar a la última página y verifique que aparezca su tarea recientemente agregada. 6.3. Complete una tarea. Para completar una tarea, solo debe activar la casilla de verificación en la columna Done (Listo) en cada tarea. Al volver a activar la casilla de verificación, se revierte el estado y se marca la tarea como pendiente. 6.4. Elimine una tarea. Para eliminar una tarea, haga clic en la X roja de la última columna de la derecha. 7. Limpieza y calificación. 7.1. Para verificar que ha implementado correctamente la aplicación, abra una nueva ventana de terminal en la máquina virtual de la estación de trabajo y ejecute el siguiente comando para calificar este trabajo de laboratorio: [student@workstation ~]$ lab todojee grade Si observa fallas después de ejecutar el comando, vea los errores, solucione los problemas de implementación y corrija los errores. Vuelva a ejecutar el script de calificación y verifique que el resultado sea satisfactorio. 7.2. En la ventana del terminal donde ejecutó el comando Maven para implementar la aplicación, ejecute el siguiente comando para anular la implementación de la aplicación de EAP: [student@workstation todojee]$ mvn wildfly:undeploy 21:16:33,035 INFO [org.jboss.as.server] (management-handler-thread - 9) WFLYSRV0009: Undeployed "todo.war" (runtime-name: "todo.war") 7.3. Haga clic con el botón derecho en el proyecto todojee en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). JB183-EAP7.0-es-2-20180124 77 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE 7.4. Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y haga clic en Stop (Detener) para detener la instancia EAP. Esto concluye el trabajo de laboratorio. 78 JB183-EAP7.0-es-2-20180124 Solución Solución En este trabajo de laboratorio, aprenderá a empaquetar e implementar una aplicación Java EE mediante Maven y JBoss Developer Studio. Resultados Deberá ser capaz de empaquetar e implementar la aplicación To Do List Java EE en JBoss EAP mediante Maven y JBoss Developer Studio. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab todojee setup Pasos 1. Importe el proyecto todojee en JBoss Developer Studio IDE (JBDS). 1.1. Haga clic en el icono JBDS de la máquina virtual workstation para iniciar JBDS. 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), seleccione Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). 1.5. Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta todojee y haga clic en OK (Aceptar). 1.6. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.7. Espere hasta que JBDS termine de importar el proyecto. 2. Inicie EAP desde dentro de JBDS. 2.1. Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y, luego, haga clic en Start (Inicio) para iniciar la instancia EAP configurada en el ejercicio anterior. 2.2. Cuando EAP se inicia, imprime mensajes en la pestaña Console (Consola) de JBDS. Verifique que no se encuentren errores en la consola. 3. Compile, empaquete e implemente la aplicación todo-app en EAP. Abra una nueva ventana de terminal en la estación de trabajo y ejecute los siguientes comandos: [student@workstation ~]$ cd /home/student/JB138/labs/todojee [student@workstation todojee]$ mvn clean package wildfly:deploy ... JB183-EAP7.0-es-2-20180124 79 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE [INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS [INFO] -----------------------------------------------------------------------[INFO] Total time: 9.295 s [INFO] Finished at: 2016-11-21T16:05:11+05:30 [INFO] Final Memory: 27M/389M [INFO] ------------------------------------------------------------------------ 4. Verifique la implementación correcta del archivo todo-app en EAP. Cambie a la pestaña Console (Consola) de JBDS y observe la implementación de la aplicación todo-app. Verifique que no aparezcan errores en la consola: WFLYSRV0027: Starting deployment of "todo.war" (runtime-name: "todo.war") WFLYUT0021: Registered web context: /todo WFLYSRV0010: Deployed "todo.war" (runtime-name : "todo.war") 5. Navegue a http://localhost:8080/todo para acceder a la aplicación todo. Aplicación To Do List 6. Explore la funcionalidad de la aplicación To Do List. 6.1. Explore las tareas existentes. Un conjunto de tareas se carga previamente en la base de datos. Revise las tareas utilizando la barra de paginación debajo de la lista de tareas. La casilla de verificación Done (Listo) se selecciona para tareas completadas y el texto Description (Descripción) aparece en tamaño de fuente tachado: 80 JB183-EAP7.0-es-2-20180124 Solución Una tarea completada 6.2. Agregue una nueva tarea. Puede agregar una nueva tarea ingresando una descripción de tareas en el panel Add Task (Agregar tarea) en el lado derecho. Si la tarea ya está completa, puede seleccionar la casilla de verificación Completed (Completo). Haga clic en Save (Guardar) para agregar su tarea a la base de datos. Agregue una tarea Solo se muestran cinco tareas por página. Use la barra de paginación en la parte inferior de la tabla para navegar a la última página y verifique que aparezca su tarea recientemente agregada. 6.3. Complete una tarea. Para completar una tarea, solo debe activar la casilla de verificación en la columna Done (Listo) en cada tarea. Al volver a activar la casilla de verificación, se revierte el estado y se marca la tarea como pendiente. 6.4. Elimine una tarea. Para eliminar una tarea, haga clic en la X roja de la última columna de la derecha. 7. Limpieza y calificación. 7.1. Para verificar que ha implementado correctamente la aplicación, abra una nueva ventana de terminal en la máquina virtual de la estación de trabajo y ejecute el siguiente comando para calificar este trabajo de laboratorio: [student@workstation ~]$ lab todojee grade Si observa fallas después de ejecutar el comando, vea los errores, solucione los problemas de implementación y corrija los errores. Vuelva a ejecutar el script de calificación y verifique que el resultado sea satisfactorio. JB183-EAP7.0-es-2-20180124 81 Capítulo 2. Empaquetado e implementación de una aplicación de Java EE 7.2. En la ventana del terminal donde ejecutó el comando Maven para implementar la aplicación, ejecute el siguiente comando para anular la implementación de la aplicación de EAP: [student@workstation todojee]$ mvn wildfly:undeploy 21:16:33,035 INFO [org.jboss.as.server] (management-handler-thread - 9) WFLYSRV0009: Undeployed "todo.war" (runtime-name: "todo.war") 7.3. Haga clic con el botón derecho en el proyecto todojee en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 7.4. Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y haga clic en Stop (Detener) para detener la instancia EAP. Esto concluye el trabajo de laboratorio. 82 JB183-EAP7.0-es-2-20180124 Resumen Resumen En este capítulo, aprendió lo siguiente: • Un servidor de aplicaciones proporciona el entorno y la infraestructura de tiempo de ejecución que se necesitan para alojar y administrar aplicaciones empresariales Java EE. • Red Hat JBoss EAP 7 es un servidor de aplicaciones que cumple con Java EE 7 y proporciona una infraestructura confiable, de alto rendimiento, liviana y compatible para implementar aplicaciones Java EE. • Un perfil en el contexto de un servidor de aplicaciones Java EE es un conjunto de API relacionadas dirigido a un tipo específico de aplicaciones. La especificación Java EE 7 define dos perfiles: web y completo. JBoss EAP es completamente compatible con ambos perfiles. • Un recurso es un objeto lógico que los componentes pueden buscar y utilizar en una aplicación Java EE. Cada recurso está identificado por un nombre único, denominado el nombre JNDI o una referencia de recursos JNDI. • Las referencias JNDI se organizan en un namespace lógico, en una jerarquía de árbol, generalmente denominada JNDI tree (Árbol JNDI). • Puede inyectar recursos de JNDI en aplicaciones que requieren el recurso utilizando una anotación @Resource. • Las aplicaciones Java EE se empaquetan y se implementan en diferentes formatos. Los tres más comunes son: archivos JAR, WAR y EAR. • Maven implementa aplicaciones en JBoss EAP, mediante el complemento (plug-in) Wildfly Maven, que proporciona funciones para implementar aplicaciones y anular su implementación en EAP. Admite la implementación de los tres tipos de unidades de implementación: JAR, WAR y EAR. JB183-EAP7.0-es-2-20180124 83 84 TRAINING CAPÍTULO 3 CREACIÓN DE ENTERPRISE JAVA BEANS Descripción general Meta Crear Enterprise Java Beans. Objetivos • Convertir un POJO en un EJB. • Acceder a un EJB de manera local y remota. • Describir el ciclo de vida de los EJB. • Describir las transacciones gestionadas por contenedor y gestionadas por bean y delimitar cada una en un EJB. Secciones • Conversión de un POJO a un EJB (y ejercicio guiado) • Acceso local y remoto a un EJB (y ejercicio guiado) • Descripción del ciclo de vida de un EJB (y cuestionario) • Delimitación de transacciones implícitas y explícitas (y cuestionario) Trabajo de laboratorio JB183-EAP7.0-es-2-20180124 • Creación de Enterprise Java Beans 85 Capítulo 3. Creación de Enterprise Java Beans Conversión de un POJO en un EJB Objetivo Tras finalizar esta sección, los estudiantes deberán ser capaces de convertir un POJO en un EJB. Descripción de Enterprise Java Beans (EJB) Un Enterprise Java Bean (EJB) es un componente de Java EE que, por lo general, se usa para encapsular lógica de negocio en una aplicación empresarial. A diferencia de los beans Java simples de Java SE, donde el desarrollador debe implementar explícitamente conceptos como subprocesamiento múltiple, concurrencia, transacciones y seguridad, en un EJB, el servidor de aplicaciones proporciona estas características en el tiempo de ejecución y permite que el desarrollador se centre en redactar la lógica de negocio para la aplicación. El uso de EJB para modelar la lógica de negocio de una aplicación empresarial tiene varias ventajas: • Los EJB brindan servicios de sistema de bajo nivel, como subprocesamiento múltiple y concurrencia, sin que el desarrollador tenga que escribir código explícitamente para esos servicios. Esto es importante para las aplicaciones empresariales que tienen una gran cantidad de usuarios que acceden a la aplicación al mismo tiempo. • La lógica de negocio se encapsula en un componente portátil que se puede distribuir en varias máquinas de manera transparente para los clientes, y le permite balancear la carga de las solicitudes cuando acceden muchos clientes a la aplicación al mismo tiempo. • El código cliente se simplifica debido a que el cliente puede centrarse solo en los aspectos de la interfaz de usuario, sin mezclar lógica de negocio. Por ejemplo, observe cómo la aplicación To Do List de Java SE combina el código de interfaz de usuario y la lógica core para la gestión de listas en el mismo proceso y, a menudo, en la misma clase. • Los EJB brindan capacidades transaccionales a las aplicaciones empresariales, donde una cantidad de usuarios accede al mismo tiempo a la aplicación y el servidor de aplicaciones garantiza la integridad de datos con el uso de las transacciones. • Los componentes de EJB se pueden proteger para el acceso de grupos o roles. El servidor de aplicaciones brinda una API para los servicios de autenticación y autorización, sin que el desarrollador tenga que escribir código explícitamente. • Se puede acceder a los EJB con diferentes tipos de clientes, como clientes remotos independientes, otros componentes de Java EE o clientes de servicio web que utilizan protocolos estándares como SOAP o REST. Revisión de los tipos de EJB La especificación de Java EE define dos tipos diferentes de EJB: • Sesión: Realiza una operación cuando se lo invoca desde un cliente. Por lo general, la lógica de negocio core de una aplicación se expone como API de alto nivel (patrón de fachada de sesión) que se puede distribuir y a la que se puede acceder con varios protocolos (RMI, JNDI, servicios web). 86 JB183-EAP7.0-es-2-20180124 Descripción de los beans de sesión • Bean controlado por mensaje (MDB): Usado para la comunicación asíncrona entre componentes en una aplicación Java EE y que se puede usar para recibir mensajes compatibles con el Servicio de mensajería Java (JMS) y realizar algunas acciones según el contenido de los mensajes recibidos. Descripción de los beans de sesión Un bean de sesión proporciona una interfaz a los clientes y encapsula métodos de lógica de negocio que se pueden invocar con varios clientes, ya sea local o remotamente, en diferentes protocolos. Los EJB de sesión se pueden agrupar e implementar en varias máquinas de manera transparente para los clientes. El estándar Java EE no define formalmente los detalles generales de cómo se deben agrupar los EJB. Cada servidor de aplicaciones proporciona sus propios mecanismos para agrupación en clústeres y alta disponibilidad. Por lo general, la interfaz de un bean de sesión expone una API detallada que encapsula la lógica de negocio core de la aplicación. Hay tres tipos diferentes de beans de sesión, según el caso de uso de la aplicación, que se pueden implementar en un servidor de aplicaciones compatible con Java EE: Beans de sesión sin estado (SLSB) Un bean de sesión sin estado no mantiene el estado conversacional con clientes entre llamadas. Cuando los clientes interactúan con el bean de sesión sin estado e invocan métodos en él, el servidor de aplicaciones asigna una instancia de un conjunto (pool) de beans de sesión sin estado, de los cuales ya se crearon instancias previamente. Una vez que un cliente completa la invocación y se desconecta, la instancia del bean se coloca nuevamente en el conjunto (pool) o se destruye. Un bean de sesión sin estado es útil en escenarios donde la aplicación tiene que prestar servicio a una gran cantidad de clientes que acceden al mismo tiempo a los métodos de negocio del bean. Por lo general, pueden escalarse mejor que los beans de sesión con estado debido a que el servidor de aplicaciones no tiene que mantener el estado y los beans se pueden distribuir en varias máquinas en una implementación grande. Tenga en cuenta que, cuando trabaja con EJB sin estado, debe asegurarse de no definir construcciones y elementos de datos con estado que necesiten compartirse entre varios clientes (por ejemplo, estructuras similares a asignaciones que contienen una caché). Estos tipos de casos de uso se resolverían más adecuadamente mediante el uso de un bean de sesión con estado o un bean de sesión singleton. Un bean de sesión sin estado también es la opción predilecta para exponer extremos de servicio REST o SOAP para clientes de servicios web. Las anotaciones simples se agregan a la clase del bean y a los métodos para tener esta funcionalidad sin tener que escribir código repetitivo para la comunicación de servicios web. Beans de sesión con estado (SFSB) En contraste con los beans de sesión sin estado, los beans de sesión con estado mantienen el estado conversacional con clientes en varias llamadas. Hay una relación de uno a uno entre la cantidad de instancias de beans con estado y la cantidad de clientes. Cuando un cliente completa la interacción con el bean y se desconecta, la instancia del bean se destruye. Un nuevo cliente genera un nuevo bean con estado con su propio estado único. El servidor de aplicaciones garantiza que cada cliente reciba la misma instancia de un bean de sesión con estado para cada invocación de método. JB183-EAP7.0-es-2-20180124 87 Capítulo 3. Creación de Enterprise Java Beans Los beans de sesión con estado se usan en escenarios donde el estado conversacional debe mantenerse con un cliente durante la interacción. Por ejemplo, un carrito de compras realiza un seguimiento de la cantidad de ítems que un cliente agrega al carrito en una aplicación de comercio electrónico. Cada carrito del cliente se encapsula en el estado del bean y el estado se actualiza cuando el cliente agrega, actualiza o elimina ítems de compra. Cuando el desarrollador compila un EJB con estado, cualquier atributo de nivel de clase se debe incluir como private (privado) y se crean los métodos getter y setter para proporcionar acceso a estos atributos. Este es un patrón común utilizado en el desarrollo de Java pero también se incorpora automáticamente cuando trabaja con EJB que respaldan páginas JSF (Caras de servidor Java). Cuando utiliza lenguaje de expresión (EL) en el código fuente de JSF para asignar campos de formularios a atributos EJB, se puede acceder automáticamente a los atributos EJB a través de getters y setters sin usar explícitamente el nombre del método. Un ejemplo de un bean de sesión con estado se muestra debajo con sus métodos getter y setter: @Stateful @Named("hello") public class Hello { private String name; @Inject private PersonService personService; public void sayHello() throws IllegalStateException, SecurityException, SystemException { String response = personService.hello(name); FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(response)); } public String getName() { return name; } public void setName(String name) { this.name = name; } } Beans de sesión singleton Los beans de sesión singleton son beans de sesión de los cuales se crean instancias una vez por aplicación y que existen durante el ciclo de vida de la aplicación. Cada solicitud del cliente para un bean singleton va a la misma instancia. Los beans de sesión singleton se usan en escenarios donde se comparte una única instancia de bean empresarial entre varios clientes. A diferencia de los beans de sesión sin estado, que están agrupados por el servidor de aplicaciones, solo hay una instancia de un bean de sesión singleton en la memoria. De manera similar a los beans de sesión sin estado, los beans de sesión singleton también se pueden usar para implementar extremos de servicios web. Un desarrollador puede proporcionar anotaciones para indicar que el servidor de aplicaciones debe iniciar el bean en el inicio como una optimización de rendimiento (por ejemplo, conexiones de bases de datos, búsquedas JNDI, creación de fábrica de conexión remota JMS y mucho más). 88 JB183-EAP7.0-es-2-20180124 Beans controlados por mensajes Beans controlados por mensajes Un bean controlado por mensaje (MDB) permite que las aplicaciones Java EE procesen mensajes de manera asíncrona. Una vez implementado en un servidor de aplicaciones, escucha los mensajes JMS y, para cada mensaje recibido, realiza una acción (se invoca el método onMessage() de MDB). Los MDB brindan un modelo sin conexión directa impulsado por eventos para el desarrollo de aplicaciones. Los MDB no se inyectan en o invocan con código cliente, pero se desencadenan con la recepción de mensajes. El MDB no tiene estado y no mantiene un estado conversacional con los clientes. El servidor de aplicaciones mantiene un conjunto (pool) de MDB y administra su ciclo de vida al asignar y devolver instancias desde y hacia el conjunto. También pueden participar en transacciones y el servidor de aplicaciones se ocupa del reenvío de mensajes y la confirmación de recepción de mensajes según el resultado del procesamiento de mensajes. Existen muchos casos de uso donde se pueden utilizar los MDB. El más popular es para desvincular sistemas y prevenir que sus API se conecten muy estrechamente con la invocación directa. En cambio, dos sistemas se pueden comunicar al enviar mensajes de manera asíncrona, lo que garantiza que los dos sistemas puedan evolucionar independientemente sin tener impacto uno en el otro. Generación de un EJB automáticamente mediante JBoss Developer Studio JBDS proporciona varias plantillas que se pueden aprovechar para generar código automáticamente. Mediante una plantilla, es posible sacar provecho de JBDS para generar la shell de un EJB automáticamente. Debe seguir estos pasos para lograrlo: 1. En el panel Project Explorer (Explorador de proyectos) del lado izquierdo de JBDS, seleccione el proyecto al que desea agregarle una clase EJB y, luego, haga clic con el botón derecho en el nombre del proyecto. Seleccione New (Nuevo) y desplácese a la parte inferior y seleccione Other (Otro). 2. Cuando se abra el panel de búsqueda, diríjase a EJB y elija Session Bean (EJB 3.x) (Bean de sesión [EJB 3.x]). JB183-EAP7.0-es-2-20180124 89 Capítulo 3. Creación de Enterprise Java Beans Figura 3.1: Crear un nuevo EJB en JBDS 3. 90 Proporcione el nombre del paquete, así como el nombre de la clase para la clase EJB. Especifique también el estado y haga clic en Next (Siguiente). JB183-EAP7.0-es-2-20180124 Generación de un EJB automáticamente mediante JBoss Developer Studio Figura 3.2: Asignarle un nombre a la nueva clase EJB Java en JBDS y definir su estado 4. De manera opcional, especifique un nombre alternativo para utilizar cuando inyecta este EJB, así como el tipo de transacción para el EJB y, luego, haga clic en Finish (Finalizar). JB183-EAP7.0-es-2-20180124 91 Capítulo 3. Creación de Enterprise Java Beans Figura 3.3: Proporcionar un nombre para el EJB y definir el tipo de transacción 5. La nueva clase EJB se abre en la ventana del editor. Figura 3.4: La nueva clase EJB, creada automáticamente. 92 JB183-EAP7.0-es-2-20180124 Conversión de un POJO en un EJB Conversión de un POJO en un EJB Convertir un POJO en un EJB es un proceso simple en el que se anota el POJO con una o más anotaciones definidas en el estándar Java EE y se ejecuta el EJB resultante en el contexto de un servidor de aplicaciones. Tomemos el caso de un POJO de la aplicación To Do List: public class TodoBean { public void addTodo(TodoItem item) { ... } public void findTodo(int id) { ... } public void updateTodo(TodoItem item) { ... } public void deleteTodo(int id) { ... } } El POJO tiene cuatro métodos de negocio para agregar, buscar, actualizar y eliminar ítems pendientes. Para convertir este POJO en un EJB de sesión sin estado, solo debe agregar una anotación @Stateless en el POJO. @Stateless public class TodoBean { ... } Para convertir este POJO en un bean de sesión con estado, agregue la anotación @Stateful: @Stateful public class TodoBean { ... } En ambos casos, el servidor de aplicaciones garantiza automáticamente que los métodos del EJB se ejecuten en un contexto transaccional. Puede anotar el EJB con anotaciones relacionadas con la seguridad y exponer el EJB como un extremo de servicio web al agregar anotaciones de servicios web desde el estándar de Java EE. Para convertir este POJO en un bean de sesión singleton, agregue la anotación @Singleton: @Singleton public class TodoBean { ... } En escenarios donde desea que un bean singleton realice una inicialización antes de iniciar las solicitudes de servicio de cliente, puede agregar la anotación @Startup en la clase singleton para indicarle al contenedor EJB que se requiere esta clase durante la secuencia de JB183-EAP7.0-es-2-20180124 93 Capítulo 3. Creación de Enterprise Java Beans inicialización de la aplicación y que se debe crear primero, antes de que se creen instancias de cualquier otro EJB. Es importante tener en cuenta que la aplicación no podrá iniciarse si cualquier EJB con la anotación @Startup lanza una excepción durante la inicialización. También es posible anotar un método de inicialización con la anotación @PostConstruct, que le indica al contenedor EJB que invoque a ese método de inmediato después de crear instancias del EJB. En el siguiente ejemplo se muestra un EJB que se inicializa para el arranque de la aplicación y utiliza el método init() para definir su estado inicial: @Singleton @Startup public class TodoBean { @PostConstruct public void init() { // do some initialization } ... } Otra diferencia importante entre los POJO y EJB es que, debido a que el contenedor EJB crea instancias de los EJB, estos no pueden usar un constructor que se base en argumentos. Esto se debe a que el contenedor EJB no puede definir adecuadamente estos argumentos cuando crea una instancia del EJB. Por este motivo, si está trabajando para convertir una clase de POJO que actualmente usa un constructor con argumentos en un EJB, necesita encontrar una manera de proporcionar lógica equivalente en un constructor sin argumentos. Si no se proporciona un constructor para una clase de EJB, el contenedor EJB usa el constructor sin argumentos predeterminado proporcionado por JVM. Si una clase de EJB proporciona solo un constructor con argumentos, el contenedor genera un error durante la implementación de la aplicación. Demostración: Conversión de un POJO en un EJB 1. Ejecute el siguiente comando para preparar archivos usados por esta demostración. [student@workstation ~]$ demo convert-ejb setup 2. Inicie JBDS e importe el proyecto convert-ejb. Este proyecto es una aplicación web simple que usa una página JSF respaldada por un EJB con estado y alcance de solicitud para brindar una respuesta aleatoria a una pregunta. 3. Inspeccione el archivo pom.xml y observe la dependencia en la especificación del EJB. <dependency> <groupId>javax.enterprise</groupId> <artifactId>cdi-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.spec.javax.ejb</groupId> 94 JB183-EAP7.0-es-2-20180124 Demostración: Conversión de un POJO en un EJB <artifactId>jboss-ejb-api_3.2_spec</artifactId> <scope>provided</scope> </dependency> Ambas librerías deben utilizar las anotaciones de CDI y EJB, pero se pueden marcar como proporcionadas debido a que están ubicadas en el servidor de aplicaciones y están disponibles en el tiempo de ejecución sin estar comprimidas en el paquete WAR. 4. Revise la página JSF src/main/web-app/index.xhtml, que define la vista que le aparece al usuario en su explorador. <ui:define name="content"> <h1>Magic 8 Ball web app</h1> <br class="clear"/> <h:form id="form"> <p class="input"> <h:outputLabel value="Enter your question:" for="question" /> <h:inputText value="">#{eightBall.question}" id="question" required="true" requiredMessage="Question is required"/> </p> <br class="clear"/> <br class="clear"/> <p class="input"> <h:commandButton action="">#{eightBall.answerQuestion()}" value="Submit" styleClass="btn" /> </p> <br class="clear"/> <br class="clear"/> <h:messages styleClass="messages"/> </h:form> </ui:define> JSF usa lenguaje de expresión (EL) para conectar elementos en la IU con métodos de un EJB. Por ejemplo, la expresión de EL #{eightBall.question} usa automáticamente getter y setter para el campo question (pregunta) en la clase de EJB EightBall.java. Otro ejemplo de EL es la expresión #{eightBall.answerQuestion()}, que asigna el método answerQuestion() en la clase de EJB EightBall. 5. Actualice la clase de bean administrado EightBall utilizada por la página JSF. //TODO Make @RequestScoped //TODO Name "eightBall" public class EightBall { private String question; //TODO Inject this EJB Magic8BallBean eightBallEJB; public String ask() { return eightBallEJB.answerQuestion(question); } public void answerQuestion() { String response = ask(); FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(response)); } JB183-EAP7.0-es-2-20180124 95 Capítulo 3. Creación de Enterprise Java Beans public String getQuestion() { return question; } public void setQuestion(String question) { this.question = question; } } Este bean es @RequestScoped debido a que contiene el valor actual de la variable question, que cambia con cada envío de solicitud o formulario. Actualice las anotaciones del nivel de la clase para incluir las anotaciones @RequestScoped y @Named("eightBall") para convertir la clase EightBall en un EJB que esté disponible para ser utilizado por la página JSF. @RequestScoped @Named("eightBall") public class EightBall { Incluya una anotación @EJB para inyectar Magic8BallBean en este bean. @EJB Magic8BallBean eightBallEJB; 6. Actualice la clase Magic8BallBean para que sea un EJB sin estado y pueda ser usado por el EJB EightBall. //TODO Make this a stateless EJB public class Magic8BallBean { public String answerQuestion(String question) { String[] answers = new String[10]; answers[0] = "It is decidedly so."; answers[1] = "Ask again later."; answers[2] = "My reply is no."; answers[3] = "Cannot predict now."; answers[4] = "Don't count on it."; answers[5] = "As I see it, yes."; answers[6] = "Signs point to yes."; answers[7] = "My sources say no."; answers[8] = "Yes."; answers[9] = "No."; String answer = answers[ThreadLocalRandom.current().nextInt(0, 10)]; // respond back with Question: {question} | Answer: {answer}. return "Question: " + question + " | Answer: " + answer; } } Actualice la anotación del nivel de la clase para incluir @Stateless. Esto hace que el EJB esté disponible para la inyección. //TODO Make this a stateless EJB 96 JB183-EAP7.0-es-2-20180124 Demostración: Conversión de un POJO en un EJB @Stateless public class Magic8BallBean { 7. Inicie el servidor JBoss EAP local dentro de JBDS. En la pestaña Servers (Servidores) en el panel inferior de JBDS, haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y vea el mensaje iniciado: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started Se debe encender el servidor para poder ejecutar las pruebas de Arquillian debido a que requieren el contenedor EJB proporcionado por JBoss EAP. 8. Ejecute la prueba JUnit test/java/com/redhat/training/ejb/EJBTest.java proporcionada para verificar que la inyección del bean esté funcionando adecuadamente; para ello, haga clic con el botón derecho en el archivo EJBTest.java y en Run As (Ejecutar como) y, luego, haga clic en JUnit Test (Prueba de JUnit). @RunWith(Arquillian.class) public class EJBTest { @Inject private EightBall eightBall; @Deployment public static WebArchive createDeployment() { return ShrinkWrap.create(WebArchive.class,"convert-ejbtest.war").addClass(EightBall.class).addClass(Magic8BallBean.class) .addAsManifestResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml")); } @Test public void testEightBallEJB() { eightBall.setQuestion("Is it going to rain today?"); Assert.assertNotNull(eightBall.ask()); } } La clase de la prueba usa Arquillian para ejecutarse en el servidor. Esto es necesario porque se requiere un contenedor para que CDI funcione adecuadamente. Asegúrese de que se supere la prueba para verificar que el bean de respaldo y el EJB se hayan inyectado correctamente. 9. Implemente la aplicación en el servidor JBoss EAP local y pruébela en un explorador. En una ventana de terminal, ejecute el siguiente comando: [student@workstation ~]$ cd /home/student/JB183/labs/convert-ejb [student@workstation convert-ejb]$ mvn wildfly:deploy Abra la siguiente URL en su explorador http://localhost:8080/convert-ejb, pruebe la aplicación y asegúrese de que responda preguntas correctamente. JB183-EAP7.0-es-2-20180124 97 Capítulo 3. Creación de Enterprise Java Beans 10. Anule la implementación de la aplicación y detenga el servidor. [student@workstation convert-ejb]$ mvn wildfly:undeploy Referencias Para obtener más información, consulte el capítulo Beans de sesión y el capítulo Beans controlados por mensajes (MDB) de la Guía de desarrollo para Red Hat JBoss EAP: https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/7.0/ 98 JB183-EAP7.0-es-2-20180124 Ejercicio guiado: Creación de un EJB sin estado Ejercicio guiado: Creación de un EJB sin estado En este ejercicio, creará un EJB sin estado que se invocará y los resultados se mostrarán en una página web. Resultados Deberá poder implementar un EJB sin estado que pueda invocarse desde un bean administrado por JSF. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab stateless-ejb setup Pasos 1. Abra JBDS e importe el proyecto de Maven. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta stateless-ejb y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Explore el archivo pom.xml del proyecto; para ello, expanda el ítem stateless-ejb en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y haga doble clic en el archivo pom.xml. 2.1. Haga clic en la pestaña Overview (Descripción general) en la ventana principal del editor. En esta pestaña se muestra una vista general del proyecto, y los cambios que se realizaron en esta ventana se aplicarán en la sección adecuada del archivo pom.xml. JB183-EAP7.0-es-2-20180124 99 Capítulo 3. Creación de Enterprise Java Beans 2.2. Haga clic en la pestaña Dependencies (Dependencias) para ver las dependencias (librerías, marcos [frameworks] y módulos de los que depende este proyecto) del proyecto. 2.3. Haga clic en la pestaña pom.xml para ver el texto completo del archivo pom.xml. Observe que la API del EJB se declaró como una dependencia con alcance proporcionado. Esto se debe a que JBoss EAP implementa el perfil Java EE completo y, por lo tanto, proporciona las librerías de EJB necesarias en el tiempo de ejecución. <dependency> <groupId>org.jboss.spec.javax.ejb</groupId> <artifactId>jboss-ejb-api_3.2_spec</artifactId> <scope>provided</scope> </dependency> Esto también le indica a Maven que no comprima estas librerías en el archivo WAR final. 3. Explore el código fuente de la aplicación. 3.1. Revise la página JSF que invoca el EJB; para ello, expanda el ítem stateless-ejb en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Expanda las carpetas stateless-ejb > src > main > webapp y haga doble clic en el archivo index.xhtml. <h:form id="form"> <p class="input"> <h:outputLabel value="Enter your name:" for="name" /> <h:inputText value="#{hello.name}" id="name" required="true" requiredMessage="Name is required"/> </p> <br class="clear"/> <br class="clear"/> <p class="input"> <h:commandButton action="#{hello.sayHello()}" value="Submit" styleClass="btn" /> </p> <br class="clear"/> <br class="clear"/> <h:messages styleClass="messages"/> </h:form> El valor de Lenguaje de expresión (Expression Language, EL) #{hello.sayHello()} se invoca cuando se envía el formulario web. nota Para ver la fuente real del archivo index.xhtml, haga clic en la pestaña Source (Fuente). 3.2. Explore el archivo Java Hello.java. 100 JB183-EAP7.0-es-2-20180124 En el ítem stateless-ejb expandido de la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione stateless-ejb > Java Resources (Recursos de Java) > src/main/java > com.redhat.training.ui y expándalo. Haga doble clic en el archivo Hello.java. Observe que el EJB sin estado se inyecta mediante la anotación @EJB. @EJB private HelloBean helloEJB; 3.3. Explore el archivo Java HelloBean.java. En el ítem stateless-ejb expandido de la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione stateless-ejb > Java Resources (Recursos de Java) > src/main/java > com.redhat.training.ejb y expándalo. Haga doble clic en el archivo HelloBean.java. public class HelloBean { public String sayHello(String name) { // respond back with Hello, {name}. return "Hello, " + name; } } Este bean define el método público sayHello, que repite una cadena que se envía como entrada. 4. Inicie EAP. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS. Se debe haber agregado el servidor JBoss EAP en un trabajo de laboratorio anterior. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde de inicio para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y vea el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 5. Ejecute la prueba de JUnit e inspeccione el resultado. 5.1. Revise la clase de prueba EJBTest.java En el ítem stateless-ejb expandido de la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione stateless-ejb > Java Resources (Recursos de Java) > src/test/java > com.redhat.training.ejb y haga doble clic en el archivo EJBTest.java. ... @RunWith(Arquillian.class) public class EJBTest { @Inject JB183-EAP7.0-es-2-20180124 101 Capítulo 3. Creación de Enterprise Java Beans private Hello hello; @Deployment public static WebArchive createDeployment() { return ShrinkWrap.create(WebArchive.class,"stateless-ejbtest.war").addClass(HelloBean.class).addClass(Hello.class) .addAsManifestResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml")); } @Test public void testHelloEJB() { hello.setName("John Doe"); String result = hello.greet(); assertEquals("Hello, John Doe", result); } } ... La clase de prueba está anotada con @RunWith(Arquillian.class) para garantizar que JBDS utilice Arquillian Runner para implementar la aplicación en el servidor para realizar pruebas. El bean de respaldo Hello se inyecta con las siguientes líneas: @Inject private Hello hello; 5.2. Haga clic con el botón derecho en el nombre del archivo EJBTest.java en el panel izquierdo, haga clic en la opción Run As (Ejecutar como) y seleccione JUnit Test (Prueba de JUnit) para ejecutar el método de prueba. 5.3. Expanda el panel JUnit al hacer doble clic en la pestaña JUnit. Observe que la prueba falló debido a una NameNotFoundException. Se produce una NameNotFoundException debido a que nunca se creó una instancia de la clase EJB. Para corregirlo, la clase HelloBean debe anotarse con una anotación @Stateless para que la clase se convierta en un EJB a fin de que pueda inyectarse y pueda crearse una instancia de ella. 6. Actualice HelloBean para que se convierta en un EJB sin estado. 6.1. Actualice HelloBean con la anotación @Stateless: import javax.ejb.Stateless; @Stateless public class HelloBean { public String sayHello(String name) { // respond back with Hello, {name}. return "Hello, " + name; } } 102 JB183-EAP7.0-es-2-20180124 6.2. Presione Ctrl+S para guardar los cambios. 7. Vuelva a ejecutar la prueba de unidad y asegúrese de que la prueba sea exitosa. 7.1. Haga clic con el botón derecho en el nombre del archivo EJBTest.java en el panel izquierdo, haga clic en la opción Run As (Ejecutar como) y seleccione JUnit Test (Prueba de JUnit) para volver a ejecutar la prueba. 7.2. Observe el servidor Console (Consola) en JBDS. Los siguientes mensajes confirman referencias JNDI en EAP para el EJB de sesión sin estado: INFO [org.jboss.as.ejb3.deployment] (MSC service thread 1-3) WFLYEJB0473: JNDI bindings for session bean named 'HelloBean' in deployment unit 'deployment "test.war"' are as follows: java:global/test/HelloBean!com.redhat.training.ejb.HelloBean java:app/test/HelloBean!com.redhat.training.ejb.HelloBean java:module/HelloBean!com.redhat.training.ejb.HelloBean java:global/test/HelloBean java:app/test/HelloBean java:module/HelloBean 7.3. Observe el resultado de la prueba en la pestaña JUnit Test (Prueba de JUnit) en JBDS. Esta vez la prueba fue satisfactoria. Figura 3.5: Resultado satisfactorio de la prueba de JUnit. 8. Implemente la aplicación en JBoss EAP con Maven; para ello, ejecute los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/stateless-ejb [student@workstation stateless-ejb]$ mvn wildfly:deploy Una vez finalizado, verá BUILD SUCCESS como se muestra en el próximo ejemplo: [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] -----------------------------------------------------------------------BUILD SUCCESS -----------------------------------------------------------------------Total time: 17.116 s Finished at: 2016-12-01T07:26:55-05:00 Final Memory: 35M/210M ------------------------------------------------------------------------ También valide la implementación exitosa en el registro del servidor que aparece en la pestaña Console (Consola) en JBDS. Debe aparecer lo siguiente en el registro cuando la aplicación se implementa de manera correcta: JB183-EAP7.0-es-2-20180124 103 Capítulo 3. Creación de Enterprise Java Beans INFO [org.jboss.as.server] (management-handler-thread - 9) WFLYSRV0010: Deployed "stateless-ejb.war" (runtime-name : "stateless-ejb.war") 9. Pruebe la aplicación en un explorador. 9.1. Abra la siguiente URL en un explorador en la máquina virtual workstation: http://localhost:8080/stateless-ejb. Figura 3.6: Página de inicio de la aplicación. 9.2. Ingrese Shadowman en el cuadro de texto denominado Enter your name: (Ingrese su nombre:) y haga clic en Submit (Enviar). La página se actualiza con el mensaje Hello Shadowman: Figura 3.7: Respuesta de la aplicación. 10. Anule la implementación de la aplicación y detenga EAP. 104 JB183-EAP7.0-es-2-20180124 10.1.Ejecute el siguiente comando para anular la implementación de la aplicación: [student@workstation stateless-ejb]$ mvn wildfly:undeploy 10.2.Haga clic con el botón derecho en el proyecto stateless-ejb en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 10.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el ejercicio guiado. JB183-EAP7.0-es-2-20180124 105 Capítulo 3. Creación de Enterprise Java Beans Acceso local y remoto a un EJB Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de acceder a un EJB de manera local y remota. Acceso a los EJB Un EJB es un componente portátil que contiene lógica de negocio que se ejecuta en un servidor de aplicaciones. Los clientes pueden acceder a los EJB de manera local, si el cliente y EJB forman parte de la misma aplicación, o a través de una Interfaz remota, si el EJB se ejecuta de manera remota. Si el cliente y EJB son locales, es decir que se ejecutan dentro del mismo proceso JVM, el cliente puede invocar todos los métodos públicos en el EJB. En los casos donde el EJB es remoto, debe proporcionarse una Interfaz remota, que es una interfaz simple de Java que expone los métodos de negocio de un EJB. La clase de EJB implementa los métodos en la interfaz remota y se ocultan los detalles de la implementación a los clientes. Acceso a EJB locales mediante la anotación @EJB Supongamos que definió un EJB de la siguiente manera: @Stateless public class TodoBean { public void addTodo(TodoItem item) { ... } public void findTodo(int id) { ... } ... } ... } Los clientes pueden invocar métodos en el EJB al inyectar el EJB directamente en el código mediante la anotación @EJB: public class TodoClient { @EJB TodoBean todo; TodoItem item = new TodoItem(); item.setDescription("Buy milk"); item.setStatus("PENDING"); //invoke EJB methods todo.addTodo(item); ... } 106 JB183-EAP7.0-es-2-20180124 Acceso remoto a los EJB Acceso remoto a los EJB En escenarios donde el cliente se ejecuta fuera del contexto de un servidor de aplicaciones Java EE, o si un componente de Java EE que se ejecuta en un servidor de aplicaciones necesita acceder a otro EJB que está implementado en un servidor de aplicaciones remoto, puede usar JNDI para buscar el EJB. Para asegurarse de que los clientes remotos puedan consumir un EJB, debe declarar una interfaz enumerando los métodos de negocio del EJB y hacer que el EJB implemente y anule estos métodos. Por ejemplo, suponiendo que desea brindar un EJB que realiza varias operaciones matemáticas, declare una interfaz y enumere los métodos de la siguiente manera: package com.redhat.training.ejb; public interface Calculator { public int add(int a, int b); public int multiply(int a, int b); ... } Ahora tiene que proporcionar implementaciones concretas de estos métodos en el EJB e indicar que Calculator es la interfaz remota del EJB mediante la anotación @Remote: package com.redhat.training.ejb; @Stateless @Remote(Calculator.class) public class CalculatorBean implements Calculator { @Override public int add(int a, int b) { return a + b; } @Override public int multiply(int a, int b) { return a * b; } ... } ... } Su EJB ahora está listo para ser empaquetado e implementado en un servidor de aplicaciones y puede servir a clientes remotos. Búsqueda de EJB remotos mediante JNDI Los estándares de Java EE han especificado un esquema de búsqueda de JNDI estándar para los clientes que buscan EJB. Tiene el siguiente aspecto: /<application-name>/<module-name>/<bean-name>!<fully-qualified-interface-name> • application-name: El nombre de la aplicación es el nombre del EAR en el que se implementa el EJB (sin la ampliación .ear). Si el JAR del EJB no se implementa en un EAR, JB183-EAP7.0-es-2-20180124 107 Capítulo 3. Creación de Enterprise Java Beans aparece en blanco. El nombre de la aplicación también puede especificarse en el descriptor de la implementación application.xml del EAR. • module-name: Por defecto, el nombre del módulo es el nombre del archivo JAR del EJB (sin el sufijo .jar). El nombre del módulo se puede sobrescribir en el descriptor de la implementación ejb-jar.xml. • bean-name: El nombre del EJB que se invocará (la clase de implementación). • fully-qualified-interface-name: El nombre de la clase completamente calificado de la interfaz remota. Incluya el nombre completo del paquete. Si tenemos en cuenta la lista de códigos que se muestra arriba y suponemos que el EJB está empaquetado dentro de un archivo denominado calculator-ejb.jar, que, a su vez, está empaquetado en un archivo EAR denominado myapp.ear, los clientes pueden buscar el EJB mediante la siguiente cadena de búsqueda: myapp/calculator-ejb/CalculatorBean!com.redhat.training.ejb.Calculator Cuando se implementa un EJB, el servidor de aplicaciones enumera los diferentes referencias de JNDI para el EJB en los registros del servidor. En la siguiente lista se muestran las entradas de JDNI si el EJB está empaquetado e implementado como un archivo JAR, y no como un archivo EAR: INFO [org.jboss.as.ejb3.deployment] (MSC service thread 1-2) WFLYEJB0473: JNDI bindings for session bean named 'CalculatorBean' in deployment unit 'deployment "calculatorejb.jar"' are as follows: java:global/calculator-ejb/CalculatorBean!com.redhat.training.ejb.Calculator java:app/calculator-ejb/CalculatorBean!com.redhat.training.ejb.Calculator java:module/CalculatorBean!com.redhat.training.ejb.Calculator java:global/calculator-ejb/CalculatorBean java:app/calculator-ejb/CalculatorBean java:module/CalculatorBean Un programa cliente JNDI de muestra que utiliza el esquema de nomenclatura de JNDI para buscar los EJB remotos tiene el siguiente aspecto: package com.redhat.training.client; public class CalculatorClient { public static void main(String[] args) throws Exception { String JNDI_URL= "myapp/calculator-ejb/CalculatorBean!com.redhat.training.ejb.Calculator"; try { Context ic = new InitialContext(); Calculator calc = (Calculator) ic.lookup(JNDI_URL); System.out.println("Response from server = " + calc.add(1,2); ... } catch (Exception e) { // handle the exception } } 108 JB183-EAP7.0-es-2-20180124 Búsqueda de EJB remotos mediante JNDI ... } También necesita proporcionar un archivo denominado jndi.properties en la ruta de la clase del programa cliente, con el nombre de host, la dirección IP, el puerto y los detalles de seguridad (si está protegido del acceso remoto) del servidor de aplicaciones remoto donde se está ejecutando el EJB. java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory java.naming.provider.url=http-remoting://10.2.0.15:8080 jboss.naming.client.ejb.context=true InitialContext de la API de JNDI es una construcción genérica estándar de Java EE para realizar búsquedas de componentes implementados en un servidor de aplicaciones. Busca jndi.properties en la ruta de la clase con un conjunto de propiedades. Algunas de las propiedades son comunes a todos los servidores de aplicaciones y algunas son específicas de cada servidor de aplicaciones. Los diferentes servidores de aplicaciones tienen sus propias formas específicas de buscar componentes de EJB de manera óptima, pero no hablaremos sobre esto en el curso. Consulte las referencias que aparecen al final de esta sección para obtener más detalles. Referencias Para obtener más información, consulte el capítulo de EJB de la Guía de desarrollo para Red Hat JBoss EAP: https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/7.0/ JB183-EAP7.0-es-2-20180124 109 Capítulo 3. Creación de Enterprise Java Beans Ejercicio guiado: Acceso remoto a un EJB En este ejercicio, creará una aplicación de línea de comando para acceder a un EJB remoto implementado en JBoss EAP. Resultados Debe poder acceder a un EJB implementado en un servidor de aplicaciones remoto mediante la búsqueda JNDI e invocar sus métodos de negocio. Antes de comenzar Abra una ventana de terminal en la máquina virtual de la estación de trabajo y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab hello-remote setup 1. Abra JBDS e importe los proyectos de Maven. Este ejercicio guiado consta de dos subproyectos de Maven, hello-remote-ejb y hello-remote-client, que están ubicados en la carpeta del proyecto principal en el directorio /home/student/JB183/labs/hello-remote. El proyecto hello-remote-ejb instala un EJB al que se accede de manera remota en JBoss EAP para que esté disponible para clientes externos a través de búsquedas JNDI. El proyecto hello-remote-client es una aplicación de Java SE que accede de manera remota (desde otra JVM) al EJB. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. Importe el proyecto hello-remote-ejb al seleccionarlo desde el menú principal de JBDS File (Archivo) > Import... (Importar…). Desde el cuadro de diálogo Import (Importar), seleccione Maven > Existing Maven Projects (Proyectos de Maven existentes) y haga clic en Next > (Siguiente >). Haga clic en Browse... (Explorar…), elija /home/student/JB183/labs/hello-remote/ ejb y haga clic en OK (Aceptar) para elegir el proyecto de Maven. Haga clic en Finish (Finalizar) para completar el proceso de importación en JBDS. Una vez completo, se enumera un nuevo proyecto nombrado hello-remote-ejb en la vista Project Explorer (Explorador de proyectos). 1.3. Importe el proyecto hello-remote-client al seleccionarlo desde el menú principal de JBDS File (Archivo) > Import... (Importar…). Desde el cuadro de diálogo Import (Importar), seleccione Maven > Existing Maven Projects (Proyectos de Maven existentes) y haga clic en Next > (Siguiente >). Haga clic en Browse... (Explorar…), elija /home/student/JB183/labs/hello-remote/ client y haga clic en OK (Aceptar) para elegir el proyecto de Maven. Haga clic en Finish (Finalizar) para completar el proceso de importación en JBDS. 110 JB183-EAP7.0-es-2-20180124 Una vez completo, se enumera un nuevo proyecto nombrado hello-remote-client en la vista Project Explorer (Explorador de proyectos). El proyecto hello-remote-client es una aplicación de Java SE que accede de manera remota (desde otra JVM) al EJB. 2. Explore el código fuente del proyecto hello-remote-ejb. 2.1. Revise el archivo pom.xml. Expanda el ítem hello-remote-ejb en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y haga doble clic en el archivo pom.xml. Haga clic en la pestaña pom.xml para ver el texto completo del archivo pom.xml. Observe el uso de maven-ejb-plugin para empaquetar este EJB. <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-ejb-plugin</artifactId> <version>${version.ejb.plugin}</version> <configuration> <ejbVersion>3.1</ejbVersion> <generateClient>true</generateClient> </configuration> </plugin> Tenga en cuenta que el empaquetado se declaró como ejb. Esto le indica a Maven cómo empaquetar el artefacto implementable final. <packaging>ejb</packaging> 2.2. Explore el archivo HelloRemote.java de la interfaz empresarial. En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, seleccione src/main/java > com.redhat.training.ejb y haga doble clic en HelloRemote.java para ver el archivo. package com.redhat.training.ejb; public interface HelloRemote { public String sayHello(String name); } Observe que esta es una interfaz simple de Java con un método público sayHello que toma un argumento name de cadena y devuelve una cadena. Cuando trabaja con EJB, es común utilizar interfaces para definir los métodos que están disponibles, independientemente de la implementación. 2.3. Explore el archivo HelloBean.java de la clase de implementación. JB183-EAP7.0-es-2-20180124 111 Capítulo 3. Creación de Enterprise Java Beans En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, seleccione src/main/java > com.redhat.training.ejb y haga doble clic en HelloBean.java para ver el archivo. @Stateless public class HelloBean implements HelloRemote { public String sayHello(String name) { // respond back with "Hello, {name}". return "Hello, " + name; } } Observe que esta clase de EJB implementa el método sayHello de la interfaz HelloRemote y observe la anotación @Stateless que marca esta clase como un EJB sin estado. 3. Seleccione la pestaña Servers (Servidores) en JBDS para iniciar EAP. Haga clic con el botón derecho en la entrada del servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en la opción Start (Iniciar) verde para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y vea el mensaje iniciado: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 4. Compile e implemente el EJB en JBoss EAP, y observe las referencia de JNDI para el EJB. 4.1. Abra un nuevo terminal y cambie a la carpeta /home/student/JB183/labs/ hello-remote/ejb/. [student@workstation ~]$ cd /home/student/JB183/labs/hello-remote/ejb Compile e implemente el EJB en JBoss EAP al ejecutar el siguiente comando: [student@workstation ejb]$ mvn clean wildfly:deploy La compilación debe ejecutarse correctamente y usted debe ver la siguiente salida: [student@workstation ejb]$ mvn clean wildfly:deploy [INFO] Scanning for projects... [INFO] [INFO] -----------------------------------------------------------------------[INFO] Building hello-remote-ejb 1.0 [INFO] -----------------------------------------------------------------------[INFO] [INFO] <<< wildfly-maven-plugin:1.0.2.Final:deploy (default-cli) < package @ hello-remote-ejb <<< ... [INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ 112 JB183-EAP7.0-es-2-20180124 4.2. Dentro de JBDS, observe la pestaña Console (Consola) y verifique que la implementación sea correcta. Observe las referencias de JNDI para su bean de sesión sin estado: JNDI bindings for session bean named 'HelloBean' in deployment unit 'deployment "hello-ejb-remote.jar"' are as follows: java:global/hello-ejb-remote/HelloBean!com.redhat.training.ejb.HelloRemote java:app/hello-ejb-remote/HelloBean!com.redhat.training.ejb.HelloRemote java:module/HelloBean!com.redhat.training.ejb.HelloRemote java:global/hello-ejb-remote/HelloBean java:app/hello-ejb-remote/HelloBean java:module/HelloBean JBoss EAP requiere que los EJB estén enlazados en el espacio de nombre java:jboss/exported/* para permitirles a los clientes externos buscar e invocar el EJB. Observe que no hay referencias de JNDI "exportadas". Necesita proporcionarle una interfaz remota al EJB para que esté enlazado en este espacio de nombre. 5. Edite la clase de implementación del proyecto de EJB HelloBean.java para permitir las búsquedas de JNDI remotas y volver a implementar la aplicación. 5.1. Edite la clase de implementación HelloBean.java para permitir las búsquedas de JNDI remotas. Agregue la anotación @Remote en su clase de implementación y guarde el archivo. import javax.ejb.Remote; import javax.ejb.Stateless; @Stateless @Remote(HelloRemote.class) public class HelloBean implements HelloRemote { public String sayHello(String name) { // respond back with "Hello, {name}". return "Hello, " + name; } } 5.2. Presione Ctrl+S para guardar sus cambios. 5.3. Ejecute el siguiente comando para volver a implementar el EJB en EAP: [student@workstation ejb]$ mvn clean wildfly:deploy 5.4. Observe las referencias de JNDI nuevamente. Esta vez debe ver la referencia de JNDI exportada en la pestaña Console (Consola) de JBDS: JNDI bindings for session bean named 'HelloBean' in deployment unit 'deployment "hello-ejb-remote.jar"' are as follows: java:global/hello-ejb-remote/HelloBean!com.redhat.training.ejb.HelloRemote ... JB183-EAP7.0-es-2-20180124 113 Capítulo 3. Creación de Enterprise Java Beans java:jboss/exported/hello-ejb-remote/HelloBean! com.redhat.training.ejb.HelloRemote ... 6. Instale el artefacto hello-remote-ejb en el repositorio local mediante Maven para que esté disponible para el proyecto cliente durante la compilación: [student@workstation ejb]$ cd /home/student/JB183/labs/hello-remote/ejb [student@workstation ejb]$ mvn install La compilación debe realizarse correctamente y debe recibir un mensaje de compilación correcta. [student@workstation ejb]$ mvn install [INFO] -----------------------------------------------------------------------[INFO] Building hello-remote-ejb 1.0 [INFO] -----------------------------------------------------------------------... [INFO] --- maven-install-plugin:2.4:install (default-install) @ hello-remote-ejb --[INFO] Installing /home/student/JB183/labs/hello-remote/ejb/target/hello-ejbremote.jar to /home/student/.m2/repository/com/redhat/training/hello-remote-ejb/1.0/ hello-remote-ejb-1.0.jar [INFO] Installing /home/student/JB183/labs/hello-remote/ejb/pom.xml to /home/ student/.m2/repository/com/redhat/training/hello-remote-ejb/1.0/hello-remoteejb-1.0.pom [INFO] Installing /home/student/JB183/labs/hello-remote/ejb/target/hello-ejb-remoteclient.jar to /home/student/.m2/repository/com/redhat/training/hello-remote-ejb/1.0/ hello-remote-ejb-1.0-client.jar [INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ 7. Explore el código fuente del proyecto hello-client y actualícelo para buscar HelloBean mediante JNDI. 7.1. Expanda el ítem hello-client en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y haga doble clic en el archivo pom.xml. Haga clic en la pestaña pom.xml para ver el texto completo del archivo pom.xml. Observe la dependencia en el artefacto de maven hello-remote-ejb que instaló anteriormente. <dependency> <groupId>com.redhat.training</groupId> <artifactId>hello-remote-ejb</artifactId> <type>ejb-client</type> <version>1.0</version> </dependency> El tipo de dependencia es ejb-client. Esto le indica a Maven que este artefacto es un cliente para los EJB definidos en el artefacto hello-remote-ejb, que se utiliza para la compilación de códigos. 7.2. Explore el archivo HelloClient.java. 114 JB183-EAP7.0-es-2-20180124 En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, seleccione src/main/java > com.redhat.training.client y haga doble clic en HelloClient.java para ver el archivo. public class HelloClient { public static void main(String[] args) throws Exception { //TODO Update JNDI address for the HelloBean String uriJNDI = ""; try { Context ic = new InitialContext(); // Lookup the remote EJB HelloRemote hello = (HelloRemote) ic.lookup(uriJNDI); // Invoke the sayHello method System.out.println("Response from server = " + hello.sayHello("Shadowman")); } catch (NamingException ex) { ex.printStackTrace(); } } } 7.3. Corrija el código HelloClient para que pueda invocar el EJB remoto al especificar una cadena de búsqueda de JNDI para la creación de instancias InitialContext. Edite el archivo HelloClient.java y modifique la variable uriJNDI para especificar la cadena de búsqueda de JNDI que será utilizada por la creación de instancias InitialContext. Defina el nombre de JNDI para que coincida con lo siguiente: String uriJNDI = "hello-ejb-remote/HelloBean!com.redhat.training.ejb.HelloRemote"; 7.4. En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, seleccione src/main/resources y haga doble clic en jndi.properties para ver el archivo en la pestaña principal. Haga clic en la pestaña Source (Fuente) para ver y editar el archivo de propiedades. 7.5. Actualice el archivo jndi.properties para usar http-remoting para acceder al EJB que se ejecuta en el servidor de JBoss EAP local. Defina la propiedad java.naming.provider.url con el valor httpremoting://127.0.0.1:8080, como se muestra en el siguiente ejemplo: java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory java.naming.provider.url=http-remoting://127.0.0.1:8080 jboss.naming.client.ejb.context=true El cliente ahora se está conectando a la instancia del servidor de JBoss EAP local que se ejecuta en el puerto 8080. Presione Ctrl+S para guardar sus cambios. 8. Abra una ventana de terminal y diríjase al directorio /home/student/JB183/labs/ hello-remote/client. JB183-EAP7.0-es-2-20180124 115 Capítulo 3. Creación de Enterprise Java Beans [student@workstation ~]$ cd /home/student/JB183/labs/hello-remote/client/ Ejecute el cliente mediante Maven: [student@workstation client]$ mvn package exec:java Debe ver la siguiente salida: [student@workstation client]$ mvn package exec:java [INFO] Scanning for projects... [INFO] [INFO] -----------------------------------------------------------------------[INFO] Building hello-client 1.0 [INFO] -----------------------------------------------------------------------... [INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ hello-client --... Channel ID b334091e (outbound) of Remoting connection 63a791c4 to /127.0.0.1:8080 INFO: JBoss EJB Client version 2.1.6.Final-redhat-1 ... Response from server = Hello, Shadowman ... Verá la respuesta Hello, Shadowman del EJB. Esto verifica que el EJB remoto se invocó correctamente. 9. Limpie el ejercicio y detenga EAP. 9.1. Anule la implementación del EJB mediante el siguiente comando Maven: [student@workstation ejb]$ mvn wildfly:undeploy 9.2. Haga clic con el botón derecho en el proyecto hello-remote-ejb en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 9.3. Haga clic con el botón derecho en el proyecto hello-client en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrar el proyecto. 9.4. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el ejercicio guiado. 116 JB183-EAP7.0-es-2-20180124 Descripción del ciclo de vida de un EJB Descripción del ciclo de vida de un EJB Objetivo Tras finalizar esta sección, los estudiantes deberán ser capaces de describir el ciclo de vida de los EJB Compresión del ciclo de vida de un EJB Los componentes de un EJB en una aplicación se ejecutan en el contexto de un contenedor dentro de un servidor de aplicaciones. El contenedor es responsable de administrar el ciclo de vida (creación, ejecución y destrucción) de los EJB. Cada uno de los diferentes tipos de EJB (sin estado, con estado, singleton, MDB) tiene su propio ciclo de vida. Descripción del ciclo de vida del bean de sesión con estado Un bean de sesión con estado tiene tres estados diferentes en su ciclo de vida: • Does Not Exist (No existe): El EJB con estado no se crea y no existe en la memoria del servidor de aplicaciones. • Ready (Listo): El EJB con estado (objeto) se crea en la memoria del servidor de aplicaciones por una llamada JNDI o inyección CDI y está listo para que los clientes invoquen sus métodos de negocio. • Passivated (Pasivado): Dado que un EJB con estado tiene un estado de objeto que perdura durante varias llamadas del cliente, el servidor de aplicaciones puede decidir pasivar (desactivar) el EJB en un almacenamiento secundario para optimizar el consumo de la memoria. Activará el EJB para que tenga el estado Listo cuando un cliente invoque cualquier método en el EJB. El desarrollador no tiene un control directo de activación y pasivación, y el servidor de aplicaciones lo maneja de forma transparente basado en determinados algoritmos. JB183-EAP7.0-es-2-20180124 117 Capítulo 3. Creación de Enterprise Java Beans Figura 3.8: Revisión del ciclo de vida de un bean de sesión con estado Descripción del ciclo de vida del bean de sesión sin estado Debido a que un bean de sesión no tiene estado y nunca se pasiva, tiene solo dos estados diferentes durante su ciclo de vida: • Does Not Exist (No existe): El EJB sin estado no se crea y no existe en la memoria del servidor de aplicaciones. • Ready (Listo): El EJB sin estado (objeto) se crea en la memoria del servidor de aplicaciones por una llamada JNDI o inyección CDI y está listo para que los clientes invoquen sus métodos de negocio. Por lo general, el servidor de aplicaciones crea y mantiene un conjunto (pool) de instancias EJB de sesión sin estado en la memoria para optimizar el rendimiento. Cada vez que un cliente invoca métodos de negocio en el EJB, el servidor de aplicaciones asigna un bean del conjunto (pool). Una vez que se ejecuta el método, se devuelve el bean al conjunto (pool). Este proceso es transparente para el desarrollador, y el servidor de aplicaciones lo maneja automáticamente. El tamaño del conjunto (pool) se puede configurar mediante descriptores de implementación y en los archivos de configuración del servidor de aplicaciones. 118 JB183-EAP7.0-es-2-20180124 Descripción del ciclo de vida del bean de sesión singleton Figura 3.9: Revisión del ciclo de vida de un bean de sesión sin estado Descripción del ciclo de vida del bean de sesión singleton De manera similar a un bean de sesión sin estado, un bean de sesión singleton tiene dos estados diferentes en su ciclo de vida: • Does Not Exist (No existe): El singleton no se crea y no existe en la memoria del servidor de aplicaciones. • Ready (Listo): El EJB singleton (un único objeto) se crea en la memoria del servidor de aplicaciones en el inicio, o a través de una inyección de CDI, y está listo para que los clientes invoquen sus métodos de negocio. Debido a que solo hay una instancia del EJB durante su ciclo de vida, no existe el concepto de un conjunto (pool). Las políticas de acceso concurrentes en el bean pueden ser controladas por descriptores de implementación o anotaciones de nivel del código. Revisión de las anotaciones del ciclo de vida de un bean Los estándares Java EE para EJB especifican el concepto de Devoluciones de llamadas (Callbacks). Una devolución de llamada (callback) es un mecanismo mediante el cual los eventos del ciclo de vida de un bean empresarial se pueden interceptar y el desarrollador puede ejecutar códigos personalizados durante estos eventos. El desarrollador anota métodos en el bean con un conjunto de anotaciones que, luego, el servidor de aplicaciones puede invocar y ejecutar. En la siguiente tabla se indican los diferentes tipos de EJB y los métodos del ciclo de vida y las anotaciones disponibles para cada uno: JB183-EAP7.0-es-2-20180124 119 Capítulo 3. Creación de Enterprise Java Beans Tipo de bean Anotación Descripción Bean de sesión con estado @PostConstruct se invoca cuando se crea un bean por primera vez. @PreDestroy se invoca cuando se destruye un bean. @PostActivate se invoca cuando se carga un bean para utilizarlo después de la activación. @PrePassivate se invoca cuando un bean está a punto de pasivarse. Bean de sesión sin estado @PostConstruct se invoca cuando se crea un bean por primera vez. @PreDestroy se invoca cuando se destruye o elimina un bean del conjunto (pool) de beans. Bean de sesión singleton @PostConstruct se invoca cuando se crea un bean por primera vez. @PreDestroy se invoca cuando se destruye un bean. @Startup El servidor de aplicaciones crea instancias del singleton en el inicio. 120 JB183-EAP7.0-es-2-20180124 Cuestionario: El ciclo de vida de un EJB Cuestionario: El ciclo de vida de un EJB Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuáles dos de los siguientes enunciados acerca del ciclo de vida de un EJB de sesión con estado son correctos? (Elija dos opciones). a. b. c. d. e. 2. ¿Cuáles dos de los siguientes enunciados acerca del ciclo de vida de un EJB de sesión sin estado son correctos? (Elija dos opciones). a. b. c. d. e. 3. El desarrollador tiene control directo sobre la pasivación o activación de EJB sin estado. El conjunto (pool) de beans de EJB sin estado tiene tamaño fijo y no puede cambiarse. Por lo general, el servidor de aplicaciones mantiene un conjunto (pool) de EJB sin estado. El tamaño del conjunto (pool) de beans EJB sin estado se puede cambiar mediante descriptores de implementación y archivos de configuración del servidor de aplicaciones. Los EJB sin estado que se encuentran en el estado Listo pueden ser invocados solo por clientes remotos a través de JNDI. ¿Cuáles de los siguientes enunciados acerca del ciclo de vida de un EJB singleton son correctos? a. b. c. d. 4. Los clientes pueden invocar métodos en un bean de sesión con estado que se encuentra en el estado Pasivado. Los clientes pueden invocar métodos en un bean de sesión con estado que se encuentra en el estado Listo. El desarrollador tiene control directo sobre la pasivación y activación del EJB con estado a través de la invocación de métodos directamente en el bean. El desarrollador no tiene control directo sobre la pasivación o activación de los EJB con estado. El servidor de aplicaciones administra la activación y pasivación de los EJB con estado. Los EJB con estado que se encuentran en el estado Listo pueden ser invocados solo por clientes remotos a través de JNDI. El servidor de aplicaciones mantiene un conjunto (pool) de EJB singleton de los cuales se crean instancias en el inicio. Los EJB singleton se pueden pasivar y activar como los EJB con estado. El servidor de aplicaciones crea y mantiene una única instancia del EJB singleton en el inicio si el EJB singleton tiene la anotación @Startup. El tamaño de conjunto (pool) de los EJB singleton se puede controlar mediante descriptores de implementación y archivos de configuración del servidor de aplicaciones. ¿Cuáles tres de los siguientes enunciados acerca de las anotaciones del ciclo de vida de un bean son correctos? (Elija tres opciones). JB183-EAP7.0-es-2-20180124 121 Capítulo 3. Creación de Enterprise Java Beans a. b. c. d. e. f. 122 Los métodos de un EJB de sesión con estado se pueden anotar con @PrePassivate. Los métodos de un EJB de sesión sin estado se pueden anotar con @PrePassivate. Un EJB singleton se puede anotar con @PostConstruct. La anotación @Initialize en un EJB singleton indica que el servidor de aplicaciones creará instancias del singleton en el inicio. Los métodos de un EJB de sesión con estado se pueden anotar con @PreDestroy. Los métodos de un EJB de sesión sin estado se pueden anotar con @PostActivate. JB183-EAP7.0-es-2-20180124 Solución Solución Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuáles dos de los siguientes enunciados acerca del ciclo de vida de un EJB de sesión con estado son correctos? (Elija dos opciones). a. b. c. d. e. 2. ¿Cuáles dos de los siguientes enunciados acerca del ciclo de vida de un EJB de sesión sin estado son correctos? (Elija dos opciones). a. b. c. d. e. 3. El desarrollador tiene control directo sobre la pasivación o activación de EJB sin estado. El conjunto (pool) de beans de EJB sin estado tiene tamaño fijo y no puede cambiarse. Por lo general, el servidor de aplicaciones mantiene un conjunto (pool) de EJB sin estado. El tamaño del conjunto (pool) de beans EJB sin estado se puede cambiar mediante descriptores de implementación y archivos de configuración del servidor de aplicaciones. Los EJB sin estado que se encuentran en el estado Listo pueden ser invocados solo por clientes remotos a través de JNDI. ¿Cuáles de los siguientes enunciados acerca del ciclo de vida de un EJB singleton son correctos? a. b. c. d. 4. Los clientes pueden invocar métodos en un bean de sesión con estado que se encuentra en el estado Pasivado. Los clientes pueden invocar métodos en un bean de sesión con estado que se encuentra en el estado Listo. El desarrollador tiene control directo sobre la pasivación y activación del EJB con estado a través de la invocación de métodos directamente en el bean. El desarrollador no tiene control directo sobre la pasivación o activación de los EJB con estado. El servidor de aplicaciones administra la activación y pasivación de los EJB con estado. Los EJB con estado que se encuentran en el estado Listo pueden ser invocados solo por clientes remotos a través de JNDI. El servidor de aplicaciones mantiene un conjunto (pool) de EJB singleton de los cuales se crean instancias en el inicio. Los EJB singleton se pueden pasivar y activar como los EJB con estado. El servidor de aplicaciones crea y mantiene una única instancia del EJB singleton en el inicio si el EJB singleton tiene la anotación @Startup. El tamaño de conjunto (pool) de los EJB singleton se puede controlar mediante descriptores de implementación y archivos de configuración del servidor de aplicaciones. ¿Cuáles tres de los siguientes enunciados acerca de las anotaciones del ciclo de vida de un bean son correctos? (Elija tres opciones). a. Los métodos de un EJB de sesión con estado se pueden anotar con @PrePassivate. JB183-EAP7.0-es-2-20180124 123 Capítulo 3. Creación de Enterprise Java Beans b. c. d. e. f. 124 Los métodos de un EJB de sesión sin estado se pueden anotar con @PrePassivate. Un EJB singleton se puede anotar con @PostConstruct. La anotación @Initialize en un EJB singleton indica que el servidor de aplicaciones creará instancias del singleton en el inicio. Los métodos de un EJB de sesión con estado se pueden anotar con @PreDestroy. Los métodos de un EJB de sesión sin estado se pueden anotar con @PostActivate. JB183-EAP7.0-es-2-20180124 Delimitación de transacciones implícitas y explícitas Delimitación de transacciones implícitas y explícitas Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de describir las transacciones implícitas y explícitas. Comprensión de las transacciones en aplicaciones de Java EE Una aplicación empresarial típica de Java EE almacena y manipula datos en uno o más almacenes de datos persistentes, como un sistema de gestión de bases de datos relacionales (RDBMS). Por lo general, varias aplicaciones a la vez acceden a los datos críticos para la empresa almacenados en estas bases de datos y es fundamental garantizar la integridad de datos. Las Transacciones garantizan que se mantenga la integridad de datos al controlar el acceso concurrente a los datos y garantizan que una transacción de negocio fallida no deje el sistema en un estado inconsistente o inválido. Una Transacción es una serie de acciones que deben ejecutarse como una unidad atómica simple. Sigue un enfoque "a todo o nada", donde las acciones se ejecutan correctamente o no se ejecutan de ningún modo. Figura 3.10: Uso de una transacción para revertir una falla La figura anterior muestra los pasos para una aplicación de gestión de pedidos, donde se debe ejecutar una secuencia de operaciones cuando un cliente realiza un pedido. La transacción comienza con la ejecución del método saveOrder(), que almacena el pedido en una base de datos de Pedidos y, luego invoca el método raisePurchaseOrder(), que establece un pedido de compras en otra base de datos mantenida por el departamento JB183-EAP7.0-es-2-20180124 125 Capítulo 3. Creación de Enterprise Java Beans de finanzas. El flujo se traslada al método updateInventory(), que actualiza la base de datos del inventario y, luego, le envía un correo electrónico al cliente mediante el método sendEmail(). Si todos los métodos de la transacción se ejecutan sin errores ni fallas, la transacción tiene un estado Committed (Confirmado). Si, por ejemplo, se produce un error en el método updateInventory(), la aplicación debe asegurarse de que las acciones de los métodos anteriores que participaron en la transacción (es decir, raisePurchaseOrder() y saveOrder()) se reviertan y de que el estado general del sistema vuelva al estado que tenía al comienzo de la transacción. Esta reversión se conoce como Rollback. El estándar de Java EE especifica la API de transacción Java (JTA) que proporciona gestión de transacciones para las aplicaciones que se ejecutan en un servidor de aplicaciones compatible con el servidor de aplicaciones Java EE. Esta API proporciona una interfaz avanzada para confirmar y restaurar transacciones en las aplicaciones. Por ejemplo, si la API de persistencia Java (JPA) se utiliza junto con JTA, el desarrollador no tiene que escribir y rastrear explícitamente las instrucciones de confirmación y revertir de SQL en la aplicación. La API de JTA se ocupa de estas operaciones de manera independiente de la base de datos. Hay dos maneras diferentes de administrar transacciones en Java EE: • Transacción implícita o administrada por contenedores (CMT): El servidor de aplicaciones administra el límite de la transacción y confirma y restaura automáticamente las transacciones sin que el desarrollador escriba código para administrar transacciones. Esta es la opción predeterminada a menos que el desarrollador la sobrescriba explícitamente. • Transacción explícita o administrada por beans (BMT): El desarrollador administra las transacciones en forma de código en el nivel del bean (en los EJB). El desarrollador es responsable de controlar el límite y alcance de la transacción de manera explícita. Revisión de la semántica de transacciones administradas por contenedores (CMT) En CMT, el servidor de aplicaciones inicia una transacción implícitamente al comienzo de un método EJB y confirma la transacción al final del método, a menos que se produzca un error o una excepción. En ese caso, se restaura la transacción. Las excepciones de tiempo de ejecución desencadenan automáticamente una reversión realizada por el servidor de aplicaciones. La agrupación de transacciones dentro de un método de bean único no está permitida. Un desarrollador puede sobrescribir el comportamiento transaccional predeterminado en el nivel del método con anotaciones denominadas Atributos de transacción. Los EJB que utilizan CMT no deben usar métodos API de JTA que entren en conflicto con el límite y alcance de la transacción del servidor de aplicaciones. Por ejemplo, no se pueden utilizar los métodos JTA, como commit(), setAutoCommit() y rollback(), o las clases JDBC como java.sql.Connection y los métodos commit() y rollback() de javax.jms.Session. Si requiere control explícito sobre el flujo de la transacción, debe usar transacciones administradas por beans. Además, los EJB que usan CMT no deben usar la interfaz javax.transaction.UserTransaction. 126 JB183-EAP7.0-es-2-20180124 Revisión de la semántica de transacciones administradas por contenedores (CMT) Ajuste de los atributos de transacción En CMT, los atributos de transacción controlan el alcance de una transacción y permiten a un desarrollador administrar las transacciones en el nivel del método individual en un EJB. Por ejemplo, observe el siguiente fragmento de código donde un EJB sin estado invoca un método de otro EJB sin estado: @Stateless public class TodoService { @Inject UserService user; public void login(String user, String password) { user.authenticate(user,password); } ... } @Stateless public class UserService { public boolean authenticate(String user, String password) { ... } ... } nota @Inject puede inyectar cualquier bean, incluidos EJB, mientras que @EJB solo puede inyectar EJB. @Inject se describe con mayor detalle en el capítulo titulado Implementación de Contextos e Inyección de dependencia. Los atributos de transacción se pueden usar para controlar el alcance y contexto con los que se ejecutan los métodos de la clase UserService. La especificación de Java EE define seis atributos de transacción. Se ilustran debajo con referencia al fragmento de código que aparece arriba: @TransactionAttribute(TransactionAttributeType.REQUIRED) Si el método login() se está ejecutando dentro de una transacción e invoca el método authenticate() en la clase UserService, authenticate() se ejecuta dentro de la misma transacción. Si no hay una transacción cuando se invoca authenticate(), el servidor de aplicaciones inicia una nueva transacción antes de ejecutar authenticate(). Este es el atributo de transacción predeterminado a menos que se sobrescriba explícitamente con otras anotaciones de atributo de transacción. @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) Si el método login() se está ejecutando dentro de una transacción e invoca el método authenticate() en la clase UserService, el servidor de aplicaciones suspende la transacción e inicia una nueva transacción antes de ejecutar authenticate(). Una vez que authenticate() finaliza la ejecución, el control vuelve a login() y la transacción suspendida se reanuda. Si no hay una transacción cuando se invoca authenticate(), el JB183-EAP7.0-es-2-20180124 127 Capítulo 3. Creación de Enterprise Java Beans servidor de aplicaciones inicia una nueva transacción antes de ejecutar authenticate(). Este atributo garantiza que su método se ejecute siempre con una nueva transacción. @TransactionAttribute(TransactionAttributeType.MANDATORY) Si el método login() se está ejecutando dentro de una transacción e invoca el método authenticate() en la clase UserService, authenticate() se ejecuta dentro de la misma transacción. Si no hay una transacción cuando se invoca authenticate(), el servidor de aplicaciones arroja una TransactionRequiredException. Use este atributo si desea que un método se ejecute siempre en el contexto de la transacción del cliente que realiza la invocación. @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) Si el método login() se está ejecutando dentro de una transacción e invoca el método authenticate() en la clase UserService, el servidor de aplicaciones suspende la transacción y ejecuta authenticate() sin un contexto de transacción. Una vez que authenticate() finaliza la ejecución, el control vuelve a login() y la transacción suspendida se reanuda. Si no hay una transacción cuando se invoca authenticate(), el servidor de aplicaciones no inicia una nueva transacción antes de ejecutar authenticate(). Use este atributo para los métodos que no necesitan transacciones. @TransactionAttribute(TransactionAttributeType.SUPPORTS) Si el método login() se está ejecutando dentro de una transacción e invoca el método authenticate(), authenticate() se ejecuta dentro de la misma transacción. Si no hay una transacción cuando se invoca authenticate(), el servidor de aplicaciones NO inicia una nueva transacción antes de ejecutar authenticate(). @TransactionAttribute(TransactionAttributeType.NEVER) Si el método login() se está ejecutando dentro de una transacción e invoca el método authenticate() en la clase UserService, el servidor de aplicaciones arroja una RemoteException. Si no hay una transacción cuando se invoca authenticate(), el servidor de aplicaciones no inicia una nueva transacción antes de ejecutar authenticate(). Ajuste de los atributos de transacción Los atributos de transacción se declaran al anotar el método o la clase de EJB con una anotación javax.ejb.TransactionAttribute y al establecerlo en una de las constantes de enumeración javax.ejb.TransactionAttributeType. Si anota al EJB con @TransactionAttribute en el nivel de la clase, el atributo especificado se aplica a todos los métodos del EJB. Anotar métodos específicos con @TransactionAttribute aplica el atributo solo a ese método. Las declaraciones de atributo del nivel del método sobrescriben las declaraciones del nivel de la clase. Observe la siguiente clase de EJB con varios métodos: @Stateless public class TodoEJB { @TransactionAttribute(TransactionAttributeType.REQUIRED) public void createTodo(TodoItem item) { ... } @TransactionAttribute(TransactionAttributeType.NEVER) public List<TodoItem> listTodos() { ... 128 JB183-EAP7.0-es-2-20180124 Comprensión de la semántica de las transacciones administradas por beans (BMT) } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void logCreateTodo(TodoItem item) { ... } El método createTodo() se anota con el atributo de transacción @TransactionAttribute(TransactionAttributeType.REQUIRED), y el método listTodos() se anota con el atributo de transacción @TransactionAttribute(TransactionAttributeType.NEVER), debido a que este método realiza únicamente una operación de solo lectura de la base de datos y no inserta, elimina ni actualiza datos. El método logCreateTodo() se anota con el atributo de transacción @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW), que garantiza que este método se ejecute siempre con una nueva transacción, incluso si se invoca desde otro método que se ejecuta con su propio contexto de transacción. Comprensión de la semántica de las transacciones administradas por beans (BMT) Puede utilizar las transacciones administradas por beans (BMT) en escenarios donde necesita control detallado sobre cuándo inicia y finaliza una transacción y cuándo realizar una confirmación y reversión. Puede usar los métodos begin(), commit() y rollback() de la interfaz javax.transaction.UserTransaction para controlar los límites y el alcance de la transacción de manera explícita. En aplicaciones que utilizan BMT, no debe usar los métodos getRollbackOnly() y setRollbackOnly() de la interfaz javax.ejb.EJBContext. En cambio, use los métodos getStatus() y rollback() de la interfaz javax.transaction.UserTransaction. Un EJB de muestra con una transacción administrada por beans tiene el siguiente aspecto: @Stateless @TransactionManagement(TransactionManagementType.BEAN) public class BeanManagedEJB { @Inject private UserTransaction tx; public Integer saveOrder(Order order) { try { tx.begin(); Integer orderId = createOrder(order); raisePurchaseOrder(orderId); sendEMail("Your order # " + orderId + "has been placed successfully..." ); tx.commit(); return orderId; } catch (Exception e) { JB183-EAP7.0-es-2-20180124 129 Capítulo 3. Creación de Enterprise Java Beans tx.rollback(); return null; } ... Esta clase es un EJB sin estado. Marque este EJB como una transacción administrada por beans; para ello, use la anotación @TransactionManagement(TransactionManagementType.BEAN). Inyecte un objeto UserTransaction. Esto se utiliza para iniciar, confirmar y restaurar transacciones en este EJB. Inicie una transacción. Si todos los métodos se ejecutan correctamente sin errores, confirme la transacción. Si se produce una excepción debido a una falla, revierta la transacción. 130 JB183-EAP7.0-es-2-20180124 Ejercicio guiado: Delimitación de transacciones Ejercicio guiado: Delimitación de transacciones En este ejercicio, tomará un EJB sin estado y lo actualizará para utilizar transacciones administradas por beans. Resultados Deberá poder implementar un EJB sin estado que utilice transacciones administradas por beans. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab bean-transactions setup Pasos 1. Abra JBDS e importe el proyecto de Maven. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta beantransactions y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Explore el código fuente de la aplicación. 2.1. Revise la página JSF que invoca el EJB; para ello, expanda el ítem bean-transactions en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Expanda las carpetas bean-transactions > src > main > webapp y haga doble clic en el archivo index.xhtml. El valor de Lenguaje de expresión (Expression Language, EL) #{hello.sayHello()} se invoca cuando se envía el formulario web. 2.2. Revise el bean de respaldo Hello con alcance de solicitud utilizado por la página JSF. JB183-EAP7.0-es-2-20180124 131 Capítulo 3. Creación de Enterprise Java Beans Expanda el ítem bean-transactions en la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione bean-transactions > Java Resources (Recursos de Java) > src/main/java > com.redhat.training.ui y expándalo. Haga doble clic en el archivo Hello.java. Tenga en cuenta que este EJB utiliza CDI para inyectar el EJB PersonService, que es donde se agregará la lógica de la transacción. 2.3. Explore la clase de EJB PersonService.java. En el ítem bean-transactions expandido en la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione bean-transactions > Java Resources (Recursos de Java) > src/main/java > com.redhat.training.service y expándalo. Haga doble clic en el archivo PersonService.java. @Stateless // TODO Add a transaction manage type of 'BEAN' public class PersonService { @PersistenceContext private EntityManager entityManager; // TODO Inject a UserTransaction to be used by this EJB public String hello(String name) { try { // TODO start a new transaction // let's grab the current date and time on the server LocalDateTime today = LocalDateTime.now(); // format it nicely for on-screen display DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm:ss a"); String fdate = today.format(format); // Create a new Person object and persist to database Person p = new Person(); p.setName(name); entityManager.persist(p); // respond back with Hello and convert the name to UPPERCASE. Also, send the // current time on the server. return "Hello " + name.toUpperCase() + "!. " + "Time on the server is: " + fdate; } catch(Exception e) { //TODO roll-back the transaction throw new EJBException(e); } } } Tenga en cuenta que este EJB ya está marcado como @Stateless, pero actualmente no contiene administración de transacciones. El método hello() crea una nueva entrada en la base de datos para cada persona que ingresa un nombre en la IU y devuelve un saludo que contiene la hora y fecha actuales. 132 JB183-EAP7.0-es-2-20180124 3. Actualice PersonService para utilizar transacciones administradas por beans. 3.1. Anote la clase PersonService con la anotación @TransactionManagement(TransactionManagementType.BEAN) para habilitar las transacciones administradas por beans en el EJB: @Stateless @TransactionManagement(TransactionManagementType.BEAN) public class PersonService { ... Esta anotación previene que el contenedor administre transacciones y le permite al EJB administrar las transacciones manualmente. 3.2. Agregue el siguiente código para usar la inyección de recursos para inyectar una instancia de la clase UserTransaction en el EJB para la administración manual de transacciones: public class PersonService { @PersistenceContext private EntityManager entityManager; //TODO Inject a UserTransaction to be used by this EJB @Resource UserTransaction tx; ... La anotación @Resource le indica al contenedor que asigne un nuevo objeto de transacción y lo inyecte en este EJB en el tiempo de ejecución. 3.3. Agregue el siguiente código al EJB para comenzar la transacción: ... public String hello(String name) { try { //TODO start a new transaction tx.begin(); ... Agregue el siguiente código para confirmar la transacción: ... public String hello(String name) { try { // TODO start a new transaction tx.begin(); // let's grab the current date and time on the server LocalDateTime today = LocalDateTime.now(); // format it nicely for on-screen display DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm:ss a"); String fdate = today.format(format); // Create a new Person object and persist to database Person p = new Person(); JB183-EAP7.0-es-2-20180124 133 Capítulo 3. Creación de Enterprise Java Beans p.setName(name); entityManager.persist(p); //TODO commit the transaction tx.commit(); ... Agregue el siguiente código para revertir la transacción en el evento de una excepción: ... public String hello(String name) { try { // TODO start a new transaction tx.begin() // let's grab the current date and time on the server LocalDateTime today = LocalDateTime.now(); // format it nicely for on-screen display DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm:ss a"); String fdate = today.format(format); // Create a new Person object and persist to database Person p = new Person(); p.setName(name); entityManager.persist(p); //TODO commit the transaction tx.commit(); // respond back with Hello and convert the name to UPPERCASE. Also, send the // current time on the server. return "Hello " + name.toUpperCase() + "!. " + "Time on the server is: " + fdate; } catch(Exception e) { //TODO roll-back the transaction tx.rollback(); throw new EJBException(e); } ... Guarde los cambios en el archivo con Ctrl+S. 3.4. Después de agregar la reversión de la transacción, observe que JBDS le informa sobre una excepción no controlada. Pase el mouse sobre la línea tx.rollback() subrayada en rojo y haga clic en Add throws declaration (Agregar declaración de generación). El encabezado del método ahora tiene el siguiente aspecto: public String hello(String name) throws IllegalStateException, SecurityException, SystemException { Guarde los cambios en el archivo con Ctrl+S. 4. Inicie EAP e implemente la aplicación con Maven. 4.1. Haga clic en la pestaña Servers (Servidores) en el panel inferior de JBDS. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el 134 JB183-EAP7.0-es-2-20180124 botón de inicio verde para iniciar el servidor. Observe la Console (Consola) hasta ver el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 4.2. Implemente la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/bean-transactions [student@workstation bean-transactions]$ mvn wildfly:deploy Una vez completado, debe ver BUILD SUCCESS: ... [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] -----------------------------------------------------------------------BUILD SUCCESS -----------------------------------------------------------------------Total time: 17.116 s Finished at: 2017-09-21T07:26:55-05:00 Final Memory: 35M/210M ------------------------------------------------------------------------ 4.3. Valide la implementación en el registro de servidores que se muestra en la pestaña Console (Consola) en JBDS: INFO [org.jboss.as.server] (management-handler-thread - 9) WFLYSRV0010: Deployed "bean-transactions.war" (runtime-name : "bean-transactions.war") 5. Pruebe la aplicación. 5.1. En la máquina virtual workstation, utilice un explorador para ir a la siguiente URL: http://localhost:8080/bean-transactions. 5.2. Ingrese Shadowman en el cuadro de texto denominado Enter your name: (Ingrese su nombre:) y haga clic en Submit (Enviar). La página se actualiza con el mensaje Hello SHADOWMAN!. Time on the server is: Sep 21 2017 04:15:04 PM 6. Anule la implementación de la aplicación y detenga EAP. 6.1. Ejecute los siguientes comandos para anular la implementación de la aplicación: [student@workstation ~]$ cd /home/student/JB183/labs/bean-transactions [student@workstation bean-transactions]$ mvn wildfly:undeploy 6.2. Haga clic con el botón derecho en el proyecto bean-transactions en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrar este proyecto. 6.3. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). JB183-EAP7.0-es-2-20180124 135 Capítulo 3. Creación de Enterprise Java Beans Esto concluye el ejercicio guiado. 136 JB183-EAP7.0-es-2-20180124 Trabajo de laboratorio: Creación de Enterprise Java Beans Trabajo de laboratorio: Creación de Enterprise Java Beans En este trabajo de laboratorio, convertirá una clase de POJO en un EJB en la aplicación To Do List y mostrará una lista de los ítems Todo en una página web. Resultados Deberá poder modificar una aplicación To Do List escrita con anterioridad y convertir una clase POJO en un EJB. La aplicación acepta una lista de cosas por hacer como entrada del usuario, y muestra la lista de ítems Todo en una página web. Antes de comenzar Abra una ventana de terminal en la máquina virtual de la estación de trabajo y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab ejb-assessment setup 1. Abra JBDS e importe el proyecto ejb-assessment ubicado en el directorio /home/ student/JB183/labs/ejb-assessment. 2. Revise la clase Item.java de la carpeta /home/student/JB183/labs/ejbassessment/src/main/java/com/redhat/training/todo/model. Esta clase modela un único ítem Todo en la aplicación. Tiene tres atributos: un id, una descripción y un atributo booleano que indica si la tarea se completó o no. 3. Revise la clase ItemRepository.java de la carpeta /home/student/JB183/labs/ ejb-assessment/src/main/java/com/redhat/training/todo/data. Esta clase simula una base de datos en la memoria y almacena la lista de ítems Todo. Tiene métodos para agregar ítems, ver un único ítem y ver una lista de todos los ítems. Tenga en cuenta que esta clase está anotada con @ApplicationScoped, que significa que los objetos de esta clase se mantienen en el alcance (activos) siempre y cuando la aplicación esté implementada y en ejecución en el servidor de aplicaciones. Tenga en cuenta que el método seedTodoList() se anotó con @PostConstruct. Este método completa la lista Todo con tres ítems una vez que se inicializa la clase. 4. Explore la clase ItemService.java, que es un POJO simple que contiene métodos para agregar ítems, ver un ítem Todo y enumerar todos los ítems Todo. Este archivo está ubicado en la carpeta /home/student/JB183/labs/ejb-assessment/src/main/ java/com/redhat/training/todo/service. Tenga en cuenta que esta clase inyecta la clase ItemRepository e invoca métodos para agregar, ver y enumerar los ítems Todo. 5. Convierta el POJO ItemService en un EJB sin estado. 6. Explore la clase ItemResourceRESTService, que proporciona puntos finales REST para la interfaz de usuario front-end (basada en AngularJS). Este archivo está ubicado en JB183-EAP7.0-es-2-20180124 137 Capítulo 3. Creación de Enterprise Java Beans la carpeta /home/student/JB183/labs/ejb-assessment/src/main/java/com/ redhat/training/todo/rest. Tenga en cuenta que esta clase necesita usar el EJB ItemService para invocar los métodos de EJB, y brinda una respuesta JSON para la capa de front-end. 7. Inyecte el EJB ItemService en la clase ItemResourceRESTService. Agregue la siguiente importación en la clase ItemResourceRESTService para la anotación: import javax.ejb.EJB 8. Inicie JBoss EAP desde dentro de JBDS. 9. Compile e implemente la aplicación en JBoss EAP con Maven. 10. Observe las referencias JNDI del EJB ItemService en los registros del servidor de JBoss EAP: 11. Explore la aplicación. 11.1.Diríjase a http://localhost:8080/ejb-assessment en un explorador para acceder a la aplicación. 11.2.En el lado izquierdo de la página web, observe que se agregaron tres ítems Todo en el método @PostConstruct de la clase ItemRepository. Observe la pestaña Console (Consola) de JBDS y verifique que la información de registro del método register() en el EJB ItemService y del método seedTodoList() en la clase ItemRepository esté visible. 11.3.En el lado derecho de la página web, agregue algunos ítems Todo; para ello, introduzca texto en el campo Description (Descripción) y haga clic en Save (Guardar). Verifique que la tarea que agregó aparezca en la tabla Task List (Lista de tareas) en la izquierda. Observe la pestaña Console (Consola) de JBDS y verifique que la información de registro del método register() en el EJB ItemService esté visible. 11.4.Agregue algunas tareas más y observe que la aplicación muestra automáticamente una barra de paginación en la parte inferior de la lista Task List (Lista de tareas) si hay más de cinco tareas. 12. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab ejb-assessment grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 13. Realice la limpieza. 138 JB183-EAP7.0-es-2-20180124 13.1.Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/ejb-assessment [student@workstation ejb-assessment]$ mvn wildfly:undeploy 13.2.Haga clic con el botón derecho en el proyecto ejb-assessment en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 13.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 139 Capítulo 3. Creación de Enterprise Java Beans Solución En este trabajo de laboratorio, convertirá una clase de POJO en un EJB en la aplicación To Do List y mostrará una lista de los ítems Todo en una página web. Resultados Deberá poder modificar una aplicación To Do List escrita con anterioridad y convertir una clase POJO en un EJB. La aplicación acepta una lista de cosas por hacer como entrada del usuario, y muestra la lista de ítems Todo en una página web. Antes de comenzar Abra una ventana de terminal en la máquina virtual de la estación de trabajo y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab ejb-assessment setup 1. Abra JBDS e importe el proyecto ejb-assessment ubicado en el directorio /home/ student/JB183/labs/ejb-assessment. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Deje la configuración predeterminada (/home/student/ JB183/workspace) y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta ejb-assessment y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Revise la clase Item.java de la carpeta /home/student/JB183/labs/ejbassessment/src/main/java/com/redhat/training/todo/model. Esta clase modela un único ítem Todo en la aplicación. Tiene tres atributos: un id, una descripción y un atributo booleano que indica si la tarea se completó o no. 3. Revise la clase ItemRepository.java de la carpeta /home/student/JB183/labs/ ejb-assessment/src/main/java/com/redhat/training/todo/data. Esta clase simula una base de datos en la memoria y almacena la lista de ítems Todo. Tiene métodos para agregar ítems, ver un único ítem y ver una lista de todos los ítems. 140 JB183-EAP7.0-es-2-20180124 Solución Tenga en cuenta que esta clase está anotada con @ApplicationScoped, que significa que los objetos de esta clase se mantienen en el alcance (activos) siempre y cuando la aplicación esté implementada y en ejecución en el servidor de aplicaciones. Tenga en cuenta que el método seedTodoList() se anotó con @PostConstruct. Este método completa la lista Todo con tres ítems una vez que se inicializa la clase. 4. Explore la clase ItemService.java, que es un POJO simple que contiene métodos para agregar ítems, ver un ítem Todo y enumerar todos los ítems Todo. Este archivo está ubicado en la carpeta /home/student/JB183/labs/ejb-assessment/src/main/ java/com/redhat/training/todo/service. Tenga en cuenta que esta clase inyecta la clase ItemRepository e invoca métodos para agregar, ver y enumerar los ítems Todo. 5. Convierta el POJO ItemService en un EJB sin estado. Anote la clase ItemService con la anotación @Stateless para convertir este POJO en un EJB. import javax.ejb.Stateless @Stateless public class ItemService { 6. Explore la clase ItemResourceRESTService, que proporciona puntos finales REST para la interfaz de usuario front-end (basada en AngularJS). Este archivo está ubicado en la carpeta /home/student/JB183/labs/ejb-assessment/src/main/java/com/ redhat/training/todo/rest. Tenga en cuenta que esta clase necesita usar el EJB ItemService para invocar los métodos de EJB, y brinda una respuesta JSON para la capa de front-end. 7. Inyecte el EJB ItemService en la clase ItemResourceRESTService. Agregue la anotación @EJB en la declaración ItemService. @EJB ItemService itemService; Agregue la siguiente importación en la clase ItemResourceRESTService para la anotación: import javax.ejb.EJB 8. Inicie JBoss EAP desde dentro de JBDS. Seleccione la pestaña Servers (Servidores) en JBDS. Haga clic con el botón derecho en la entrada del servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en la opción Start (Iniciar) verde para iniciar el servidor. Observe la pestaña Console (Consola) de JBDS hasta que el servidor se inicie y vea el siguiente mensaje: JB183-EAP7.0-es-2-20180124 141 Capítulo 3. Creación de Enterprise Java Beans INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.2.GA (WildFly Core 2.1.8.Final-redhat-1) started 9. Compile e implemente la aplicación en JBoss EAP con Maven. Abra un nuevo terminal y cambie el directorio por la carpeta /home/student/JB183/ labs/ejb-assessment. [student@workstation ~]$ cd /home/student/JB183/labs/ejb-assessment Compile e implemente el EJB en JBoss EAP al ejecutar el siguiente comando: [student@workstation ejb-assessment]$ mvn clean wildfly:deploy La compilación debe ejecutarse correctamente y usted debe ver la siguiente salida: [student@workstation ejb-assessment]$ mvn clean package wildfly:deploy [INFO] Scanning for projects... [INFO] [INFO] -----------------------------------------------------------------------[INFO] Building TODO List Project 1.0 [INFO] -----------------------------------------------------------------------[INFO] [INFO] <<< wildfly-maven-plugin:1.0.2.Final:deploy (default-cli) < package @ ejb-assessment <<< ... [INFO] --- maven-war-plugin:2.1.1:war (default-war) @ ejb-assessment --... [INFO] --- wildfly-maven-plugin:1.0.2.Final:deploy (default-cli) @ ejb-assessment [INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ 10. Observe las referencias JNDI del EJB ItemService en los registros del servidor de JBoss EAP: En la pestaña Console (Consola) de JBDS, debe ver lo siguiente: JNDI bindings for session bean named 'ItemService' in deployment unit 'deployment "ejb-assessment.war"' are as follows: java:global/ejb-assessment/ItemService!com.redhat.training.todo.service.ItemService java:app/ejb-assessment/ItemService!com.redhat.training.todo.service.ItemService java:module/ItemService!com.redhat.training.todo.service.ItemService java:global/ejb-assessment/ItemService java:app/ejb-assessment/ItemService java:module/ItemService 11. Explore la aplicación. 11.1.Diríjase a http://localhost:8080/ejb-assessment en un explorador para acceder a la aplicación. 11.2.En el lado izquierdo de la página web, observe que se agregaron tres ítems Todo en el método @PostConstruct de la clase ItemRepository. 142 JB183-EAP7.0-es-2-20180124 Solución Observe la pestaña Console (Consola) de JBDS y verifique que la información de registro del método register() en el EJB ItemService y del método seedTodoList() en la clase ItemRepository esté visible. Debe ver la siguiente salida de registro: 11:19:52,488 INFO [com.redhat.training.todo.service.ItemService] (default task-21) Fetching all TODO items... 11:19:52,489 INFO [com.redhat.training.todo.data.ItemRepository] (default task-21) Seeding TODO List cache... 11.3.En el lado derecho de la página web, agregue algunos ítems Todo; para ello, introduzca texto en el campo Description (Descripción) y haga clic en Save (Guardar). Verifique que la tarea que agregó aparezca en la tabla Task List (Lista de tareas) en la izquierda. Observe la pestaña Console (Consola) de JBDS y verifique que la información de registro del método register() en el EJB ItemService esté visible. Por ejemplo, si agrega una nueva tarea denominada Plan Project, debe ver la siguiente salida de registro: 11:21:23,985 INFO [com.redhat.training.todo.service.ItemService] (default task-23) Adding new task: Plan Project 11.4.Agregue algunas tareas más y observe que la aplicación muestra automáticamente una barra de paginación en la parte inferior de la lista Task List (Lista de tareas) si hay más de cinco tareas. 12. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab ejb-assessment grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 13. Realice la limpieza. 13.1.Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/ejb-assessment [student@workstation ejb-assessment]$ mvn wildfly:undeploy 13.2.Haga clic con el botón derecho en el proyecto ejb-assessment en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 13.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). JB183-EAP7.0-es-2-20180124 143 Capítulo 3. Creación de Enterprise Java Beans Esto concluye el trabajo de laboratorio. 144 JB183-EAP7.0-es-2-20180124 Resumen Resumen En este capítulo, aprendió lo siguiente: • Un Enterprise Java Bean (EJB) es un componente portátil de Java EE que, por lo general, se usa para encapsular lógica de negocio en una aplicación empresarial. Se ejecuta en un servidor de aplicaciones y se puede consumir por clientes remotos, así como otros componentes de Java EE que se ejecutan localmente en el mismo proceso JVM. • Un EJB proporciona subprocesamiento múltiple, concurrencia, transacciones y seguridad para aplicaciones empresariales sin requerir que el desarrollador escriba códigos para estas características de manera explícita. Además, el desarrollador puede agregar anotaciones en el EJB para exponer los métodos de negocio como extremos de servicio web. • Hay dos tipos diferentes de EJB: Beans de sesión y Beans controlados por mensajes (MDB). Los beans de sesión pueden ser de tres tipos: Beans de sesión sin estado (SLSB), Beans de sesión con estado (SFSB) y Beans de sesión singleton. • Un bean controlado por mensaje (MDB) permite que las aplicaciones Java EE procesen mensajes de manera asíncrona. Un MDB escucha mensajes JMS. Realiza una acción por cada mensaje recibido. Los MDB brindan un modelo sin conexión directa impulsado por eventos para el desarrollo de aplicaciones. • Si un cliente de EJB y el EJB se ejecutan localmente en el mismo proceso JVM, los clientes pueden inyectar referencias directamente en el EJB mediante la anotación @EJB. Si el cliente es remoto, se utilizan las búsquedas de JNDI. • Los componentes de un EJB en una aplicación se ejecutan en el contexto de un contenedor dentro de un servidor de aplicaciones. El contenedor es responsable de administrar el ciclo de vida (creación, ejecución y destrucción) de los EJB. Cada uno de los diferentes tipos de EJB (sin estado, con estado, singleton, MDB) tiene su propio ciclo de vida. • Java EE respalda las Transacciones para garantizar que se mantenga la integridad de datos al controlar el acceso concurrente a los datos y para garantizar que una transacción de negocio fallida no deje el sistema en un estado inconsistente o inválido. • En Java EE, las transacciones se pueden administrar de dos maneras: Transacciones administradas por contenedores (CMT) y Transacciones administradas por beans (BMT). • En CMT, el servidor de aplicaciones administra las transacciones sin que el desarrollador tenga que escribir código explícito y el alcance se puede controlar mediante el uso de Atributos de transacción. El servidor de aplicaciones puede realizar automáticamente una reversión cuando encuentra una falla o una excepción. • En BMT, el desarrollador es responsable de administrar las transacciones y tiene pleno control del alcance de las transacciones. El desarrollador tiene que confirmar y revertir manualmente las transacciones si se produce una excepción o una falla. JB183-EAP7.0-es-2-20180124 145 146 TRAINING CAPÍTULO 4 GESTIÓN DE LA PERSISTENCIA Descripción general Meta Crear entidades de persistencia con validaciones. Objetivos • Describir la API de persistencia. • Conservar datos en un almacén de datos mediante entidades. • Anotar beans para validar datos. • Crear una consulta mediante Java Persistence Query Language. Secciones • Descripción de la API de persistencia (y cuestionario) • Persistencia de datos (y ejercicio guiado) • Anotación de clases para validar beans (y ejercicio guiado) • Creación de consultas (y ejercicio guiado) Trabajo de laboratorio JB183-EAP7.0-es-2-20180124 Gestión de la persistencia 147 Capítulo 4. Gestión de la persistencia Descripción de la API de persistencia Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de realizar lo siguiente: • Comprender los conceptos de asignación relacional de objetos. • Describir las anotaciones y clases de entidad. • Describir cómo utilizar EntityManager en un EJB. • Describir un descriptor XML de contexto de persistencia. • Describir el impacto de las transacciones en la persistencia. Asignación relacional de objetos La persistencia es cuando una aplicación almacena datos en un almacén permanente, como un archivo sin formato, archivo XML o una base de datos para la durabilidad. Las bases de datos relacionales son uno de los almacenes de datos más comunes que una aplicación empresarial utiliza para preservar datos para volver a utilizarlos. Los datos de negocio en una aplicación empresarial de Java EE se definen como objetos Java. Estos objetos se preservan en las tablas de base de datos correspondientes. Los objetos Java y las tablas de base de datos utilizan diferentes tipos de datos, como una String en Java y Varchar en una base de datos, para almacenar datos de negocio. A medida que se transfieren los datos entre la aplicación y la base de datos como resultado de operaciones de escritura, se pueden generar diferencias entre el modelo del objeto y el modelo relacional. Esta discrepancia es conocida como disparidad de impedancia, y los desarrolladores de aplicaciones deben escribir código para justificarla si el proveedor de persistencia aún no solucionó la disparidad. Figura 4.1: Disparidad de impedancia La técnica para automatizar la corrección de la disparidad de impedancia es conocida como Asignación relacional de objetos (ORM). El software de ORM utiliza metadatos para describir la asignación entre las clases definidas en una aplicación y el esquema de una tabla de base de datos. La asignación se proporciona en archivos de configuración XML o anotaciones. 148 JB183-EAP7.0-es-2-20180124 Anotaciones y clases de entidad Por ejemplo, desea almacenar objetos de la clase TodoItem en la tabla de base de datos TodoItem; ORM asigna el nombre de la clase Java al nombre de una tabla de base de datos y los atributos de la clase se asignan a los campos correspondientes en la tabla de manera automática. Figura 4.2: Asignación relacional de objetos Java EE proporciona la especificación de Java Persistence API (JSR 338) que varios proveedores de ORM implementan. Hay muchos productos de software de ORM disponibles en el mercado, como Hibernate y EclipseLink. Una ORM completamente implementada brinda técnicas de optimización, almacenamiento en caché, portabilidad de base de datos, lenguaje de consulta y persistencia de objetos. Los tres conceptos clave relacionados con Java Persistence API son entidades, unidades de persistencia y contexto de persistencia. Anotaciones y clases de entidad Una entidad es un objeto de dominio liviano que tiene capacidad de persistencia. Una clase de entidad se asigna a una tabla en una base de datos relacional. Cada instancia de una clase de entidad tiene un campo de clave primaria. El campo de clave primaria se utiliza para asignar una instancia de entidad a una fila de una tabla de base de datos. Todos los atributos no transitorios se asignan a campos de una tabla de base de datos. En una tabla de base de datos, cada instancia persistida de una entidad tiene una identidad de persistencia que la identifica en una tabla. En Java, una entidad es una clase de Objetos comunes antiguos de Java (POJO) que está marcada con la anotación @Entity. Todos los campos de una clase de entidad se almacenan en la base de datos de manera predeterminada y se conocen como campos persistentes. Los atributos que se declaran como transitorios no se almacenan en una tabla de base de datos y se conocen como no persistentes. Declaración de clases de entidad Una clase de entidad se declara de la siguiente manera: import javax.persistence.*; import java.io.*; @Entity public class TodoItem implements Serializable { @Id private int id; //primary key -- required for an Entity class private String item; private String status; public TodoItem(){ } //No argument constructor // other constructor public TodoItem(String item,String status) { this.item=item; this.status=status; JB183-EAP7.0-es-2-20180124 149 Capítulo 4. Gestión de la persistencia } //Setter and Getter methods public String getItem() { return item; } public void setItem(String item) { this.item = item; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } } La relación predeterminada entre una clase de entidad y una tabla de base de datos es la siguiente: Asignación predeterminada de entidad a tabla Entidad Tabla Clase de entidad Nombre de tabla Atributos de la clase de entidad Columnas de una tabla de base de datos Instancia de entidad Registro o fila de una tabla de base de datos Utilización de las anotaciones JPA Las anotaciones se utilizan para decorar las clases Java, los campos y los métodos con metadatos para la asignación, configuración, consulta, validación, etc., para que se compilen y estén disponibles en el tiempo de ejecución. A continuación se muestran algunas anotaciones comunes: @Entity La anotación @Entity especifica que una clase es una entidad. Una clase Java se puede configurar como una clase de entidad sin utilizar una anotación @Entity al asignarla en el archivo de configuración orm.xml. orm.xml contiene todos los detalles de configuración requeridos para declarar una clase Java como una entidad. @Table La anotación @Table se utiliza para especificar la asignación entre una clase de entidad y una tabla. Se utiliza cuando el nombre de una clase de entidad es diferente al nombre de una tabla en la base de datos. @Entity @Table(name="ThingsToDo") public class TodoItem { ... } La clase de entidad TodoItem se asigna a la tabla ThingsToDo. 150 JB183-EAP7.0-es-2-20180124 Anotaciones y clases de entidad @Column La anotación @Column se utiliza para asignar un campo o una propiedad a una columna de la base de datos. @Entity @Table(name="ThingsToDo") public class TodoItems implements Serializable { @Column(name="itemname") private String item; ... Un atributo item se asigna a la columna itemname de la tabla. @Temporal La anotación @Temporal se utiliza con un tipo Date de atributo. La base de datos almacena una fecha de manera diferente que las clases Java. La anotación Temporal administra la asignación para un tipo java.util.Date o java.util.Calendar y lo convierte en un tipo de fecha adecuado en la base de datos. @Entity public class TodoItem implements Serializable { ... @Temporal(TemporalType.DATE) private Date completionDate; @Transient La anotación Transient se utiliza para especificar un campo no persistente. @Entity public class TodoItem implements Serializable { ... @Transient private int countPending; El campo countPending no se guarda en la tabla de la base de datos. @Id La anotación@Id se utiliza para especificar la clave primaria. El campo id se utiliza para identificar una fila única en la tabla de la base de datos. @Entity public class TodoItem implements Serializable { @Id private int id; ... } Una clave primaria puede ser un tipo simple de Java o un valor compuesto, que consta de varios campos. Para una clave primaria compuesta, se define una clase de clave primaria. La anotación @EmbeddedId o @IdClass se utiliza para especificar la clave primaria compuesta. JB183-EAP7.0-es-2-20180124 151 Capítulo 4. Gestión de la persistencia Referencias Para obtener más información acerca de la configuración de claves compuestas, consulte la API pública para Red Hat JBoss EAP 7, que se encuentra en https://developers.redhat.com/apidocs/eap/7.0.0/ Generación de ID Cada instancia de entidad se asigna a una fila en una tabla de base de datos. Cada fila de una tabla es única y está identificada con un ID único, conocido como una identidad de entidad persistente. La identidad de entidad persistente se genera a partir del campo de clave primaria. Se requiere un campo de clave primaria en cada clase de entidad. Una clave primaria simple debe ser uno de los siguientes tipos: • Tipos primitivos de Java: byte, short, int, long o char • Tipo java.lang.String • Clases de Java Wrapper para tipos primitivos: Byte, Short, Integer, Long o Character • Tipos temporales: java.util.Date o java.sql.Date La anotación @Id se utiliza parar especificar una clave primaria simple. La anotación @GeneratedValue se aplica al campo o a la propiedad de la clave primaria para especificar la estrategia de generación de la clave primaria. La anotación @GeneratedValue brinda un elemento GenerationType del tipo de enumeración. Las cuatro estrategias de generación de clave primaria son las siguientes: GenerationType.AUTO La estrategia AUTO es la estrategia de generación de ID predeterminada y significa que el proveedor de JPA usa cualquier estrategia de su elección para generar la clave primaria. Hibernate selecciona la estrategia de generación basada en el dialecto específico de la base de datos. @Entity public class TodoItem implements Serializable { @Id @GeneratedValue(GenerationType.AUTO) private int id; ... } GenerationType.SEQUENCE La estrategia SEQUENCE significa que el proveedor de JPA usa la secuencia de la base de datos para generar la clave primaria. La secuencia se debe crear en la base de datos, y el nombre de la secuencia se proporciona en el elemento generator. /* ITEMS_SEQ sequence create sequence ITEMS_SEQ MINVALUE 1 START WITH 1 152 JB183-EAP7.0-es-2-20180124 Descripción del administrador de entidades INCREMENT BY 1 */ @Entity public class TodoItem implements Serializable { @Id @GeneratedValue(GenerationType.SEQUENCE, generator="ITEMS_SEQ")) private int id; ... } GenerationType.IDENTITY La estrategia IDENTITY significa que el proveedor de JPA usa la columna de identidad de la base de datos para generar la clave primaria. @Entity public class TodoItem { @Id @GeneratedValue(GenerationType.IDENTITY) private int id; ... } GenerationType.TABLE La estrategia TABLE significa que el proveedor de JPA utiliza la tabla de generación de ID de la base de datos. Esta es una tabla separada que se utiliza para generar el valor del ID. La tabla de generación de ID tiene dos columnas. La primera columna es una cadena que identifica la secuencia del generador, y la segunda columna es un valor entero que almacena la secuencia del ID. @Entity public class TodoItem implements Serializable { @TableGenerator(name="Items_gen", table="ITEM_ID_GEN", pkColumnName="GEN_NAME", valueColumnName="GEN_VAL", pkColumnValue="ITEM_ID", allocationSize=60) @Id @GeneratedValue(Generator="Items_gen") private int id; ... } Descripción del administrador de entidades La API EntityManager se define para realizar operaciones de persistencia. Un administrador de entidades obtiene la referencia a una entidad y realiza las operaciones CRUD (Crear, leer, actualizar y eliminar) en la base de datos. Una instancia EntityManager puede obtenerse de un objeto EntityManagerFactory. Un administrador de entidades trabaja dentro de un conjunto de instancias de entidades administradas. Estas instancias de entidades administradas son conocidas como el contexto de persistencia del administrador de entidades. Podemos considerar al contexto de persistencia como una instancia única de una unidad de persistencia. Una unidad de persistencia es un conjunto de todas las clases de entidades y un archivo persistence.xml almacenado en una colección de archivos de aplicaciones. El persistence.xml es un archivo de configuración que contiene información acerca de las clases de entidad, fuentes de datos, tipos de transacción y otros datos de configuración. JB183-EAP7.0-es-2-20180124 153 Capítulo 4. Gestión de la persistencia Creación del administrador de entidades en EJB Un objeto EntityManagerFactory se crea para la unidad de persistencia, y este objeto se utiliza para obtener una instancia de EntityManager. @Stateless public class ItemService { //ItemPU is the name of the persistence unit EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("ItemPU"); EntityManager em = emFactory.createEntityManager(); .... } Otra manera de obtener una instancia de EntityManager en los objetos administrados de Java EE, como un EJB, es la técnica de productor. Se puede inyectar un objeto mediante Contextos e Inyección de dependencia (CDI). CDI es un conjunto de servicios de administración de componentes que permite la inyección de dependencias con seguridad de tipos. Hablaremos sobre CDI con mayor detalle más adelante en este curso. Una clase de productor define un método de productor que devuelve el tipo de datos que se inyecta en otra clase. public class EMProducer { @Produces @PersistenceContext(unitName= "ItemPU") private EntityManager em; } Una clase de EJB puede inyectar EntityManager mediante la anotación @Inject. @Stateless public class ItemService{ @Inject private EntityManager em; public void registerItem(Item item) throws Exception { ... em.persist(item); .... } public void removeItem(Long id) throws Exception { ... em.remove(findById(id)); .... } public void updateItem(Item item) { em.merge(item); } } Descripción de la unidad de persistencia Una unidad de persistencia describe los ajustes de configuración relacionados con una fuente de datos, transacciones, clases concretas y asignación relacional de objetos. Una unidad de persistencia se configura en un archivo persistence.xml en el directorio METAINF de la aplicación. Cada aplicación que usa persistencia tiene al menos una unidad de persistencia. Una unidad de persistencia contiene información acerca del nombre de la unidad de persistencia, la fuente de datos y el tipo de transacción. Hablaremos sobre el archivo persistence.xml en la siguiente sección. 154 JB183-EAP7.0-es-2-20180124 Impacto de transacciones en la persistencia <?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/ XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="Items" transaction-type="JTA"> <jta-data-source>java:jboss/datasources/MySQLDS</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="true" /> </properties> </persistence-unit> </persistence> Impacto de transacciones en la persistencia Una transacción es una unidad simple de trabajo que está compuesta por una serie de operaciones. Si una de las operaciones falla en la transacción, la transacción completa vuelve a su estado original antes del inicio de la transacción. Si se pueden ejecutar todas las operaciones, la transacción se confirma y no se requiere una restauración. Cuando trabaja con persistencia, las transacciones garantizan que los cambios en una base de datos no se completen parcialmente como resultado de una falla en la operación. JPA proporciona comportamiento transaccional para operaciones en los recursos de JPA mediante dos enfoques para las transacciones: • Transacciones locales de recursos • Transacciones de JTA Las transacciones locales de recursos son transacciones con un alcance que abarca un único recurso, como una fuente de datos. Por ejemplo, si una aplicación está configurada para usar las transacciones locales de recursos, el administrador de entidades que está vinculado con la fuente de datos que no corresponde a JTA utiliza la clase EntityTransaction para administrar la transacción. Sin embargo, esta transacción solo se aplica a las operaciones en esa fuente de datos individual en función del administrador de entidades, que limita transacciones más complejas para abarcar varias fuentes de datos o varios sistemas de mensajería. En contraste, las transacciones de JTA abarcan todos los recursos en un contenedor. En lugar de hacer referencia a EntityTransaction del administrador de entidades, JTA usa la clase UserTransaction que le permite iniciar, confirmar o restaurar transacciones de manera independiente del recurso o de los recursos. Esta separación de la transacción de un recurso único permite que las transacciones contengan operaciones complejas que abarcan varios recursos, como múltiples fuentes de datos y sistemas de mensajería JMS. Referencias JSR de persistencia de Java https://www.jcp.org/en/jsr/detail?id=338 JB183-EAP7.0-es-2-20180124 155 Capítulo 4. Gestión de la persistencia Referencias Para obtener más información, consulte la Guía de desarrollo para API de persistencia Java para Red Hat JBoss EAP 7, que se encuentra en https://docs.jboss.org/author/display/AS7/JPA+Reference+Guide/ 156 JB183-EAP7.0-es-2-20180124 Cuestionario: Descripción de la API de persistencia Cuestionario: Descripción de la API de persistencia Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuál de las siguientes anotaciones se requiere para convertir una clase de Java SE en una clase de entidad? a. b. c. d. 2. ¿Cuáles de las siguientes propiedades no se definen en el archivo persistence.xml? a. b. c. d. e. 3. Nombre de la unidad de persistencia Tipo de transacción URL de fuente de datos Proveedor de base de datos Parámetros específicos del proveedor ¿Cuáles dos de los siguientes enunciados acerca del administrador de entidades son correctos? (Elija dos opciones). a. b. c. d. 4. @Table @Produces @Entity @EntityManager Los objetos del administrador de entidades se asignan a las filas de una tabla de base de datos. Un administrador de entidades realiza las operaciones Crear, leer, actualizar y eliminar (CRUD) en una entidad. Un administrador de entidades tiene un contexto de persistencia asociado. Un administrador de entidades puede producir un conjunto de objetos EntityManagerFactory. ¿Cuál es la estrategia de generación de ID que utiliza una columna de base de datos para generar el ID? a. b. c. d. SEQUENCE_GENERATOR TABLE SEQUENCE IDENTITY JB183-EAP7.0-es-2-20180124 157 Capítulo 4. Gestión de la persistencia Solución Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuál de las siguientes anotaciones se requiere para convertir una clase de Java SE en una clase de entidad? a. b. c. d. 2. ¿Cuáles de las siguientes propiedades no se definen en el archivo persistence.xml? a. b. c. d. e. 3. b. c. d. Los objetos del administrador de entidades se asignan a las filas de una tabla de base de datos. Un administrador de entidades realiza las operaciones Crear, leer, actualizar y eliminar (CRUD) en una entidad. Un administrador de entidades tiene un contexto de persistencia asociado. Un administrador de entidades puede producir un conjunto de objetos EntityManagerFactory. ¿Cuál es la estrategia de generación de ID que utiliza una columna de base de datos para generar el ID? a. b. c. d. 158 Nombre de la unidad de persistencia Tipo de transacción URL de fuente de datos Proveedor de base de datos Parámetros específicos del proveedor ¿Cuáles dos de los siguientes enunciados acerca del administrador de entidades son correctos? (Elija dos opciones). a. 4. @Table @Produces @Entity @EntityManager SEQUENCE_GENERATOR TABLE SEQUENCE IDENTITY JB183-EAP7.0-es-2-20180124 Persistencia de datos Persistencia de datos Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de realizar lo siguiente: • Describir los requisitos de las clases de entidad. • Describir propiedades y campos de entidad. • Describir la interfaz EntityManager y los métodos clave. Creación de una clase de entidad Una clase de entidad es similar a una clase POJO estándar, pero una entidad tiene varias distinciones importantes que requieren la administración por parte de EntityManager. Para convertir una clase POJO en una entidad, prefije una anotación @Entity en el encabezado de la clase. Además, se debe poder acceder a cada variable de instancia a través del uso de los métodos getter y setter. Por último, la clase debe tener al menos un constructor que no tenga argumentos, aunque la clase pueda tener otros constructores que tienen argumentos. A continuación se muestra un ejemplo de una clase de entidad: @Entity public abstract class Customer { @Id private int custId; private String custName; .... public Customer(){ } // No argument constructor //setter and getter methods public String getCustName() { return custName; } public void setCustName(String custName) { this.custName = custName; } ... } Propiedades y campos de entidad Los datos no transitorios en una clase de entidad se persisten en una tabla de base de datos. Un proveedor de JPA puede cargar datos de una tabla de base de datos en una clase de entidad y almacenar datos de una clase de entidad en una tabla de base de datos. La manera en que el proveedor accede al estado es conocida como el modo de acceso. Hay dos modos de acceso: acceso basado en el campo y acceso basado en la propiedad. Acceso basado en el campo El acceso basado en el campo se provee mediante la anotación de campos. Un campo persistente en una clase de entidad se debe declarar con acceso de nivel privado, protegido o de paquete. Un campo persistente debe ser uno de los siguientes tipos: JB183-EAP7.0-es-2-20180124 159 Capítulo 4. Gestión de la persistencia • Tipos primitivos de Java: byte, short, int, long o char • Tipo java.lang.String • Clases de Java Wrapper para tipos primitivos: Byte, Short, Integer, Long o Character • Tipos temporales: java.util.Date o java.sql.Date • Tipos enumerados • Clases incorporables, otras entidades y colecciones de entidades Los métodos getter y setter pueden estar presentes como no. El siguiente es un ejemplo de acceso basado en campos: @Entity public class Customer implements Serializable { // Note that the fields are annotated @Id protected int custId; protected String custName; @Temporal(TemporalType.DATE) protected Date registrationDate; @Column(name="address") protected Address custAddress; ..... } nota Se requiere una interfaz Serializable para clases de entidades a las que se accede a través de una interfaz remota. El acceso basado en campos proporciona una flexibilidad adicional ya que los campos o métodos de ayuda que no deben formar parte del estado persistente se pueden excluir mediante la anotación @Transient u omitiendo los métodos getter y setter. Acceso basado en propiedades Para proporcionar acceso basado en propiedades, se deben definir los métodos getter y setter en una clase de entidad Java. El acceso basado en propiedades proporciona una mejor encapsulación, ya que el acceso se da solo a través de métodos. El acceso basado en propiedades se brinda anotando los métodos getter. El tipo de retorno del método getter determina el tipo de propiedad. El tipo de retorno del método getter debe ser el mismo que el tipo de argumento enviado al método setter. Los métodos getter y setter deben ser públicos o protegidos y deben seguir las convenciones de denominación de Java bean. El siguiente es un ejemplo de acceso basado en propiedades: @Entity public class Customer implements Serializable { protected int custId; protected String custName; protected Date registrationDate; protected Address custAddress; .... //Note the getter methods are annotated 160 JB183-EAP7.0-es-2-20180124 Propiedades y campos de entidad @Id public int getCustId(){ return custId; } public String getCustName(){ return custName; } @Temporal(TemporalType.DATE) public Date getRegistrationDate(){ return registrationDate; } @Column(name="address") public Address getCustAddress(){ return custAddress; } .... //Setter methods } Estados de entidades Una entidad puede existir en uno de cuatro estados durante su vida útil. Estos cuatro estados son: • New State (Estado nuevo): una instancia de entidad creada mediante el operador new de Java está en un estado nuevo o temporal. Una instancia de entidad no tiene una identidad persistente y aún no está relacionada con el contexto de persistencia. • Managed State (Estado gestionado): una instancia de entidad con una identidad persistente, relacionada con un contexto de persistencia se encuentra en estado gestionado o persistente. Cuando se realiza un cambio en los datos de campos de entidades gestionadas, se lo sincroniza con los datos de la tabla de la base de datos. Una instancia de entidad se encuentra en estado gestionado después de que una aplicación invoca el método persist, find o merge de un administrador de entidades. • Removed State (Estado eliminado): una entidad persistente se puede eliminar de la tabla de base de datos de muchas maneras. Una instancia de entidad gestionada se puede eliminar de la tabla de la base de datos cuando se confirma una transacción o cuando se invoca un método remove de un administrador de entidades. A continuación, una entidad pasa a estar en estado eliminado. • Detached State (Estado separado): una entidad tiene una identidad de entidad persistente pero no está relacionada con el contexto de persistencia. Esto puede ocurrir cuando la entidad está serializada o al final de una transacción. Este estado se conoce como estado separado. JB183-EAP7.0-es-2-20180124 161 Capítulo 4. Gestión de la persistencia Figura 4.3: Relación de los componentes de JPA Interfaz EntityManager y métodos clave La interfaz javax.persistence.EntityManager se usa para interactuar con el contexto de persistencia. Las instancias de entidades y sus ciclos de vida se gestionan dentro del contexto de persistencia. La API javax.persistence.EntityManager se utiliza para crear nuevas instancias de entidades, encontrar instancias de entidades a través de su clave primaria, realizar consultas a través de instancias de entidades y eliminar las instancias de entidades existentes. Los métodos clave de EntityManager son: persist() El método persist() persiste a una entidad y la vuelve gestionada. El método persist() inserta una fila en una tabla de base de datos. El método persist() arroja PersistenceException si falla la operación persist. @Stateless public class CustomerServices { .... public void saveCustomer(Customer customer) { ... try{ entityManager.persist(customer); }catch(PersistenceException persistenceException){ // code to handle PersistenceException } } } find() El método find() busca una entidad de una clase específica por su clave primaria y devuelve una instancia de entidad gestionada. Si no se encuentra el objeto, devuelve uno nulo. @Stateless public class CustomerServices { .... 162 JB183-EAP7.0-es-2-20180124 Interfaz EntityManager y métodos clave public void getCustomer(Customer customer) { ... Customer customer; try{ customer = entityManager.find(Customer.class,custId); if (customer != null){ System.out.print(customer.getCustName()); } else } System.out.print("Not Found"); } }catch(Exception exception){ // code to handle PersistenceException } } } contains() El método contains() toma una instancia como argumento y verifica si esta se encuentra en el contexto de persistencia: @Stateless public class CustomerServices { .... public boolean saveCustomer(Customer customer) { ... entityManager.persist(customer); return entityManager.contains(customer); } } merge() El método merge() actualiza los datos en la tabla para una entidad separada existente. El método merge() inserta una nueva fila en una tabla de base de datos para una entidad que se encuentra en un estado nuevo o temporal. Después de la operación de fusión, una entidad se encuentra en estado gestionado. @Stateless public class CustomerServices { .... public void updateCustomer(Customer customer) { ... Customer customer; try{ customer = entityManager.find(Customer.class,custId); entityManager.merge(customer); }catch(Exception exception){ // code to handle PersistenceException } } } remove() El método remove() elimina una entidad gestionada. Para eliminar una entidad separada, invoque un método find() que regresa una instancia gestionada y, luego, invoque el método remove(). @Stateless JB183-EAP7.0-es-2-20180124 163 Capítulo 4. Gestión de la persistencia public class CustomerServices { .... public void deleteCustomer(Customer customer) { ... Customer customer; try{ customer = entityManager.find(Customer.class,custId); entityManager.remove(customer); }catch(Exception exception){ // code to handle PersistenceException } } } clear() El método clear() elimina el contexto de persistencia. Después de esta operación, todas las entidades gestionadas se encuentran en estado separado. ... try{ entityManager.clear(); }catch(Exception exception){ // code to handle PersistenceException } refresh() El método refresh() actualiza el estado de una instancia de entidad de una tabla de base de datos. Los datos actuales en una instancia de entidad se sobrescriben con los datos capturados de una tabla de base de datos. ... try{ entityManager.refresh(customer); }catch(Exception exception){ // code to handle PersistenceException } Etiquetas importantes del archivo persistence.xml El archivo persistence.xml es un archivo de configuración estándar que contiene las unidades de persistencia. Cada unidad de persistencia tiene un nombre único. <?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/ XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="Items" transaction-type="JTA"> <jta-data-source>java:jboss/datasources/MySQLDS</jta-data-source> 164 JB183-EAP7.0-es-2-20180124 Demostración: Persistencia de datos <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="true" /> </properties> </persistence-unit> </persistence> persistence-unit name es el nombre de la unidad de persistencia. El nombre de la unidad de persistencia se utiliza para obtener el EntityManager. transaction-type puede ser JTA o RESOURCE_LOCAL. El tipo de transacción define el tipo de transacciones que una aplicación pretende realizar. Las transacciones de contenedor usan la API de transacción Java (JTA), provista en cada servidor de aplicaciones Java EE. En las transacciones de tipo JTA, un contenedor es responsable de crear el administrador de entidades y de realizarle un seguimiento. En RESOURCE_LOCAL, usted es responsable de crear el administrador de entidades y de realizarle un seguimiento. jta-data-source es el nombre de la fuente de datos. Cada unidad de persistencia debe tener una conexión de base de datos. El proveedor de JPA encuentra la fuente de datos por nombre con el servicio de búsqueda de JNDI durante el inicio. Propiedades adicionales, tanto estándares como específicas del proveedor, se pueden establecer en el elemento properties (propiedades). La propiedad hibernate.Dialect especifica qué base de datos se utiliza. La propiedad hibernate.hbm2ddl.auto con un valor update actualiza el esquema de manera automática. La propiedad hibernate.show-sql con un valor true permite el registro de enunciados SQL en la consola. Demostración: Persistencia de datos 1. Ejecute el siguiente comando para preparar archivos usados por esta demostración. [student@workstation ~]$ demo persist-data setup 2. Inicie JBDS e importe el proyecto persist-data. Este proyecto es una simple aplicación web JSF que muestra todos los datos almacenados en la base de datos. También utiliza CDI para inyectar el EJB responsable de consultar la base de datos. 3. Inspeccione el archivo pom.xml y observe la dependencia en las librerías hibernate para la especificación JPA y las clases de administrador de la entidad. <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.1-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <scope>provided</scope> </dependency> JB183-EAP7.0-es-2-20180124 165 Capítulo 4. Gestión de la persistencia 4. Actualice la clase de entidad Member (Miembro) para incluir las anotaciones JPA @Entity, @Id y @GeneratedValue para convertir la clase POJO a una clase de entidad. @Entity public class Member implements Serializable{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String emailId; ... } 5. Actualice el método saveMember en la clase MemberService para persist() (mantener) la instancia Member (Miembro) en la base de datos. public class MemberService { @PersistenceContext(unitName="member-unit") private EntityManager entityManager; public String saveMember(String emailId) { try { Member member = new Member(); member.setEmailId(emailId); entityManager.persist(member); return "Congratulations ! you will get our weekly Tech Letter at " + emailId ; } catch (Exception e) { throw new EJBException(e); } } 6. Observe el método getAllmembers() de la clase MemberService. Este método contiene el código JPA utilizado para consultar la tabla de la base de datos. public List<Member> getAllmembers() { TypedQuery<Member> query = entityManager.createQuery("SELECT m FROM Member m", Member.class); List<Member> members = query.getResultList(); return members; } 7. Inicie el servidor JBoss EAP local dentro de JBDS. En la pestaña Servers del panel inferior de JBDS, haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y vea el mensaje iniciado: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 8. 166 Implemente la aplicación en el servidor JBoss EAP local y pruébela en un explorador ejecutando los siguientes comandos en una ventana de terminal: JB183-EAP7.0-es-2-20180124 Demostración: Persistencia de datos [student@workstation ~]$ cd /home/student/JB183/labs/persist-data [student@workstation persist-data]$ mvn wildfly:deploy Abra http://localhost:8080/persist-data/ en un explorador en la máquina virtual workstation y asegúrese de que la aplicación esté almacenando y mostrando correctamente las direcciones de correo electrónico de los miembros. 9. Anule la implementación de la aplicación y detenga el servidor. [student@workstation persist-data]$ mvn wildfly:undeploy Referencias Para obtener más información, consulte la Guía de desarrollo para API de persistencia Java para Red Hat JBoss EAP 7, que se encuentra en https://docs.jboss.org/author/display/AS7/JPA+Reference+Guide/ JB183-EAP7.0-es-2-20180124 167 Capítulo 4. Gestión de la persistencia Ejercicio guiado: Persistencia de datos En este ejercicio, persistirá datos de la aplicación en la base de datos. Resultados Deberá poder crear una clase de entidad y persistir datos de entidad. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos del trabajo de laboratorio necesarios para este ejercicio guiado. [student@workstation ~]$ lab persist-entity setup Pasos 1. Importe el proyecto persist-entity en el IDE de JBoss Developer Studio (JBDS). 1.1. Inicie JBDS haciendo doble clic en el icono del escritorio de la máquina virtual workstation. 1.2. En la ventana Eclipse Launcher, ingrese /home/student/JB183/workspace en el campo Workspace (Espacio de trabajo) y, luego, haga clic en Launch (Iniciar). 1.3. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.4. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta persist-entity y haga clic en OK (Aceptar). 1.6. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.7. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. nota El proyecto persist-entity tiene un error de compilación cuando se importa. Este error se resuelve en los siguientes pasos del trabajo de laboratorio. 2. 168 Convierta una clase Person simple de Java en una clase de entidad. JB183-EAP7.0-es-2-20180124 2.1. En la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione persist-entity > Java Resources > src/main/java > com.redhat.training.model y expanda el paquete. 2.2. Haga doble clic en el archivo Person.java del paquete com.redhat.training.model seleccionado para abrir la clase en el editor. 2.3. Agregue la anotación @Entity en la clase Person en el paquete com.redhat.training.model. Agregue la anotación @Entity e importe la librería javax.persistence.Entity. //add the required libraries import javax.persistence.Entity; //add @Entity annotation to the Person class @Entity public class Person { ..... } nota Agregar la anotación @Entity crea un error de compilación. Este error se puede ignorar y se resolverá en un paso posterior. 2.4. La clase de entidad Person debe implementar la interfaz Serializable. Importe e implemente la interfaz Serializable. import javax.persistence.Entity; import java.io.Serializable; @Entity public class Person implements Serializable { ..... } 2.5. Agregue las anotaciones @Id y @GeneratedValue(strategy = GenerationType.IDENTITY) en el atributo id de la clase Person para convertirla en una clave primaria con la estrategia de generación de clave IDENTITY. Agregue la anotación @Column(name="name") en el atributo personName para asignarla al campo name en la tabla de la base de datos. Importe las librerías requeridas. ... import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Column; @Entity public class Person implements Serializable { //add annotations for primary key @Id JB183-EAP7.0-es-2-20180124 169 Capítulo 4. Gestión de la persistencia @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; //add @Column(name="name") annotation to map column in database table @Column(name="name") private String personName; .... } 2.6. Presione Ctrl+S para guardar sus cambios. 3. Abra la clase PersonService en el paquete com.redhat.training.services y agregue la funcionalidad de persistencia para guardar Person en la base de datos y buscar a una persona de la base de datos. 3.1. En la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione persist-entity > Java Resources > src/main/java > com.redhat.training.services y expanda el paquete. 3.2. Haga doble clic en el archivo PersonService.java del paquete com.redhat.training.services para abrir la clase PersonService en el panel del editor. 3.3. Se requiere el objeto EntityManager para realizar las operaciones de persistencia en la clase PersonService. Agregue una anotación @PersistenceContext para obtener un objeto EntityManager: import javax.persistence.PersistenceContext; import javax.persistence.EntityManager; @Stateless public class PersonService { //TODO: obtain an EntityManager instance using @PersistenceContext @PersistenceContext(unitName="hello") private EntityManager entityManager; ... } 3.4. Persista una clase Person en la base de datos mediante el administrador de entidades y agregue el siguiente código en el método public String hello(String name), de la siguiente manera: public String hello(String name) { ... // call persist() method of entity manager to save the data entityManager.persist(p); ... } 3.5. Busque el nombre de una persona mediante el uso de id y agregue el método getPerson(Long id) en la clase PersonService. En la instrucción de devolución, use el método find() del administrador de entidades para devolver el atributo name de Person en función del id. // TODO:add public String getPerson(Long id) method here to fetch result //by Person id using find() method 170 JB183-EAP7.0-es-2-20180124 public String getPerson(Long id) { return entityManager.find(Person.class, id).getPersonName(); } 3.6. Observe el método getAllPersons() que devuelve todos los objetos Person almacenados en la base de datos: // Get all Person objects in the Database public List<Person> getAllPersons() { TypedQuery<Person> query = entityManager.createQuery("SELECT p FROM Person p", Person.class); List<Person> persons = query.getResultList(); return persons; } 3.7. Presione Ctrl+S para guardar sus cambios. 4. Abra la clase Hello en el paquete com.redhat.training.ui. Elimine el comentario de los métodos getPerson() y getPersons() para agregar la funcionalidad de interfaz de usuario para ver el nombre de una única persona y todos los nombres almacenados en la base de datos. 4.1. En la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione persist-entity > Java Resources > src/main/java > com.redhat.training.ui y expanda el paquete. 4.2. Haga doble clic en el archivo Hello.java del paquete com.redhat.training.ui seleccionado. La clase Hello se abre en el panel del editor. 4.3. Elimine los comentarios del método public void getPerson(). public void getPerson() { try { String response = personService.getPerson(id); FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(response)); }catch(Exception e){ System.out.println(e.getCause()); if(e.getCause() != null && e.getCause() instanceof ConstraintViolationException) { ConstraintViolationException ex = (ConstraintViolationException) e.getCause(); String violations = ""; for(ConstraintViolation<?> cv: ex.getConstraintViolations()) { violations += cv.getMessage() + "\n"; System.out.println("Violations: "+violations); } FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(violations)); } } } JB183-EAP7.0-es-2-20180124 171 Capítulo 4. Gestión de la persistencia 4.4. Elimine los comentarios del método public List<Person> getPersons(). public List<Person> getPersons() { return personService.getAllPersons(); } 5. Compile e implemente la aplicación. 5.1. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde de inicio para iniciar el servidor. 5.2. Compile la aplicación persist-entity mediante los siguientes comandos en la ventana del terminal: [student@workstation ~]$ cd /home/student/JB183/labs/persist-entity [student@workstation persist-entity]$ mvn clean package 5.3. Implemente la aplicación persist-entity mediante el siguiente comando en la ventana del terminal: [student@workstation persist-entity]$ mvn wildfly:deploy 6. Pruebe la persistencia de la aplicación. 6.1. Use un explorador web en la máquina virtual workstation para navegar a http:// localhost:8080/persist-entity/ para acceder a la aplicación persist-entity. Figura 4.4: La aplicación persist-entity 6.2. Ingrese Samuel en el campo Enter your name (Ingrese su nombre) y haga clic en Submit (Enviar). 172 JB183-EAP7.0-es-2-20180124 6.3. Verifique que el servidor procese la entrada y responda con un saludo utilizando el nombre que usted ingresó, así como la hora actual en el servidor. Figura 4.5: La respuesta de la aplicación persist-entity 6.4. Repita el paso anterior al menos 2 veces más, ingresando diferentes nombres para completar la base de datos. 6.5. Haga clic en View all names (Ver todos los nombres) para comprobar que todos los nombres estén almacenados en la base de datos. Figura 4.6: La lista de todos los nombres Haga clic en el enlace Atrás [/persist-entity/index.xhtml] para ir a la página de inicio. 6.6. Para buscar el nombre de una persona individual en la base de datos, haga clic en el enlace Search a name (Buscar un nombre). JB183-EAP7.0-es-2-20180124 173 Capítulo 4. Gestión de la persistencia Figura 4.7: Búsqueda de un nombre individual en la base de datos 6.7. Introduzca el valor de un id en el campo Enter Id: (Ingresar id:) y haga clic en Submit (Enviar). Figura 4.8: Visualización de un nombre individual de la base de datos Haga clic en Back (Atrás) para ir a la página de inicio. 7. Anule la implementación de la aplicación y detenga EAP. 7.1. En la ventana del terminal donde ejecutó el comando Maven para implementar la aplicación, ejecute el siguiente comando para anular la implementación de la aplicación: [student@workstation persist-entity]$ mvn wildfly:undeploy 174 JB183-EAP7.0-es-2-20180124 7.2. Haga clic con el botón derecho en el proyecto persist-entity en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrar este proyecto. 7.3. Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y haga clic en Stop (Detener) para detener la instancia EAP. Esto concluye el ejercicio guiado. JB183-EAP7.0-es-2-20180124 175 Capítulo 4. Gestión de la persistencia Anotación de clases para validar beans Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de realizar lo siguiente: • Explicar la validación de beans. • Usar las restricciones y anotaciones de la validación de beans. • Comparar la invocación automática y manual. Validación de beans Las aplicaciones de Java almacenan datos en objetos de Java. Estos objetos de Java viajan a través de la red, enviados como argumentos a métodos, y están presentes en las diferentes capas de una aplicación Java EE. Para mantener la integridad de los datos, la validación de datos es un requisito importante en la lógica de aplicaciones. Los desarrolladores deben escribir código de validación de datos en las diferentes capas de la aplicación para la validación de datos, que es propensa a errores y lleva mucho tiempo. La especificación de la API de validación de beans se proporciona para evitar la duplicación de códigos y simplificar la validación de datos. La validación de beans es un modelo para validar datos en objetos de Java al usar anotaciones incorporadas y personalizadas que pueden aplicar restricciones predefinidas. La validación de beans es común a todas las capas de aplicaciones web Java EE y Java. Java proporciona la API 1.1 de validación de beans en JSR 349. JPA admite validación de tiempo de ejecución de clases de entidad a través de API de validación de beans. JBoss EAP cumple por completo con JSR 349. Restricciones y anotaciones de la validación de beans Las restricciones de validación son las reglas aplicadas para validar datos. Estas restricciones se aplican en la forma de anotaciones para atributos, métodos, propiedades o constructores. La API 1.1 de validación de beans permite el uso de restricciones de validación en argumentos y devuelve valores de métodos y constructores. Java proporciona restricciones incorporadas y también admite restricciones personalizadas definidas por el usuario. El paquete javax.validation.constraints contiene varias restricciones incorporadas. Algunas anotaciones comunes: Anotación Descripción @NotNull La anotación @NotNull verifica que el valor en el campo o la propiedad no sea nulo. @NotNull private String itemName; @Null La anotación @Null verifica que el valor en el campo o la propiedad sea nulo. @Null private String comments; @Size La anotación @Size verifica que el tamaño del campo sea entre el mín. y el máx., incluidos los valores límite. 176 Ejemplo @Size (min=3, max=40) private String name; JB183-EAP7.0-es-2-20180124 Restricciones y anotaciones de la validación de beans Anotación Descripción @Min La anotación @Min verifica que el valor en el campo o la propiedad sea superior o equivalente al valor definido en el Mín. El valor es un elemento requerido del tipo long. @Max La anotación @Max verifica que el valor en el campo o la propiedad sea inferior o equivalente al valor definido en el Máx. El valor es un elemento requerido del tipo long. @Digits La anotación @Digits verifica la precisión y escala del campo. El campo debe ser un número dentro del rango especificado. El rango se define por el integer (número entero) y los elementos de fraction (fracción). Un número entero y la fracción son elementos requeridos de un tipo int. @DecimalMin La anotación @DecimalMín verifica si el valor en el campo o la propiedad es un valor decimal superior o equivalente al valor definido en el DecimalMín. El valor es un elemento requerido del tipo String. @DecimalMax La anotación @DecimalMáx verifica si el valor en el campo o la propiedad es un valor decimal superior o equivalente al valor definido en el DecimalMáx. El valor es un elemento requerido del tipo String. @Future La anotación @Future verifica si el valor en el campo o la propiedad es una fecha futura. @Future private Date promotionDate; @Past La anotación @Past verifica si el valor en el campo o la propiedad es una fecha pasada. @Past private Date startDate; @Pattern La anotación @Pattern verifica si el valor en el, campo o la propiedad coincide con la expresión regexp. La expresión regular es un elemento requerido del tipo String. JB183-EAP7.0-es-2-20180124 Ejemplo @Min(100) private int minStock; @Max(1000) private int maxStock; @Digits (integer=7, fraction=2) private double monthlySale; @DecimalMin("8.5") private double minTax; @DecimalMax("19.5") private double maxTax; @Pattern (regexp="\\(\\d{3}\\)\ \d{3}-\\d{4}") private String phoneNumber; 177 Capítulo 4. Gestión de la persistencia Anotación Descripción @AssertFalse La anotación @AssertFalse verifica si el valor en el campo o la propiedad es falso. @AssertTrue La anotación @AssertTrue verifica si el valor en el campo o la propiedad es falso. Ejemplo @AssertFalse private boolean isAvailable; @AssertTrue private boolean isOrdered; Todas las anotaciones de validación de beans tienen atributos opcionales, como el atributo message (mensaje), el cual se puede usar para mostrar un mensaje personalizado si no se puede realizar la validación. Algunas anotaciones tienen atributos requeridos. Por ejemplo, la anotación DecimalMáx. tiene un atributo de valor de tipo String para representar un valor máximo. Los siguientes son algunos ejemplos: @NotNull con el atributo message (mensaje) puede tener un mensaje personalizado que se puede mostrar en lugar de un mensaje predeterminado si no se puede realizar la validación. @NotNull(message="Address cannot be a null value") private Address address; Invocación automática en relación a la manual Invocación automática El servidor de aplicaciones Java EE 7 proporciona el paquete Hibernate Validator, que incluye anotaciones de validación de beans, así como una invocación automática de las restricciones de validación. Al adjuntar una anotación en un campo de entidad, Hibernate valida automáticamente que los datos coincidan con las restricciones de anotación colocadas en los campos. Por ejemplo, el siguiente snippet de código demuestra el uso de una restricción @Size(min=4), aplicándola al atributo personName de la clase Person (Persona). Al crear una instancia de una entidad, si los datos presentados no cumplen con la restricción de validación, en este caso en que el tamaño de la Cadena es de al menos cuatro caracteres, se devuelve un error. El servidor de aplicaciones y el marco (framework) del validador verifican automáticamente la restricción antes de persistir una entidad a una base de datos. ... //Using validation in constructor public Person(@NotNull String personName){ this.personName=personName; } //using validation in method parameter public void setPersonName(@Size(min=4) String name){ this.personName=personName; } ... Invocación manual Si bien muchos marcos (frameworks) validan automáticamente campos de entidad basados en estas anotaciones de validación, ocasionalmente, los desarrolladores necesitan desencadenar la validación de beans por programación. Para validar la instancia de una entidad por programación, use la API javax.validation.Validator. La interfaz de validator proporciona métodos para validar una entidad entera o una única propiedad 178 JB183-EAP7.0-es-2-20180124 Invocación automática en relación a la manual de una entidad. El siguiente código ilustra cómo crear una instancia ValidatorFactory y Validator y usa el validator para validar un objeto. ... Person p = new Person(); p.setPersonName("RH"); ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); Validator validator = validatorFactory.getValidator(); Set<ConstraintViolation<Person>> constraintViolations = validator.validate(p); ... Este código crea un conjunto de ContstraintViolations que se pueden iterar para ver todas las violaciones que se producen en función de las anotaciones de la entidad. El siguiente es un ejemplo de iteración del conjunto constraintViolations y registro de cada error: for (ConstraintViolation<Person> cv : constraintViolations) { log.error(cv.getMessage()); } Referencias API de validación de beans http://beanvalidation.org/ Referencias Para obtener más información, consulte la guía JBoss Application Server 7 para Red Hat JBoss EAP 7 en https://docs.jboss.org/author/display/AS71/Documentation/ JB183-EAP7.0-es-2-20180124 179 Capítulo 4. Gestión de la persistencia Ejercicio guiado: Validación de datos En este ejercicio, validará datos en una clase de entidad usando validación de beans. Resultados Deberá ser capaz de validar un campo de beans mediante restricciones incorporadas. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos del trabajo de laboratorio necesarios para este ejercicio guiado. [student@workstation ~]$ lab validate-data setup Pasos 1. Importe el proyecto validate-data en el IDE de JBoss Developer Studio (JBDS). 1.1. Inicie JBDS haciendo doble clic en el icono del escritorio de la máquina virtual workstation. 1.2. En la ventana Eclipse Launcher, ingrese /home/student/JB183/workspace en el campo Workspace (Espacio de trabajo) y, luego, haga clic en Launch (Iniciar). 1.3. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.4. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta validate-data y haga clic en OK (Aceptar). 1.6. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.7. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Agregue las restricciones de validación a la clase de entidad Person para garantizar que el personName no quede en blanco. 2.1. En la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione validate-data > Java Resources > src/main/java > com.redhat.training.model y expanda el paquete. 2.2. Haga doble clic en el archivo Person.java del paquete com.redhat.training.model seleccionado para abrir la clase en el editor. 2.3. Importe la librería javax.validation.constraints.NotNull y agregue la siguiente anotación @NotNull al atributo personName en la clase Person: 180 JB183-EAP7.0-es-2-20180124 //ToDo:add the validation imports import javax.validation.constraints.NotNull; ..... @Entity public class Person implements Serializable{ .... //TODO: Add validation constraints here @NotNull(message="Name cannot be blank") @Column(name="name") private String personName; ..... } 2.4. Presione Ctrl+S para guardar sus cambios. 3. Compile e implemente la aplicación. 3.1. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde de inicio para iniciar el servidor. 3.2. Compile la aplicación validate-data mediante los siguientes comandos en la ventana del terminal: [student@workstation ~]$ cd /home/student/JB183/labs/validate-data [student@workstation validate-data]$ mvn clean package 3.3. Implemente la aplicación validate-data mediante el siguiente comando en la ventana del terminal: [student@workstation validate-data]$ 4. mvn wildfly:deploy Pruebe la aplicación para la validación del campo en blanco. 4.1. Use un explorador web en la máquina virtual workstation para navegar a http:// localhost:8080/validate-data/ para acceder a la aplicación validate-data. 4.2. No ingrese ningún dato en el campo Enter your name (Ingrese su nombre) y haga clic en Submit (Enviar). 4.3. Verifique que el campo esté en blanco y vea la respuesta con un mensaje de error Name cannot be blank (El nombre no puede estar en blanco). JB183-EAP7.0-es-2-20180124 181 Capítulo 4. Gestión de la persistencia Figura 4.9: La respuesta de nombre en blanco de la aplicación validate-data 4.4. Ingrese James en el campo Enter your name (Ingrese su nombre) y haga clic en Submit (Enviar). 4.5. Verifique que el servidor procese la entrada y responda con un saludo utilizando el nombre que usted ingresó. Figura 4.10: La respuesta de la aplicación validate-data 5. Agregue una restricción de validación a la clase de entidad Person para probar que el personName no sea de menos de dos caracteres. 5.1. Importe la librería javax.validation.constraints.Size. Agregue la anotación @Size(min=2, message="Name cannot be less than 2 characters") al atributo personName en la clase Person. //ToDo:add the validation imports import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; .... 182 JB183-EAP7.0-es-2-20180124 @Entity public class Person implements Serializable{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; //TODO: Add validation constraints here @NotNull(message="Name cannot be blank") @Size( min=2, message="Name cannot be less than 2 characters" ) @Column(name="name") private String personName; ..... } 5.2. Presione Ctrl+S para guardar sus cambios. 6. Vuelva a compilar e implementar la aplicación. 6.1. Vuelva a compilar la aplicación validate-data mediante los siguientes comandos en la ventana del terminal: [student@workstation validate-data]$ mvn clean package 6.2. Vuelva a implementar la aplicación validate-data mediante el siguiente comando en la ventana del terminal: [student@workstation validate-data]$ 7. mvn wildfly:deploy Pruebe la aplicación para la validación del tamaño del campo del nombre. 7.1. Use un explorador web en la máquina virtual workstation para navegar a http:// localhost:8080/validate-data/ para acceder a la aplicación validate-data. 7.2. Ingrese un carácter en el campo Enter your name (Ingrese su nombre) y haga clic en Submit (Enviar). 7.3. Verifique que el mensaje Name cannot be less than 2 characters (El nombre no puede tener menos de dos caracteres) se muestre como respuesta. JB183-EAP7.0-es-2-20180124 183 Capítulo 4. Gestión de la persistencia Figura 4.11: La validación de longitud de la aplicación validate-data 7.4. Ingrese Al en el campo Enter your name (Ingrese su nombre) y haga clic enSubmit (Enviar). Verifique que el servidor procese la entrada y responda con un saludo utilizando el nombre que usted ingresó y la hora actual en el servidor. 8. Anule la implementación de la aplicación y detenga EAP. 8.1. En la ventana del terminal donde ejecutó el comando Maven para implementar la aplicación, ejecute el siguiente comando para anular la implementación de la aplicación: [student@workstation validate-data]$ mvn wildfly:undeploy 8.2. Haga clic con el botón derecho en el proyecto validate-data del Project Explorer (Explorador de proyectos) y seleccione Close Project para cerrar este proyecto. 8.3. Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y haga clic en Stop (Detener) para detener la instancia EAP. Esto concluye el ejercicio guiado. 184 JB183-EAP7.0-es-2-20180124 Creación de consultas Creación de consultas Objetivo Tras completar esta sección, los estudiantes deberán poder crear consultas mediante Java Persistence Query Language. Creación de consultas Java Persistence Query Language (JPQL) es un lenguaje de consultas que no depende de plataformas y que está definido como parte de la especificación JPA para realizar consultas en entidades de manera orientada al objeto. JPQL es similar a SQL en sintaxis, pero las consultas de JPQL se expresan en términos de entidades Java en lugar de tablas y columnas de una base de datos. Los proveedores de JPA, como Hibernate, transforman consultas de JPQL a SQL. JPQL admite las instrucciones SELECT, UPDATE y DELETE. Para comprender cómo crear diferentes tipos de consultas con JPQL, observe un ejemplo de una clase de la entidad Employee: @Entity public class Employee implements Serializable{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String empName; private double salary; ... } Una tabla Employee se crea en una base de datos para una entidad Employee. Debajo se muestran los datos de muestra de todos los empleados de una tabla Employee: MariaDB [todo]> select * from Employee; +----+---------+--------+ | id | empName | salary | +----+---------+--------+ | 1 | Tim | 120000 | | 2 | Joe | 100000 | | 3 | Tom | 125000 | | 4 | Mia | 135000 | | 5 | Matt | 145000 | | 6 | Kim | 115000 | | 7 | Nita | 117000 | +----+---------+--------+ 7 rows in set (0.00 sec) La consulta JPQL recuperará registros de todos los empleados de la base de datos de la siguiente manera: SELECT e FROM Employee e; La API EntityManager admite métodos para crear consultas estáticas y dinámicas. El método createNamedQuery se utiliza para crear consultas estáticas, mientras que el método createQuery se utiliza para crear consultas dinámicas. JB183-EAP7.0-es-2-20180124 185 Capítulo 4. Gestión de la persistencia Una aplicación crea las consultas dinámicas en el tiempo de ejecución mediante el siguiente proceso: 1. Cree una cadena que contenga una consulta JPQL. 2. Envíe la cadena al método createQuery del administrador de entidades y almacene el objeto Query devuelto. 3. Use el método getResultList() de la consulta para ejecutar la consulta y devolver las filas seleccionadas de la base de datos. ... String simpleQuery="SELECT e from Employee e"; Query query=entityManager.createQuery(simpleQuery); List<Employee> persons = query.getResultList(); ... La consulta recupera todos los empleados de una tabla de base de datos. 1, 2, 3, 4, 5, 6, 7, Tim, 120000.0 Joe, 100000.0 Tom, 125000.0 Mia, 135000.0 Matt, 145000.0 Kim, 115000.0 Nita, 117000.0 Las funciones de la base de datos como LOWER, UPPER, LENGTH, así como las funciones aritméticas, también se pueden aplicar a las consultas JPQL: ... public List<String> getAllNames(){ Query query=entityManager.createQuery("SELECT UPPER(e.empName) from Employee e"); List<String> names = query.getResultList(); return names; } Este código muestra todos los nombres Employee en mayúsculas: Output: TIM JOE TOM MIA MATT KIM NITA Para brindar seguridad de tipos, JPA también admite la clase TypedQuery<?>, que permite el tipo estático de consultas a fin de evitar problemas con la difusión de resultados. Para crear una TypedQuery, envíe la clase que debe coincidir con el tipo de resultados de consulta al método createQuery. En el siguiente ejemplo se muestra una TypedQuery con seguridad de tipos: 186 JB183-EAP7.0-es-2-20180124 Creación de consultas TypedQuery<Employee> query=entityManager.createQuery("SELECT e from Employee e where e.salary >?1 or e.empName=?2", Employee.class); JPQL también admite funciones aritméticas en las consultas. El siguiente es un ejemplo para obtener la suma y un promedio de los salarios de todos los empleados: ... public String[] getSumAndAvgSalary(){ Query query=entityManager.createQuery("SELECT sum(e.salary), round(avg(e.salary),2) from Employee e"); Object[] sal =(Object[])query.getSingleResult(); String[] s= {"Sum of all salaries :"+sal[0],"avg of all salaries : "+sal[1]}; return s; } Este código genera la siguiente salida en función del conjunto de datos proporcionado: Output: Sum of all salaries: 857000.0 Average of all salaries: 122428.57 Observe que, en el ejemplo anterior, la consulta contiene dos campos: uno para la suma de los salarios y el otro para el salario promedio. Cuando se devuelven varios campos como resultado de la consulta, el método getSingleResult devuelve un arreglo Object. Los resultados de las consultas se filtran mediante la cláusula WHERE en las consultas. La cláusula WHERE se utiliza para definir las condiciones de los datos que la consulta devuelve. Para crear una expresión condicional para la consulta, se pueden usar varios operadores. Los operadores disponibles en SQL también están disponibles en JPQL. Los operandos en la condición dependen de la expresión. Los operadores más comunes son los siguientes: • <, =, >, <=, >=, <> se utilizan para comparar los valores aritméticos. • IN y NOT IN se utilizan para todos los tipos. El operador IN se utiliza para determinar si los datos de un campo son uno de los valores proporcionados en una lista de valores. • LIKE y NOT LIKE se utilizan para los valores de cadena. Se utiliza para determinar si los datos de un campo coinciden con una secuencia de caracteres proporcionados en la cadena. • BETWEEN y NOT BETWEEN se utilizan para valores aritméticos, fecha, hora y cadena. Se utiliza para determinar si los datos de un campo residen en un rango de valores predeterminado. • MEMBER OF, NOT MEMBER OF, IS EMPTY e IS NOT EMPTY se utilizan para los tipos Collection. Puede usar caracteres especiales como _ (guión bajo) para cualquier carácter único y un carácter % para cualquier secuencia de caracteres para compilar las expresiones de cadena. A continuación se muestra un ejemplo simple de una consulta con una cláusula WHERE para imprimir todos los empleados cuyo salario es superior a 120000: Query query=entityManager.createQuery("SELECT e from Employee e where e.salary >120000"); JB183-EAP7.0-es-2-20180124 187 Capítulo 4. Gestión de la persistencia List<Employee> persons = query.getResultList(); Este código produce la siguiente salida en función del conjunto de datos proporcionado: Output: 3, Tom, 125000.0 4, Mia, 135000.0 5, Matt, 145000.0 Parámetros nombrados en consultas Se pueden crear consultas con la cláusula WHERE con los parámetros nombrados en JPQL. Un parámetro nombrado es un parámetro de consulta que funciona como marcador para los valores reales. Una consulta se puede reutilizar y ejecutar con un conjunto diferente de datos proporcionado en el tiempo de ejecución para un parámetro nombrado. Un parámetro nombrado está prefijado con un carácter :. Un parámetro nombrado se vincula a los argumentos mediante el uso del método setParameter() de la API javax.persistence.Query. El primer parámetro del método setParameter() es el nombre de un parámetro nombrado. El segundo parámetro es el valor del parámetro nombrado. Un ejemplo de una consulta JPQL con un parámetro nombrado es el siguiente: ... public List<Employee> getEmployeesWithGreaterSalary(double salary) { Query query=entityManager.createQuery("SELECT e from Employee e where e.salary >:sal"); query.setParameter("sal", salary); List<Employee> persons = query.getResultList(); return persons; } Cuando un usuario ingresa el valor 115000 para el salario, el código devuelve la siguiente salida: Output: 1, 3, 4, 5, 7, Tim, 120000.0 Tom, 125000.0 Mia, 135000.0 Matt, 145000.0 Nita, 117000.0 Parámetros posicionales en consultas Los parámetros posicionales son parámetros de consulta en la forma de un índice o la posición ordinal de un parámetro en la consulta. Se pueden enviar a consultas como una alternativa a los parámetros nombrados, según la preferencia del desarrollador para la legibilidad. Los parámetros posicionales tienen el prefijo ? seguido de la posición numérica del parámetro en la consulta. El método setParameter() se utiliza para vincular un parámetro posicional con una consulta. Los valores de un parámetro posicional se brindan en el tiempo de ejecución. El primer parámetro del método setParameter() es la posición de un parámetro en la 188 JB183-EAP7.0-es-2-20180124 Consultas nombradas consulta y el segundo parámetro es una variable que contiene el valor del parámetro. A continuación se muestra un ejemplo de una consulta JPQL con un parámetro posicional donde el valor del salario para la consulta se proporciona en el primer parámetro posicional como ?1: ... public List<Employee> getAllPersonsWithPositionParam(double salary) { Query query=entityManager.createQuery("SELECT e from Employee e where e.salary >?1"); query.setParameter(1, salary); return query.getResultList(); } Cuando el usuario introduce el valor 130000 para el salario, el código produce la siguiente salida según el conjunto de datos proporcionado: Output: 4, Mia, 135000.0 5, Matt, 145000.0 A continuación se muestra un ejemplo de una consulta con seguridad de tipos con dos parámetros posicionales, donde el primer parámetro posicional ?1 hace referencia al salario y el segundo parámetro posicional ?2 hace referencia al nombre de la consulta: ... public List<Employee> getAllPersons(double salary, String name) { TypedQuery<Employee> query=entityManager.createQuery("SELECT e from Employee e where e.salary >?1 or e.empName=?2", Employee.class); query.setParameter(1, salary); query.setParameter(2, name); return query.getResultList(); } Cuando el usuario introduce los valores 130000 para el salario y Tim para el nombre, el código produce la siguiente salida: Output: 1, Tim, 120000.0 4, Mia, 135000.0 5, Matt, 145000.0 Consultas nombradas Una consulta nombrada es una consulta predefinida que está adjunta a una entidad. Las consultas nombradas se analizan en el inicio para que los errores se puedan detectar rápidamente. Otra ventaja es que el código y las consultas están separadas. Las consultas nombradas se definen al utilizar la anotación javax.persistence.NamedQuery. La anotación @namedQuery se puede aplicar en el nivel de la clase de la entidad. La anotación @NamedQuery tiene cuatro elementos: name, query, hints y lockMode. • name es un elemento requerido de la anotación NamedQuery. Define el nombre utilizado por los métodos EntityManager para hacer referencia a una consulta. El nombre de la consulta nombrada debe ser único, ya que el alcance de la consulta nombrada es la unidad de persistencia. JB183-EAP7.0-es-2-20180124 189 Capítulo 4. Gestión de la persistencia • query es un elemento requerido de la anotación NamedQuery y representa la cadena de la consulta JPQL. • El elemento hints es un elemento opcional de la anotación NamedQuery. Representa sugerencias y propiedades de la consulta. Estas sugerencias pueden ser específicas del proveedor. • El elemento lockMode es un elemento opcional de la anotación NamedQuery. Representa el tipo de modo de bloqueo para usar en la ejecución de la consulta. Cuando el tipo de modo de bloqueo está definido como cualquier valor diferente a NONE, la consulta se debe ejecutar en una transacción. La consulta nombrada para ver todos los empleados con un salario superior al valor ingresado por el usuario se define en la clase de la entidad Employee: @Entity @NamedQuery( name="getAllEmployees", query="select e from Employee e where e.salary > :sal") public class Employee implements Serializable{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String empName; private double salary; ... Para ejecutar la consulta nombrada, use el método createNamedQuery() de EntityManager para crear la consulta y definir los parámetros: ... public List<Employee> getPersonsWithNamedQuery(double salary) { Query query=entityManager.createNamedQuery("getAllEmployees") query.setParameter("sal", salary); retrun query.getResultList(); } Para definir más de una consulta nombrada para una entidad, se utiliza la anotación @NamedQueries. Actúa como un contenedor para varias consultas; la anotación @NamedQueries se aplica en el nivel de la clase de la entidad. @Entity @NamedQueries({ @NamedQuery(name="getAllEmployees", query="select e from Employee e where e.salary > :sal"), @NamedQuery(name="getEmployeesWithSalaryOrName", query="select e from Employee e where e.salary > :sal or e.empName=:name") }) public class Employee implements Serializable{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String empName; private double salary; .... 190 JB183-EAP7.0-es-2-20180124 Consultas nombradas El método createNamedQuery() crea la consulta. Los parámetros se definen de la siguiente manera: public List<Employee> getAllPersonsWithNamedQueries(double salary, String ename) { Query query=entityManager.createNamedQuery("getEmployeesWithSalaryOrName"); query.setParameter("sal", salary); query.setParameter("name", ename); List<Employee> persons = query.getResultList(); return persons; } JB183-EAP7.0-es-2-20180124 191 Capítulo 4. Gestión de la persistencia Ejercicio guiado: Creación de consultas En este ejercicio, creará consultas con parámetros nombrados y parámetros posicionales, así como una consulta nombrada para recuperar datos de la base de datos. Resultados Deberá poder crear consultas nombradas y crear consultas con parámetros nombrados y posicionales. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos del trabajo de laboratorio necesarios para este ejercicio guiado. [student@workstation ~]$ lab create-queries setup Pasos 1. Importe el proyecto create-queries en el IDE de JBoss Developer Studio (JBDS). 1.1. Inicie JBDS haciendo doble clic en el icono del escritorio de la máquina virtual workstation. 1.2. En la ventana Eclipse Launcher, ingrese /home/student/JB183/workspace en el campo Workspace (Espacio de trabajo) y, luego, haga clic en Launch (Iniciar). 1.3. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.4. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta create-queries y haga clic en OK (Aceptar). 1.6. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.7. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Agregue un método getAllPersons() en la clase PersonService para ver todas las personas de la tabla de la base de datos Person. 2.1. En la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione create-queries > Java Resources > src/main/java > com.redhat.training.services y expanda el paquete. 2.2. Haga doble clic en el archivo PersonService.java del paquete com.redhat.training.services para abrir la clase PersonService en el panel del editor. 192 JB183-EAP7.0-es-2-20180124 2.3. Para ver los datos de todas las personas en una tabla de base de datos, agregue un nuevo método getAllPersons con una consulta simple: // Get all Person objects in the Database public List<Person> getAllPersons() { TypedQuery<Person> query = entityManager.createQuery("SELECT p FROM Person p", Person.class); return query.getResultList(); } 2.4. Presione Ctrl+S para guardar sus cambios. 3. En el bean Hello.java del paquete com.redhat.training.ui, elimine el comentario del método getPersons() para agregar la funcionalidad para ver los nombres de todos los objetos Person de la base de datos. 3.1. En la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione create-queries > Java Resources > src/main/java > com.redhat.training.ui y expanda el paquete. 3.2. Haga doble clic en el archivo Hello.java del paquete com.redhat.training.ui para ver la clase en el panel del editor. 3.3. Elimine los comentarios del método getPersons(). //View all persons in the database table public List<Person> getPersons() { return personService.getAllPersons(); } 3.4. Presione Ctrl+S para guardar sus cambios. 4. Compile e implemente la aplicación. 4.1. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde de inicio para iniciar el servidor. 4.2. Compile la aplicación create-queries mediante los siguientes comandos en la ventana del terminal: [student@workstation ~]$ cd /home/student/JB183/labs/create-queries [student@workstation create-queries]$ mvn clean package 4.3. Implemente la aplicación create-queries mediante el siguiente comando en la ventana del terminal: [student@workstation create-queries]$ mvn wildfly:deploy 5. Pruebe la aplicación para ver todas las personas. JB183-EAP7.0-es-2-20180124 193 Capítulo 4. Gestión de la persistencia 5.1. Use un explorador web en la máquina virtual workstation para dirigirse a http:// localhost:8080/create-queries/ y acceder a la aplicación create-queries. 5.2. Haga clic en View All Users (Ver todos los usuarios) para ver una lista de los objetos Person en la base de datos. Figura 4.12: La lista de nombres en la base de datos 6. Cree una consulta con parámetros nombrados para capturar todos los objetos Person de la tabla de la base de datos que coinciden con un nombre específico. 6.1. Haga clic en el archivo PersonService.java para editar la clase PersonService en el panel del editor. 6.2. Agregue un nuevo método getPersonsWithName(String name) con un parámetro nombrado: //Get persons whose name matches the name given in the query public List<Person> getPersonsWithName(String name) { TypedQuery<Person> query = entityManager.createQuery("SELECT p from Person p where p.name =:pname", Person.class); query.setParameter("pname", name); return query.getResultList(); } 6.3. Presione Ctrl+S para guardar sus cambios. 7. En la clase Hello.java del paquete com.redhat.training.ui, elimine el comentario del método search() para ver los nombres que coinciden con la búsqueda del usuario. 7.1. Haga doble clic en el archivo Hello.java del paquete com.redhat.training.ui para ver la clase en el panel del editor. 7.2. Elimine los comentarios del método search(). //view all persons whose name matches the name given in the query public void search() { results = personService.getPersonsWithName(name); } 7.3. Presione Ctrl+S para guardar sus cambios. 194 JB183-EAP7.0-es-2-20180124 8. Implemente la aplicación create-queries mediante el siguiente comando en la ventana del terminal: [student@workstation create-queries]$ mvn wildfly:deploy 9. Pruebe la característica de búsqueda de la aplicación para verificar que la consulta devuelva objetos Person por nombre. 9.1. Use un explorador web en la máquina virtual workstation para dirigirse a http:// localhost:8080/create-queries/ y acceder a la aplicación create-queries. 9.2. Haga clic en Search Users (Buscar usuarios) para ir a la página de búsqueda. 9.3. En el campo Name (Nombre), ingrese un nombre que ya existe en la base de datos, como John, y haga clic en Submit (Enviar). Observe que la consulta devuelve ambas entradas para John. 10. Modifique la consulta existente para usar parámetros posicionales para capturar objetos Person de la tabla de la base de datos. 10.1.Haga clic en el archivo PersonService.java para editar la clase PersonService en el panel del editor. 10.2.Actualice el método getPersonsWithName() en la clase PersonService para utilizar parámetros posicionales: //Get persons whose name matches the name given in the query public List<Person> getPersonsWithName(String name) { TypedQuery<Person> query = entityManager.createQuery("SELECT p from Person p where p.name =?1", Person.class); query.setParameter(1, name); return query.getResultList(); } 10.3.Presione Ctrl+S para guardar sus cambios. 11. Implemente la aplicación create-queries mediante el siguiente comando en la ventana del terminal: [student@workstation create-queries]$ mvn wildfly:deploy 12. Pruebe la característica de búsqueda que usa una consulta de parámetro posicional. 12.1.Use un explorador web en la máquina virtual workstation para dirigirse a http:// localhost:8080/create-queries/ y acceder a la aplicación create-queries. 12.2.Haga clic en Search Users (Buscar usuarios) para ir a la página de búsqueda. En el campo Name (Nombre), ingrese un nombre que ya existe en la base de datos, como Nita, y haga clic en Submit (Enviar). Verifique que el servidor devuelva los resultados correctos. 13. Cree una consulta nombrada para capturar objetos Person de una tabla de base de datos. JB183-EAP7.0-es-2-20180124 195 Capítulo 4. Gestión de la persistencia 13.1.En la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione create-queries > Java Resources > src/main/java > com.redhat.training.model y expanda el paquete. 13.2.Haga doble clic en el archivo Person.java del paquete com.redhat.training.model para abrir la clase Person en el panel del editor. 13.3.Haga clic en el archivo Person.java para editar la clase Person en el panel del editor. 13.4.Agregue la siguiente anotación NamedQuery para crear una consulta nombrada para capturar los objetos Person por nombre: @Entity //add named query here @NamedQuery( name="getAllPersonsWithName", query="select p from Person p where p.name = :pname") public class Person{ @Id private Long id; private String name; ... } 13.5.Presione Ctrl+S para guardar sus cambios. 13.6.En la clase PersonService.java, actualice el método getPersonsWithName para utilizar la consulta nombrada getAllPersonsWithName: //Get persons whose name matches the name given in the query public List<Person> getPersonsWithName(String name) { TypedQuery query=entityManager.createNamedQuery("getAllPersonsWithName",Person.class); query.setParameter("pname", name); return query.getResultList(); } 13.7.Presione Ctrl+S para guardar sus cambios. 14. Implemente la aplicación create-queries mediante el siguiente comando en la ventana del terminal: [student@workstation create-queries]$ mvn wildfly:deploy 15. Pruebe la característica de búsqueda de la aplicación mediante una consulta nombrada. 15.1.Use un explorador web en la máquina virtual workstation para navegar a http:// localhost:8080/create-queries/ y acceder a la aplicación create-queries. 15.2.Haga clic en Search Users (Buscar usuarios) para ir a la página de búsqueda. En el campo Name (Nombre), ingrese un nombre que ya existe en la base de datos, como 196 JB183-EAP7.0-es-2-20180124 John, y haga clic en Submit (Enviar). Verifique que el servidor devuelva los resultados previstos. 16. Anule la implementación de la aplicación y detenga EAP. 16.1.En la ventana del terminal donde ejecutó el comando Maven para implementar la aplicación, ejecute el siguiente comando para anular la implementación de la aplicación: [student@workstation create-queries]$ mvn wildfly:undeploy 16.2.Haga clic con el botón derecho en el proyecto create-queries en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrar este proyecto. 16.3.Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y haga clic en Stop (Detener) para detener la instancia EAP. Esto concluye el ejercicio guiado. JB183-EAP7.0-es-2-20180124 197 Capítulo 4. Gestión de la persistencia Trabajo de laboratorio: Gestión de la persistencia En este trabajo de laboratorio, implementará persistencia en la aplicación To Do List mediante JPA. Resultado Deberá poder inyectar un administrador de entidades mediante el contexto de persistencia predeterminado y, luego, implementar la funcionalidad de persistencia mediante la API del administrador de entidades. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab persistence-lab setup 1. Abra JBDS e importe el proyecto persistence-lab ubicado en el directorio /home/ student/JB183/labs/persistence-lab. 2. Revise el nombre JNDI asignado al trabajo de laboratorio definido para MySQLDS en el archivo de configuración JBoss. 3. Revise el archivo persistence.xml que define el contexto de persistencia para la aplicación persistence-lab}. 4. Actualice la clase del modelo Item para que sea una entidad administrada por JPA. Marque el campo id como el campo del identificador único, así como un valor generado. 5. Inyecte un administrador de entidades que use el contexto de persistencia predeterminado en la clase ItemRepository. A continuación, actualice la clase para usar el administrador de entidades para implementar la funcionalidad del método findById para que ya no devuelva null. Además, actualice el método findAllItems para crear una consulta mediante JPQL que seleccione los ítems ordenados según si se completaron o no (utilizando el campo done [listo]). 6. Actualice la clase EJB ItemService para inyectar un administrador de entidades asociado con el contexto de persistencia predeterminado. A continuación, implemente los métodos register(Item item), remove(Long id) y update(Item item) para utilizar los métodos del administrador de entidades adecuados. Sugerencia: Para el método de eliminación, use el método ItemRepository.findById() para recuperar la clase Item primero; a continuación, puede eliminar la instancia mediante el administrador de entidades. 7. 198 Inicie JBoss EAP desde dentro de JBDS. JB183-EAP7.0-es-2-20180124 8. Compile e implemente la aplicación en JBoss EAP con Maven. 9. Pruebe la aplicación en un explorador. 9.1. Abra Firefox en la máquina virtual workstation y diríjase a http:// localhost:8080/persistence-lab para acceder a la aplicación. 9.2. Agregue al menos dos nuevos ítems pendientes mediante la interfaz de la aplicación To Do List. Si la persistencia funciona correctamente, los nuevos ítems se agregan a la lista. 9.3. Después de haber agregado correctamente los nuevos ítems, actualice su estado a Completados. Si la consulta funciona correctamente, los ítems marcados como done (listo) se mueven a la parte inferior de la lista. 10. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab persistence-lab grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 11. Realice la limpieza. 11.1.Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/persistence-lab [student@workstation persistence-lab]$ mvn wildfly:undeploy 11.2.Haga clic con el botón derecho en el proyecto persistence-lab en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrar este proyecto. 11.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 199 Capítulo 4. Gestión de la persistencia Solución En este trabajo de laboratorio, implementará persistencia en la aplicación To Do List mediante JPA. Resultado Deberá poder inyectar un administrador de entidades mediante el contexto de persistencia predeterminado y, luego, implementar la funcionalidad de persistencia mediante la API del administrador de entidades. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab persistence-lab setup 1. Abra JBDS e importe el proyecto persistence-lab ubicado en el directorio /home/ student/JB183/labs/persistence-lab. 1.1. Para abrir JBDS, haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo. Deje el espacio de trabajo predeterminado (/home/ student/JB183/workspace) y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta persistence-lab y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Revise el nombre JNDI asignado al trabajo de laboratorio definido para MySQLDS en el archivo de configuración JBoss. 2.1. Usando su editor de texto preferido, abra el archivo de configuración EAP en /opt/ eap/standalone/configuration/standalone-full.xml: [student@workstation ~]$ less /opt/eap/standalone/configuration/\ standalone-full.xml 2.2. Navegue al subsistema urn:jboss:domain:datasources:4.0: <subsystem xmlns="urn:jboss:domain:datasources:4.0"> <datasources> <datasource jndi-name="java:jboss/datasources/MySQLDS" pool-name="MySQLDS"> 200 JB183-EAP7.0-es-2-20180124 Solución <connection-url>jdbc:mysql://172.25.250.10:3306/todo</connection-url> <driver>mysql</driver> <security> <user-name>todo</user-name> <password>todo</password> </security> </datasource> </datasources> </subsystem> Observe que MySQLDS tiene una entrada JNDI de java:jboss/datasources/ MySQLDS. 2.3. Cierre el editor de texto y no guarde los cambios al archivo standalone-full.xml. Advertencia Pueden producirse problemas en el trabajo de laboratorio si se introducen cambios inesperados en el archivo de configuración. 3. Revise el archivo persistence.xml que define el contexto de persistencia para la aplicación persistence-lab}. Abra el archivo persistence.xml; para ello, expanda el ítem persistence-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en persistence-lab > Java Resources > src/main/resources > META-INF para expandirlo. Haga doble clic en el archivo persistence.xml. <?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http:// xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="Items" transaction-type="JTA"> <jta-data-source>java:jboss/datasources/MySQLDS</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="true" /> </properties> </persistence-unit> </persistence> 4. Actualice la clase del modelo Item para que sea una entidad administrada por JPA. Marque el campo id como el campo del identificador único, así como un valor generado. 4.1. Abra la clase Item; para ello, expanda el ítem persistence-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en persistence-lab > Java Resources > src/main/java > com.redhat.training.todo.model para expandirlo. Haga doble clic en el archivo Item. JB183-EAP7.0-es-2-20180124 201 Capítulo 4. Gestión de la persistencia 4.2. Agregue la anotación @Entity en el nivel de la clase para marcar la clase Item como una entidad administrada por JPA que se asigna a una tabla de base de datos nombrada "Item": //TODO mark the Item class as an entity to be managed by JPA, mapped to a table named 'Item' @Entity public class Item { 4.3. Agregue las anotaciones @Id y @GeneratedValue en el campo id para marcarlo como el identificador único para JPA. @Entity public class Item { //TODO mark this field as the unique identifier for JPA as well as a generated value @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; 4.4. Guarde los cambios usando Ctrl+S. 5. Inyecte un administrador de entidades que use el contexto de persistencia predeterminado en la clase ItemRepository. A continuación, actualice la clase para usar el administrador de entidades para implementar la funcionalidad del método findById para que ya no devuelva null. Además, actualice el método findAllItems para crear una consulta mediante JPQL que seleccione los ítems ordenados según si se completaron o no (utilizando el campo done [listo]). 5.1. Abra la clase ItemRepository; para ello, expanda el ítem persistence-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en persistence-lab > Java Resources > src/main/java > com.redhat.training.todo.data para expandirlo. Haga doble clic en el archivo ItemRepository. 5.2. Inyecte un administrador de entidades que utilice el contexto de persistencia predeterminado mediante la anotación @PersistenceContext: @ApplicationScoped public class ItemRepository { //TODO Inject EntityManager using the default persistence context @PersistenceContext private EntityManager em; 5.3. Implemente el método findById mediante el método find del administrador de entidades: public Item findById(Long id) { 202 JB183-EAP7.0-es-2-20180124 Solución //TODO use the entity manager to implement the findById method return em.find(Item.class, id); } 5.4. Actualice el método findAllItems para crear una consulta mediante JPQL que utilice la palabra clave ORDER BY para ordenar los resultados: public List<Item> findAllItems() { //TODO Create a query to select all the items in order by whether or not they are done TypedQuery<Item> query = em.createQuery("SELECT i FROM Item i ORDER BY i.done" , Item.class); return query.getResultList(); } 5.5. Guarde los cambios usando Ctrl+S. 6. Actualice la clase EJB ItemService para inyectar un administrador de entidades asociado con el contexto de persistencia predeterminado. A continuación, implemente los métodos register(Item item), remove(Long id) y update(Item item) para utilizar los métodos del administrador de entidades adecuados. Sugerencia: Para el método de eliminación, use el método ItemRepository.findById() para recuperar la clase Item primero; a continuación, puede eliminar la instancia mediante el administrador de entidades. 6.1. Abra la clase ItemService; para ello, expanda el ítem persistence-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en persistence-lab > Java Resources > src/main/java > com.redhat.training.todo.service para expandirlo. Haga doble clic en el archivo ItemService. 6.2. Inyecte un administrador de entidades que utilice el contexto de persistencia predeterminado mediante la anotación @PersistenceContext: import javax.persistence.PersistenceContext; ... @Stateless public class ItemService { @Inject private Logger log; //TODO Inject an entity manager using the default persistence context @PersistenceContext private EntityManager em; 6.3. Implemente el método register mediante el uso del método persist en el administrador de entidades: JB183-EAP7.0-es-2-20180124 203 Capítulo 4. Gestión de la persistencia public void register(Item item) throws Exception { log.info("Adding new task: " + item.getDescription()); //TODO persist item with the entity manager em.persist(item); } 6.4. Implemente el método remove mediante el uso del método findById y, luego, envíe la clase Item que se encontró al método remove del administrador de entidades. public void remove(Long id) { //TODO lookup the item by its ID then remove it using the entity manager em.remove(repository.findById(id)); } 6.5. Implemente el método update mediante el uso del método merge en el administrador de entidades para actualizar los ítems existentes en la base de datos: public void update(Item item) { //TODO update the item in the database using the entity manager em.merge(item); } 6.6. Guarde los cambios usando Ctrl+S. 7. Inicie JBoss EAP desde dentro de JBDS. Seleccione la pestaña Servers (Servidores) en JBDS. Haga clic con el botón derecho en la entrada del servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en la opción verde Start (Iniciar) para iniciar el servidor. Observe la pestaña Console (Consola) de JBDS hasta que el servidor se inicie y vea el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.2.GA (WildFly Core 2.1.8.Final-redhat-1) started 8. Compile e implemente la aplicación en JBoss EAP con Maven. Ejecute los siguientes comandos para compilar e implementar la aplicación: [student@workstation ~]$ cd /home/student/JB183/labs/persistence-lab [student@workstation persistence-lab]$ mvn clean wildfly:deploy Si la compilación es correcta, se mostrará la siguiente salida: [student@workstation persistence-lab]$ mvn clean wildfly:deploy ... [INFO] [INFO] <<< wildfly-maven-plugin:1.0.2.Final:deploy (default-cli) < package @ persistence-lab <<< ... [INFO] --- maven-war-plugin:2.1.1:war (default-war) @ persistence-lab --... [INFO] --- wildfly-maven-plugin:1.0.2.Final:deploy (default-cli) @ persistence-lab 204 JB183-EAP7.0-es-2-20180124 Solución [INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ 9. Pruebe la aplicación en un explorador. 9.1. Abra Firefox en la máquina virtual workstation y diríjase a http:// localhost:8080/persistence-lab para acceder a la aplicación. 9.2. Agregue al menos dos nuevos ítems pendientes mediante la interfaz de la aplicación To Do List. Si la persistencia funciona correctamente, los nuevos ítems se agregan a la lista. 9.3. Después de haber agregado correctamente los nuevos ítems, actualice su estado a Completados. Si la consulta funciona correctamente, los ítems marcados como done (listo) se mueven a la parte inferior de la lista. 10. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab persistence-lab grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 11. Realice la limpieza. 11.1.Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/persistence-lab [student@workstation persistence-lab]$ mvn wildfly:undeploy 11.2.Haga clic con el botón derecho en el proyecto persistence-lab en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrar este proyecto. 11.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 205 Capítulo 4. Gestión de la persistencia Resumen En este capítulo, aprendió lo siguiente: • La especificación de API de persistencia Java (JPA) admite la asignación relacional de objetos y tiene tres conceptos básicos: entidades, unidad de persistencia y contexto de persistencia. • Una clase simple de Java se convierte en una clase de entidad mediante una anotación @Entity. • Una clase de entidad debe especificar una clave primaria mediante la anotación @Id. • EntityManager realiza las operaciones CRUD (Crear, leer, actualizar y eliminar) mediante los métodos persist(), find(), update() y delete(). Un objeto de EntityManager se puede inyectar en un EJB mediante la anotación @Inject. • Una unidad de persistencia contiene información acerca de una fuente de datos, el tipo de transacción y el nombre de la unidad de persistencia. Se configura en un archivo persistence.xml. • La API de validación de beans configura restricciones de validación con anotaciones. Las restricciones incorporadas están disponibles para su validación en el paquete javax.validation.constraints. • JPQL es un lenguaje de consulta que admite consultas dinámicas y estáticas. Las consultas se pueden crear con parámetros nombrados y parámetros posicionales. • Las consultas nombradas se definen en el nivel de la clase. 206 JB183-EAP7.0-es-2-20180124 TRAINING CAPÍTULO 5 ADMINISTRACIÓN DE RELACIONES ENTRE ENTIDADES Descripción general Meta Definir y administrar relaciones entre entidades JPA. Objetivos • Configurar relaciones de una entidad con otra entidad y de una entidad con varias entidades. • Describir relaciones de varias entidades con varias entidades. Secciones • Configuración de relaciones entre entidades (y ejercicio guiado) • Descripción de relaciones de varias entidades con varias entidades (y cuestionario) Trabajo de laboratorio JB183-EAP7.0-es-2-20180124 Administración de relaciones entre entidades 207 Capítulo 5. Administración de relaciones entre entidades Configuración de relaciones entre entidades Objetivo Después de completar esta sección, los estudiantes deben poder configurar relaciones entre entidades de uno a uno y de uno a muchos. Comprensión de las relaciones entre entidades Cuando compilan aplicaciones empresariales, los desarrolladores usan bases de datos relacionales para almacenar datos de negocio creados y actualizados con la aplicación. Por lo general, los datos de la aplicación abarcan varias tablas de base de datos, por lo que es común que los datos de una tabla deban hacer referencia a datos en otra. Observe el siguiente ejemplo: una base de datos que almacena datos de pedidos del cliente debe tener tres tablas, una para los clientes, otra para los ítems y una para los pedidos del cliente. La tabla de pedidos necesita hacer referencia al registro del cliente, así como al registro de un ítem. Para representar estas relaciones, las bases de datos usan lo que se denomina una clave externa. Una clave externa es una columna donde el valor de los datos de la columna es una referencia al ID o a la clave primaria de una fila de otra tabla. Cuando un cliente carga el registro del pedido, los datos que están en la columna de la clave externa se utilizan para recuperar la información del cliente, así como la información del ítem. Dado que las claves primarias hacen referencia a los datos directamente en las tablas del cliente y ítem, no hay necesidad de duplicar esos datos en la tabla del pedido. Cuando representan tablas de base de datos en Java EE, los desarrolladores usan beans de entidad, uno para cada tabla. Para crear una relación entre las dos entidades, las variables del nivel de la clase se utilizan para representar una instancia de una entidad como un atributo de otra entidad. En el siguiente ejemplo se muestran beans de entidad Java de muestra para los datos del pedido del cliente que se describieron anteriormente: Entidad de cliente: @Entity public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; ... Entidad de ítem: @Entity 208 JB183-EAP7.0-es-2-20180124 Comprensión de las relaciones entre entidades public class Item { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String description; private Double price; ... Entidad de pedido: @Entity public class Item { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Customer customer; private Item item; ... Observe que las clases Customer (Cliente) e Item (Ítem) se utilizan como atributos de la clase Order (Pedido). Esto, desde la perspectiva de la clase de Java, representa la relación entre esas entidades; cada Order tiene una clase Customer y una clase Item. Después de que se representan adecuadamente las relaciones entre entidades mediante variables del nivel de la clase en los beans de entidad, los desarrolladores usan anotaciones para controlar JPA y asignar correctamente esas entidades y sus relaciones cuando recuperan datos de la base de datos. En la siguiente tabla se resumen estas anotaciones, que se cubren en profundidad con ejemplos más adelante en el capítulo: Anotaciones de relaciones JPA estándares Anotación Descripción @OneToOne Define una relación entre entidades como un valor único, donde una fila de la tabla X está relacionada con una fila de la tabla Y, y viceversa. @OneToMany Define una relación entre entidades como varios valores, donde una fila de la tabla X puede estar relacionada a una o varias filas de la tabla Y. @ManyToOne Define una relación entre entidades como varios valores, donde varias filas de la tabla X pueden estar relacionadas a una única fila de la tabla Y. @ManyToMany Define una relación entre entidades como varios valores, donde varias filas de la tabla X pueden estar relacionadas a una o varias filas de la tabla Y. @JoinColumn Define la columna que JPA usa como clave externa. JB183-EAP7.0-es-2-20180124 209 Capítulo 5. Administración de relaciones entre entidades Utilización de una relación entre entidades de uno a uno Use la anotación @OneToOne cuando dos entidades se relacionan entre sí, como cuando una instancia de una entidad solo se relaciona con una única instancia de la otra entidad. Por ejemplo, si una aplicación almacena información del usuario, pero coloca información confidencial, como un número de seguridad social, en una tabla separada, dos entidades, como User y UserSSN se relacionan mediante una anotación @OneToOne. Mediante la relación @OneToOne, cada usuario tiene un único SSN, y cada SSN tiene un único usuario. En el siguiente ejemplo se demuestran beans de entidad Java para dicho escenario: SQL creará las tablas para estas entidades: CREATE TABLE `UserSSN` ( `id` BIGINT not null auto_increment primary key, `socialSecurityNumber` VARCHAR(25) ); CREATE TABLE `User` ( `id` BIGINT not null auto_increment primary key, `username` VARCHAR(25), `userSSNID` BIGINT, UNIQUE (`userSSNID`), UNIQUE (`username`), FOREIGN KEY (`userSSNID`) REFERENCES UserSSN(`id`) ON DELETE CASCADE ); Datos de muestra: MariaDB [todo]> select * from UserSSN; +----+-----------------------+ | id | socialSecurityNumber | +----+-----------------------+ | 1 | aaa-aa-aaaa | | 2 | bbb-bb-bbbb | | 3 | ccc-cc-cccc | | 4 | ddd-dd-dddd | +----+----------------------+ MariaDB [todo]> select * from User; +----+----------+-----------+ | id | username | userSSNID | +----+----------+-----------+ | 1 | usera | 1 | | 2 | userb | 2 | | 3 | userc | 3 | | 4 | userd | 4 | +----+----------+-----------+ Entidad User: @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; 210 JB183-EAP7.0-es-2-20180124 Utilización de una relación entre entidades de uno a muchos private String username; @OneToOne(optional=false) @JoinColumn(name="userSSNID") private UserSSN userSSN; La anotación @OneToOne le indica a JPA que una única instancia de UserSSN está relacionada con cada instancia de User. La opción optional le indica a JPA que este campo es obligatorio, y se genera un error si la base de datos contiene una fila sin un valor para esta columna. La anotación @JoinColumn le indica a JPA la columna de la tabla User que debe usar cuando busca la fila UserSSN; en este caso, se usa la columna nombrada userSSNID, que es la clave externa configurada en la base de datos. Entidad de UserSSN: @Entity public class UserSSN { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String socialSecurityNumber; @OneToOne(optional=false, mappedBy="userSSN") private User user; La anotación @OneToOne le indica a JPA que una única instancia de User está relacionada con cada instancia de UserSSN. Se usa la opción mappedBy debido a que el registro UserSSN no tiene una referencia directa a User; en cambio, usa el nombre del campo userSSN en la clase User. El resultado de asignar esta relación en las entidades es que cuando se recupera un User de la base de datos, JPA usa automáticamente un SQL JOIN para conectar la tabla User con la tabla UserSSN y completa el objeto UserSSN de la instancia User cuando se devuelve a la aplicación. Utilización de una relación entre entidades de uno a muchos Use las anotaciones @OneToOne y @ManyToOne para asignar una relación JPA cada vez que dos entidades se relacionen entre sí, como cuando una instancia de una entidad se relaciona con varias instancias de la otra entidad. Un ejemplo de esto es una base de datos utilizada para almacenar información del usuario, colocando cada usuario en un único grupo. Para esto, se usan dos entidades: una entidad User y una entidad UserGroup. Estas dos se relacionan con las anotaciones @OneToMany y @ManyToOne, lo que implica que varios usuarios pertenecen a un grupo, y un grupo puede estar asignado a varios usuarios. En el siguiente ejemplo se muestran beans de entidad Java para dicho escenario de grupo: SQL creará las tablas para estas entidades: CREATE TABLE `UserGroup` ( JB183-EAP7.0-es-2-20180124 211 Capítulo 5. Administración de relaciones entre entidades `id` BIGINT not null auto_increment primary key, `name` VARCHAR(25) ); CREATE TABLE `User` ( `id` BIGINT not null auto_increment primary key, `groupID` BIGINT, `username` VARCHAR(25), UNIQUE (`username`), FOREIGN KEY (`groupID`) REFERENCES UserGroup(`id`) ); Datos de muestra: MariaDB [todo]> select * from UserGroup; +----+----------------------+ | id | name | +----+----------------------+ | 1 | Group A | | 2 | Group B | | 3 | Group C | | 4 | Group D | +----+----------------------+ MariaDB [todo]> select * from User; +----+----------+-----------+ | id | username | groupID | +----+----------+-----------+ | 1 | usera | 1 | | 2 | userb | 2 | | 3 | userc | 3 | | 4 | userd | 4 | +----+----------+-----------+ Entidad User: @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name="groupID") private UserGroup userGroup; La anotación @ManyToOne le indica a JPA que una única instancia de UserGroup está relacionada con varias instancias de User. La anotación @JoinColumn le indica a JPA la columna que debe usar cuando se une a la tabla UserGroup; en este ejemplo, groupID. Entidad UserGroup: @Entity public class UserGroup { @Id 212 JB183-EAP7.0-es-2-20180124 Ajuste del rendimiento de la carga de datos de relaciones @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy="userGroup") private Set<User> users; La anotación @OneToMany le indica a JPA que varias instancias de User pueden pertenecer a una única instancia de UserGroup. La variable de miembro de la entidad UserGroup es Set<User>, que permite almacenar varias instancias de User. Se usa la opción mappedBy debido a que la tabla UserGroup no contiene una referencia a User, por lo que JPA necesita usar el atributo userGroup en la entidad User para realizar la asignación cuando crea la consulta. Ajuste del rendimiento de la carga de datos de relaciones Cada relación asignada con anotaciones JPA de la relación requiere un procesamiento adicional para completar los datos relacionales. Aunque, por lo general, esto no es un problema para los conjuntos de datos pequeños o los esquemas de base de datos simples, se vuelve complejo si los desarrolladores no son cuidadosos. Agrupar relaciones o esquemas de base de datos complejas con muchas relaciones puede ser particularmente perjudicial para el rendimiento, ya que a veces se deben ejecutar varias consultas para recuperar datos necesarios o se deben usar enlaces complejos. También es muy fácil usar JPA para recuperar más datos de lo previsto, especialmente si cada relación está asignada bidireccionalmente en ambas entidades. Considere un escenario donde una aplicación almacena datos de automóviles. Hay entidades para Make (Marca), Model (Modelo), SubModel (Submodelo) y Car (Automóvil). Cada una de estas entidades tiene una relación con la otra. Para cada Make, hay muchas instancias de Model, y para cada Model hay varias instancias de SubModel. Además, la entidad Car tiene relaciones @ManyToOne con cada una de estas entidades. Entidad de marca: @Entity public class Make { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy="make") private Set<Model> models; Entidad de modelo: @Entity public class Model { @Id JB183-EAP7.0-es-2-20180124 213 Capítulo 5. Administración de relaciones entre entidades @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToOne @JoinColumn(name="makeID") private Make make; @OneToMany(mappedBy="model") private Set<SubModels> submodels; Entidad de submodelo: @Entity public class SubModel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToOne @JoinColumn(name="modelID") private Model model; Entidad de automóvil: @Entity public class Car { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name="makeID") private Make make; @ManyToOne @JoinColumn(name="modelID") private Model model; @ManyToOne @JoinColumn(name="subModelID") private SubModel subModel; private Long year; Si todas las relaciones entre estas entidades se asignan mediante anotaciones JPA, cada vez que se recupera un Car de la base de datos, también se recuperan Make, Model y SubModel mediante enlaces. Sin embargo, debido a que cada una de estas entidades tiene sus interrelaciones asignadas, también usan las anotaciones JPA para completar las variables de sus miembros. Esto significa que SubModel recupera sus instancias de Model, luego Model recupera sus instancias de Make y todas sus instancias de SubModel, y Make recupera sus instancias de Model. Después de que JPA carga todos estos datos en la memoria, se recupera la mayoría de los datos de la base de datos. Esto dificulta el rendimiento de la aplicación con un conjunto grande de datos. 214 JB183-EAP7.0-es-2-20180124 Utilización de carga diferida (lazy loading) para mejorar el rendimiento de JPA Utilización de carga diferida (lazy loading) para mejorar el rendimiento de JPA Incluso si una entidad tiene una relación asignada a otra entidad con anotaciones JPA, no siempre es necesario recuperar esa entidad relacionada cuando carga los datos para la primera entidad de la base de datos. Esto depende de la lógica de negocio que procesa la instancia de la entidad que se está cargando. Por ejemplo, diferentes pantallas en una aplicación pueden requerir que se muestren diferentes cantidades de información o tener diferentes requisitos de rendimiento. En el ejemplo de la entidad Car que se describió anteriormente, cargar un Model específico cuando se recupera un Car es probablemente necesario para mostrar un automóvil específico en la pantalla, pero cargar todas las instancias de SubModel para ese Model probablemente no es necesario para mostrar un automóvil específico en la pantalla. Para mitigar este problema y mejorar el rendimiento de la carga de entidades, JPA proporciona una funcionalidad denominada carga diferida (lazy loading) o captura diferida (lazy fetching). Con la carga diferida (lazy loading), las entidades pueden asignar relaciones, pero cargar solo aquellas relaciones cuando sea necesario. El tipo de captura es el comportamiento que marca si JPA carga o no entidades relacionadas. Se pueden utilizar dos tipos de capturas: diferida (lazy) o diligente (eager). La captura diligente está configurada de forma predeterminada, pero también se puede definir de manera explícita. Para habilitar la carga diferida (lazy loading), los desarrolladores deben definir el valor FetchType en la anotación de la relación JPA. En el siguiente ejemplo se muestra una relación cargada de manera diferida: @Entity public class Make { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy="make", fetch=FetchType.LAZY) private Set<Model> models; Después de agregar la carga diferida (lazy loading), cuando se recupera un Make de la base de datos, JPA no intenta cargar todas sus instancias de modelo. Si más adelante en la lógica de la aplicación, después de que se recupera la instancia Make de la base de datos, otra lógica de negocio intenta invocar getModels(), JPA intenta invocar automáticamente la base de datos y completar los modelos. Si la conexión de la entidad Manager utilizada para recuperar la instancia Make ya no está activa, se genera una LazyInitializationException debido a que JPA ya no puede recuperar los modelos mediante la misma conexión. JB183-EAP7.0-es-2-20180124 215 Capítulo 5. Administración de relaciones entre entidades Uso de la cláusula Join Fetch en las consultas JPQL para capturar de manera diligente entidades relacionadas La cláusula JOIN FETCH en las consultas JPQL sobrescribe el comportamiento de carga diferida (lazy loading) en las entidades y captura de manera diligente los datos de entidad relacionados. JOIN FETCH es útil cuando desarrolla consultas debido a que separa la gestión del comportamiento de captura para cada consulta. Esto permite a los desarrolladores incluir de manera segura la captura diferida (lazy fetching) de datos de entidades relacionadas de manera predeterminada, lo que permite mejorar el rendimiento de la aplicación mediante la captura diligente de los datos relacionados según se requiera al incluir una cláusula JOIN FETCH en la consulta JPQL. Para usar una cláusula JOIN FETCH, incluya el nombre de la colección asignada por JPA que JPA debe capturar de manera diligente. Una cláusula JOIN FETCH en una consulta sobrescribe cualquier atributo fetch especificado en la anotación JPA que asigna la relación. Cuando se incluye JOIN FETCH, genera que JPA incluya JOIN a la tabla asignada a la entidad relacionada en el SQL que se genera desde la consulta JPQL. El siguiente es un ejemplo de una consulta que usa una cláusula JOIN FETCH para cargar el conjunto de objetos model que están relacionados con los objetos make que JPA carga desde la base de datos: TypedQuery<Make> query = em.createQuery("SELECT ma FROM Make ma JOIN FETCH ma.models WHERE ma.id = :id" , Make.class); Demostración: Configuración de relaciones entre entidades 1. Ejecute el siguiente comando para preparar archivos usados por esta demostración. [student@workstation ~]$ demo configure-relationships setup 2. Inicie JBDS e importe el proyecto configure-relationships. Este proyecto es una aplicación web simple que usa una página JSF respaldada por un EJB de alcance de solicitud con estado para mostrar los empleados de un departamento. También utiliza CDI para inyectar el EJB responsable de consultar la base de datos. 3. Inspeccione el archivo pom.xml y observe la dependencia en las librerías hibernate para la especificación JPA y las clases de administrador de la entidad. <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.1-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <scope>provided</scope> </dependency> 216 JB183-EAP7.0-es-2-20180124 Demostración: Configuración de relaciones entre entidades 4. Abra una nueva ventana de terminal y use el cliente mysql para consultar la base de datos todo. [student@workstation ~]$ mysql -hservices -utodo -ptodo Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 11 Server version: 5.5.52-MariaDB MariaDB Server Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> use todo; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed Recupere los datos en la tabla Department. MariaDB [todo]> select * from Department; +----+-----------+ | id | name | +----+-----------+ | 1 | Sales | | 2 | Marketing | | 3 | IT | +----+-----------+ 3 rows in set (0.00 sec) Recupere los datos en la tabla Manager. MariaDB [todo]> select * from Manager; +----+------+--------------+ | id | name | departmentID | +----+------+--------------+ | | 1 | Bob | 1 | | 2 | Jill | 2 | | 3 | Sam | 3 | +----+------+--------------+ 3 rows in set (0.00 sec) La tabla Manager tiene un relación de uno a uno con la tabla Department. Recupere los datos en la tabla Employee. MariaDB [todo]> select * from Employee; +----+---------+--------------+ | id | name | departmentID | +----+---------+--------------+ | 1 | William | 1 | | 2 | Rose | 1 | | 3 | Pat | 1 | | 4 | Rodney | 2 | | 5 | Kim | 2 | | 6 | Tom | 2 | | 7 | Matt | 3 | JB183-EAP7.0-es-2-20180124 217 Capítulo 5. Administración de relaciones entre entidades | 8 | George | 3 | | 9 | Jean | 3 | +----+---------+--------------+ 9 rows in set (0.00 sec) La tabla Employee tiene un relación de muchos a uno con la tabla Manager. 5. Actualice la clase de entidad Employee para incluir las anotaciones JPA necesarias. @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToOne @JoinColumn(name="departmentID") private Department department; 6. Actualice la clase de entidad Manager para incluir las anotaciones JPA necesarias. @Entity public class Manager { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne @JoinColumn(name="departmentID") private Department department; 7. Actualice la clase de entidad Department para incluir las anotaciones JPA necesarias. Capture de manera diligente los registros de empleado de un departamento para que la aplicación cargue de manera previa los empleados cuando recupera los registros del departamento. @Entity public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne(mappedBy="department") private Manager manager; @OneToMany(mappedBy="department", fetch=FetchType.EAGER) private Set<Employee> employees; 218 JB183-EAP7.0-es-2-20180124 Demostración: Configuración de relaciones entre entidades 8. Inicie el servidor JBoss EAP local dentro de JBDS. En la pestaña Servers del panel inferior de JBDS, haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y vea el mensaje iniciado: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 9. Implemente la aplicación en el servidor JBoss EAP local y pruébela en un explorador. En una ventana de terminal, ejecute el siguiente comando: [student@workstation ~]$ cd /home/student/JB183/labs/configure-relationships [student@workstation configure-relationships]$ mvn wildfly:deploy Abra http://localhost:8080/configure-relationships/ en su explorador y pruebe la aplicación para asegurarse de que muestre adecuadamente los empleados y el administrador de diferentes departamentos. 10. Anule la implementación de la aplicación y detenga el servidor. [student@workstation configure-relationships]$ mvn wildfly:undeploy Referencias Para obtener más información, consulte el capítulo JPA de la Guía de desarrollo para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ JB183-EAP7.0-es-2-20180124 219 Capítulo 5. Administración de relaciones entre entidades Ejercicio guiado: Configuración de relaciones entre entidades En este ejercicio, configurará relaciones de entidades entre varias entidades que se utilizan en una aplicación web basada en JSF. Resultado Deberá ser capaz de asignar correctamente relaciones de uno a uno y de muchos a uno mediante las anotaciones JPA necesarias. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab entity-relationships setup Pasos 1. Abra JBDS e importe el proyecto de Maven. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta entityrelationships y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Asigne las relaciones de uno a uno entre las entidades User y Email. 2.1. Abra la clase Email; para ello, expanda el ítem entity-relationships en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en entity-relationships > Java Resources > src/main/java > com.redhat.training.model y expándalo. Haga doble clic en el archivo Email.java. Use la anotación JPA @OneToOne para asignar la relación a la entidad User: @Entity public class Email { 220 JB183-EAP7.0-es-2-20180124 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String address; //TODO map relationship @OneToOne(mappedBy="email") private User user; Observe que se utiliza el atributo mappedBy. Esto se debe a que la columna que impone cómo asignar registros de usuario a registros de correo electrónico está almacenada en la tabla de usuarios y está representada por la variable email en la clase de la entidad User. nota Está previsto que JBDS genere un error en la asignación de User. Este error se soluciona en el próximo paso. 2.2. Guarde los cambios usando Ctrl+S. 2.3. Abra la clase User; para ello, expanda el ítem entity-relationships en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en entity-relationships > Java Resources > src/main/java > com.redhat.training.model y expándalo. Haga doble clic en el archivo User.java. Utilice la anotación JPA @OneToOne para asignar la relación a la entidad Email: @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; //TODO map relationship @OneToOne @JoinColumn(name="emailID") private Email email; Observe la anotación @JoinColumn. Esto le indica a JPA la columna que debe usar cuando une la tabla User con la tabla Email. En este caso, utiliza la columna emailID. 2.4. Guarde los cambios usando Ctrl+S. 3. Asigne las relaciones de uno a muchos entre las entidades UserGroup y User. 3.1. Abra la clase UserGroup; para ello, expanda el ítem entity-relationships en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en entity-relationships > Java Resources > src/main/ JB183-EAP7.0-es-2-20180124 221 Capítulo 5. Administración de relaciones entre entidades java > com.redhat.training.model y expándalo. Haga doble clic en el archivo UserGroup.java. Use la anotación JPA @OneToMany para asignar la relación a la entidad UserGroup: @Entity public class UserGroup { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy="userGroup") private Set<User> users; Observe el atributo mappedBy. Esto se usa debido a que la columna que asigna registros de usuario a registros de grupo de usuarios está almacenada en la tabla de usuarios y está representada por la variable userGroup en la clase de la entidad User. nota Está previsto que JBDS genere un error en la asignación de User. Este error se soluciona en el próximo paso. 3.2. Guarde los cambios usando Ctrl+S. 3.3. Abra la clase User; para ello, expanda el ítem entity-relationships en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en entity-relationships > Java Resources > src/main/java > com.redhat.training.model y expándalo. Haga doble clic en el archivo User.java. Use la anotación JPA @ManyToOne para asignar la relación a la entidad UserGroup: @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; //TODO map relationship @OneToOne @JoinColumn(name="emailID") private Email email; //TODO map relationship @ManyToOne @JoinColumn(name="groupID") private UserGroup userGroup; 222 JB183-EAP7.0-es-2-20180124 Observe la anotación @JoinColumn. Esto le indica a JPA la columna que debe usar cuando une la tabla User con la tabla UserGroup. En este caso, utiliza la columna groupID. 3.4. Guarde los cambios usando Ctrl+S. 4. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y muestre el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 5. Implemente la aplicación en JBoss EAP con Maven; para ello, ejecute los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/entity-relationships [student@workstation entity-relationships]$ mvn wildfly:deploy Una vez finalizado, aparece BUILD SUCCESS, como se muestra en el próximo ejemplo: [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] -----------------------------------------------------------------------BUILD SUCCESS -----------------------------------------------------------------------Total time: 17.116 s Finished at: 2016-12-01T07:26:55-05:00 Final Memory: 35M/210M ------------------------------------------------------------------------ Valide la implementación en el registro de servidores que se muestra en la pestaña Console (Consola) en JBDS. Cuando se implementa la aplicación, aparece lo siguiente en el registro: INFO [org.jboss.as.server] (management-handler-thread - 9) WFLYSRV0010: Deployed "entity-relationships.war" (runtime-name : "entity-relationships.war") 6. Pruebe la aplicación en un explorador. 6.1. Abra http://localhost:8080/entity-relationships en un explorador en la máquina virtual workstation. JB183-EAP7.0-es-2-20180124 223 Capítulo 5. Administración de relaciones entre entidades Figura 5.1: Página de inicio de la aplicación 6.2. Elija un grupo del menú desplegable para verlo. La página se actualiza con un seguimiento de pila (stack) debido a una LazyInitializationException: Figura 5.2: Respuesta de error de la aplicación Observe el primer mensaje de error: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.redhat.training.model.UserGroup.users, could not initialize proxy - no Session Este error se produjo debido a que la página JSF intentó cargar el conjunto de usuarios desde un grupo que estaba cargado desde la base de datos, pero los usuarios no estaban cargados de manera diligente. Por lo tanto, la sesión JPA ya estaba cerrada cuando la página JSF intentó la invocación de getUsers(), provocando la LazyInitializationException. Este error se puede corregir con la captura diligente. 7. 224 Actualice la entidad UserGroup para usar la captura diligente en este conjunto de usuarios. JB183-EAP7.0-es-2-20180124 7.1. Abra la clase UserGroup; para ello, expanda el ítem entity-relationships en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en entity-relationships > Java Resources > src/main/ java > com.redhat.training.model y expándalo. Haga doble clic en el archivo UserGroup.java. Agregue el atributo fetch a la anotación JPA y defina el tipo de captura como FetchType.EAGER para cargar la relación de manera diligente en la entidad User: @Entity public class UserGroup { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy="userGroup", fetch=FetchType.EAGER) private Set<User> users; 7.2. Guarde los cambios usando Ctrl+S. 8. Vuelva a implementar la aplicación en JBoss EAP con Maven; para ello, ejecute los siguientes comandos: [student@workstation entity-relationships]$ mvn wildfly:deploy Una vez finalizado, verá BUILD SUCCESS como se muestra en el próximo ejemplo: [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] -----------------------------------------------------------------------BUILD SUCCESS -----------------------------------------------------------------------Total time: 17.116 s Finished at: 2016-12-01T07:26:55-05:00 Final Memory: 35M/210M ------------------------------------------------------------------------ Valide la implementación en el registro de servidores que se muestra en la pestaña Console (Consola) en JBDS. Cuando su aplicación se haya implementado correctamente, esto aparece en el registro: INFO [org.jboss.as.server] (management-handler-thread - 9) WFLYSRV0016: Replaced deployment "entity-relationships.war" with deployment "entity-relationships.war" 9. Pruebe la aplicación en un explorador. 9.1. Abra http://localhost:8080/entity-relationships en un explorador en la máquina virtual workstation. 9.2. Elija un grupo y vea sus usuarios. La aplicación muestra los usuarios y las direcciones de correo electrónico del grupo. JB183-EAP7.0-es-2-20180124 225 Capítulo 5. Administración de relaciones entre entidades Figura 5.3: Respuesta correcta de la aplicación 10. Actualice la entidad UserGroup para usar la captura diferida (lazy fetching) en este conjunto de usuarios y sobrescriba este comportamiento mediante el uso de JOIN FETCH en la consulta. 10.1.En la clase de la entidad UserGroup de com.redhat.training.model, actualice el atributo fetch de la anotación JPA y defina el tipo de extracción como FetchType.LAZY para cargar de manera diferida (lazy fetching) la relación en la entidad User: @Entity public class UserGroup { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy="userGroup", fetch=FetchType.LAZY) private Set<User> users; 10.2.Guarde los cambios usando Ctrl+S. 10.3.Actualice el EJB UserBean en el paquete com.redhat.training.ejb para usar JOIN FETCH para capturar de manera diligente el conjunto de usuarios de un grupo. @Stateless public class UserBean { @Inject private EntityManager em; public Set<UserGroup> getAllUserGroups(){ 226 JB183-EAP7.0-es-2-20180124 TypedQuery<UserGroup> query = em.createQuery("SELECT g FROM UserGroup g JOIN FETCH g.users" , UserGroup.class); return new HashSet<UserGroup>(query.getResultList()); } } 10.4.Guarde los cambios usando Ctrl+S. 11. Vuelva a implementar la aplicación en JBoss EAP con Maven; para ello, ejecute los siguientes comandos: [student@workstation entity-relationships]$ mvn wildfly:deploy Una vez finalizado, verá BUILD SUCCESS como se muestra en el próximo ejemplo: [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] -----------------------------------------------------------------------BUILD SUCCESS -----------------------------------------------------------------------Total time: 17.116 s Finished at: 2016-12-01T07:26:55-05:00 Final Memory: 35M/210M ------------------------------------------------------------------------ Valide la implementación en el registro de servidores que se muestra en la pestaña Console (Consola) en JBDS. Cuando su aplicación se haya implementado correctamente, esto aparece en el registro: INFO [org.jboss.as.server] (management-handler-thread - 9) WFLYSRV0016: Replaced deployment "entity-relationships.war" with deployment "entity-relationships.war" 12. Pruebe la aplicación en un explorador. 12.1.Abra http://localhost:8080/entity-relationships en un explorador en la máquina virtual workstation. 12.2.Elija un grupo y vea sus usuarios. La aplicación muestra los usuarios y las direcciones de correo electrónico del grupo sin errores. 13. Anule la implementación de la aplicación y detenga JBoss EAP. 13.1.Ejecute el siguiente comando para anular la implementación de la aplicación: [student@workstation entity-relationships]$ mvn wildfly:undeploy 13.2.Para cerrar el proyecto, haga clic con el botón derecho en el proyecto entityrelationships del Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 13.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). JB183-EAP7.0-es-2-20180124 227 Capítulo 5. Administración de relaciones entre entidades Esto concluye el ejercicio guiado. 228 JB183-EAP7.0-es-2-20180124 Descripción de relaciones de varias entidades con varias entidades Descripción de relaciones de varias entidades con varias entidades Objetivo Después de completar esta sección, los estudiantes deben poder configurar relaciones de varias entidades con varias entidades. Comprensión de relaciones de muchos a muchos Una relación de muchos a muchos ocurre cuando cada fila de una tabla tiene varias filas relacionadas en otra tabla, y viceversa. Un ejemplo de este tipo de relación es la asignación de trabajadores a proyectos. Se puede asignar cada trabajador a uno o más proyectos y cada proyecto puede tener múltiples trabajadores. Es difícil representar este tipo de relación en una base de datos relacional debido a que, a diferencia de la relación de uno a muchos, no se puede utilizar una única columna en una de las tablas para representar la relación. Para representar una relación de muchos a muchos en una base de datos relacional, se debe usar una tabla intermediaria conocida como tabla transversal o tabla de unión. Una tabla de unión es una tabla intermediaria donde cada fila de la tabla representa combinaciones de dos ID, uno de cada una de las tablas. Un ejemplo de una relación de muchos a muchos en una aplicación empresarial es una base de datos que almacena datos de inscripción a una clase. Una tabla Student se utiliza para almacenar registros de estudiante, y una tabla Class se utiliza para almacenar clases. Una tabla de unión StudentXClass se utiliza para relacionar estas dos tablas y para representar cuáles son los estudiantes que están en determinadas clases. Si utiliza estas tres tablas, es posible encontrar los estudiantes de una determinada clase, así como las clases para un determinado estudiante al unirse a la tabla StudentXClass. A continuación se muestran los beans de entidad Java para la aplicación de inscripción a clase: SQL creará las tablas para estas entidades: CREATE TABLE `Student` ( `id` BIGINT not null auto_increment primary key, `name` VARCHAR(50) ); CREATE TABLE `Class` ( `id` BIGINT not null auto_increment primary key, `name` VARCHAR(50) ); CREATE TABLE `StudentXClass` ( `id` BIGINT not null auto_increment primary key, `studentID` BIGINT not null, `classID` BIGINT not null, FOREIGN KEY (`studentID`) REFERENCES Student(`id`), FOREIGN KEY (`classID`) REFERENCES Class(`id`) ); Entidad de estudiante: JB183-EAP7.0-es-2-20180124 229 Capítulo 5. Administración de relaciones entre entidades @Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Set<Class> classes; Entidad de clase: @Entity public class Class { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Set<Student> students; Observe que no hay una entidad para la tabla de unión StudentXClass. Esto se debe a que los objetos Java no requieren un objeto intermediario para representar una relación de muchos a muchos entre otros dos proyectos. Crear un Conjunto de cada una de las entidades como variable de miembro de la otra, como se observa en el ejemplo anterior, es suficiente para crear la relación de muchos a muchos entre los dos objetos. Utilización de anotaciones JPA para asignar relaciones de muchos a muchos En lugar de utilizar una única JPA @JoinColumn, use una anotación @JoinTable y dos anotaciones @JoinColumn para asignar una relación de muchos a muchos. La anotación @JoinTable es el puente que permite que JPA use la tabla de unión para completar las relaciones entre dos objetos, y las dos anotaciones @JoinColumn se utilizan para definir cómo unir la tabla de unión con la tabla de entidad, así como la tabla de entidad relacionada. Cuando especifica la anotación @JoinTable, los atributos name, joinColumns e inverseJoinColumns son requeridos y se utilizan de la siguiente manera: @JoinTable( name="$JOIN_TABLE_NAME", joinColumns=@JoinColumn(name="$JOIN_TABLE_COLUMN_NAME"), inverseJoinColumns=@JoinColumn(name="$JOIN_TABLE_COLUMN2_NAME")) private Set<RelatedEntity> entities; name El nombre de la tabla de unión que JPA utilizará cuando complete la relación. joinColumns La columna de la tabla de unión que se utiliza para volver a unirse a la clase de entidad. Este atributo acepta una instancia de @JoinColumn, que está definida con un atributo name que se asigna al nombre de la columna en la tabla de unión. 230 JB183-EAP7.0-es-2-20180124 Utilización de anotaciones JPA para asignar relaciones de muchos a muchos inverseJoinColumns La columna de la tabla de unión que se utiliza para unirse desde la tabla de unión a la clase de entidad relacionada. Este atributo acepta una instancia de @JoinColumn, que está definida con un atributo name que se asigna al nombre de la columna en la tabla de unión. Una última consideración cuando asigna relaciones de muchos a muchos en las entidades JPA es que cada relación necesita únicamente su tabla de unión asignada en un lado de la relación. Por lo general, los desarrolladores se refieren a este lado como el propietario o lado principal de la relación. Una vez que se define un lado principal, la entidad relacionada o entidad secundaria puede usar el atributo mappedBy en la anotación @ManyToMany para hacer referencia a la variable en la entidad propietaria que asigna la relación. Importante Solo un lado de una relación JPA se puede asignar como propietaria. Si ambas entidades tienen la tabla de unión asignada y ninguna usa mappedBy, JPA intenta persistir filas duplicadas en la tabla de unión. Si continuamos con el ejemplo de inscripción a clase de la sección anterior, las clases de entidad JPA anotadas correctamente Student y Class coinciden con lo siguiente: Entidad de estudiante: @Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToMany @JoinTable( name="StudentXClass", joinColumns=@JoinColumn(name="studentID"), inverseJoinColumns=@JoinColumn(name="classID")) private Set<Class> classes; Marque esta relación como una relación de muchos a muchos con la anotación @ManyToMany. Especifique la tabla de unión que se debe usar cuando completa la relación con la anotación @JoinTable. Defina el atributo name con el nombre de la tabla de unión. Especifique @JoinColumn que se utilizará para volver a unir la tabla de unión con la tabla Student; en este caso, studentID. Especifique @JoinColumn que se utilizará para volver a unir la tabla de unión con la tabla Class relacionada; en este caso, classID. La inverseJoinColumn será siempre una columna en la entidad relacionada. Entidad de clase: JB183-EAP7.0-es-2-20180124 231 Capítulo 5. Administración de relaciones entre entidades @Entity public class Class { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToMany(mappedBy="classes") private Set<Student> students; Especifique la entidad Class como la secundaria de la relación con la entidad Student; para ello, utilice el atributo mappedBy en la anotación @ManyToMany. Haga referencia a la variable del objeto Student que se puede usar para asignar la relación; en este caso, classes. Referencias Para obtener más información, consulte el capítulo JPA de la Guía de desarrollo para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ 232 JB183-EAP7.0-es-2-20180124 Cuestionario: Descripción de relaciones de varias entidades con varias entidades Cuestionario: Descripción de relaciones de varias entidades con varias entidades Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuáles son las tres anotaciones de JPA requeridas para asignar completamente una relación de muchos a muchos? (Elija tres opciones): a. b. c. d. e. 2. @JoinColumn @ManyToOne @JoinTable @OneToMany @ManyToMany ¿Qué escenario describe una relación de muchos a muchos? a. Datos que representan cuáles son los animales que están en determinados zoológicos en cualquier momento dato. Datos que representan hijos y sus madres biológicas. Datos que representan atletas profesionales y sus equipos actuales. Datos que representan documentos de investigación y cada diario que los ha publicado. b. c. d. 3. SQL: CREATE TABLE `Employee` ( `id` BIGINT not null auto_increment primary key, `name` VARCHAR(50) ); CREATE TABLE `Project` ( `id` BIGINT not null auto_increment primary key, `name` VARCHAR(50) ); CREATE TABLE `EmployeeXProject` ( `id` BIGINT not null auto_increment primary key, `employeeID` BIGINT not null, `projectID` BIGINT not null, FOREIGN KEY (`employeeID`) REFERENCES Employee(`id`), FOREIGN KEY (`projectID`) REFERENCES Project(`id`) ); Entidad de estudiante: @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; JB183-EAP7.0-es-2-20180124 233 Capítulo 5. Administración de relaciones entre entidades private String name; private Set<Project> projects; Entidad de clase: @Entity public class Project { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Set<Employee> employees; Dada la estructura de las tablas de la base de datos en los ejemplos del código anterior, ¿cuáles son las dos anotaciones JPA que se utilizarían en el Set de instancias Project para que la entidad Employee sea la propietaria de la relación? (Elija dos opciones). a. b. @ManyToMany(mappedBy="projects") @JoinTable(name="Project", joinColumns=@JoinColumn(name="id"), c. inverseJoinColumns=@JoinColumn(name="id")) @JoinTable(name="EmployeeXProject", joinColumns=@JoinColumn(name="employeeID"), d. 4. inverseJoinColumns=@JoinColumn(name="projectID")) @ManyToMany Dada la estructura de las clases de entidades del ejemplo anterior, ¿que anotación JPA debe utilizarse en el Set de instancias Employee para que la entidad Proyect sea la secundaria de la relación? a. b. @ManyToMany(mappedBy="projects") @JoinTable(name="Project", joinColumns=@JoinColumn(name="id"), c. inverseJoinColumns=@JoinColumn(name="id")) @JoinTable(name="EmployeeXProject", joinColumns=@JoinColumn(name="employeeID"), d. 234 inverseJoinColumns=@JoinColumn(name="projectID")) @ManyToMany JB183-EAP7.0-es-2-20180124 Solución Solución Elija las respuestas correctas para las siguientes preguntas: 1. ¿Cuáles son las tres anotaciones de JPA requeridas para asignar completamente una relación de muchos a muchos? (Elija tres opciones): a. b. c. d. e. 2. @JoinColumn @ManyToOne @JoinTable @OneToMany @ManyToMany ¿Qué escenario describe una relación de muchos a muchos? a. Datos que representan cuáles son los animales que están en determinados zoológicos en cualquier momento dato. Datos que representan hijos y sus madres biológicas. Datos que representan atletas profesionales y sus equipos actuales. Datos que representan documentos de investigación y cada diario que los ha publicado. b. c. d. 3. SQL: CREATE TABLE `Employee` ( `id` BIGINT not null auto_increment primary key, `name` VARCHAR(50) ); CREATE TABLE `Project` ( `id` BIGINT not null auto_increment primary key, `name` VARCHAR(50) ); CREATE TABLE `EmployeeXProject` ( `id` BIGINT not null auto_increment primary key, `employeeID` BIGINT not null, `projectID` BIGINT not null, FOREIGN KEY (`employeeID`) REFERENCES Employee(`id`), FOREIGN KEY (`projectID`) REFERENCES Project(`id`) ); Entidad de estudiante: @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Set<Project> projects; JB183-EAP7.0-es-2-20180124 235 Capítulo 5. Administración de relaciones entre entidades Entidad de clase: @Entity public class Project { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Set<Employee> employees; Dada la estructura de las tablas de la base de datos en los ejemplos del código anterior, ¿cuáles son las dos anotaciones JPA que se utilizarían en el Set de instancias Project para que la entidad Employee sea la propietaria de la relación? (Elija dos opciones). a. b. @ManyToMany(mappedBy="projects") @JoinTable(name="Project", joinColumns=@JoinColumn(name="id"), c. inverseJoinColumns=@JoinColumn(name="id")) @JoinTable(name="EmployeeXProject", joinColumns=@JoinColumn(name="employeeID"), d. 4. inverseJoinColumns=@JoinColumn(name="projectID")) @ManyToMany Dada la estructura de las clases de entidades del ejemplo anterior, ¿que anotación JPA debe utilizarse en el Set de instancias Employee para que la entidad Proyect sea la secundaria de la relación? a. b. @ManyToMany(mappedBy="projects") @JoinTable(name="Project", joinColumns=@JoinColumn(name="id"), c. inverseJoinColumns=@JoinColumn(name="id")) @JoinTable(name="EmployeeXProject", joinColumns=@JoinColumn(name="employeeID"), d. 236 inverseJoinColumns=@JoinColumn(name="projectID")) @ManyToMany JB183-EAP7.0-es-2-20180124 Trabajo de laboratorio: Administración de relaciones entre entidades Trabajo de laboratorio: Administración de relaciones entre entidades En este trabajo de laboratorio, actualizará las clases de entidades con las anotaciones JPA adecuadas para asignar relaciones entre entidades. Resultado Deberá poder asignar una relación de uno a uno y de uno a muchos entre entidades. Antes de comenzar Abra una ventana de terminal en la máquina virtual de la estación de trabajo y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab manage-relationships setup 1. Abra JBDS e importe el proyecto manage-relationships ubicado en el directorio / home/student/JB183/labs/manage-relationships. 2. Asigne la relación de uno a uno entre Teacher y Classroom con anotaciones JPA. 3. Asigne la relación de uno a muchos entre las entidades Classroom y Student y capture de manera diligente la lista de estudiantes cuando se carga un objeto Classroom desde la base de datos. 4. Inicie JBoss EAP desde dentro de JBDS. 5. Compile e implemente la aplicación en JBoss EAP con Maven. 6. Pruebe la aplicación en un explorador. 6.1. Abra Firefox en la máquina virtual workstation y diríjase a http:// localhost:8080/manage-relationships para acceder a la aplicación. 6.2. En la pantalla de la aplicación, actualice el aula con las diferentes opciones. Asegúrese de que los datos del maestro y estudiante se completen correctamente para cada aula. Esto verifica que JPA puede utilizar las anotaciones proporcionadas para asignar relaciones entre las entidades y completar los datos de la relación correctamente. 7. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab manage-relationships grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 8. Realice la limpieza. JB183-EAP7.0-es-2-20180124 237 Capítulo 5. Administración de relaciones entre entidades 8.1. Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/manage-relationships [student@workstation manage-relationships]$ mvn wildfly:undeploy 8.2. Haga clic con el botón derecho en el proyecto manage-relationships en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrar este proyecto. 8.3. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el trabajo de laboratorio. 238 JB183-EAP7.0-es-2-20180124 Solución Solución En este trabajo de laboratorio, actualizará las clases de entidades con las anotaciones JPA adecuadas para asignar relaciones entre entidades. Resultado Deberá poder asignar una relación de uno a uno y de uno a muchos entre entidades. Antes de comenzar Abra una ventana de terminal en la máquina virtual de la estación de trabajo y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab manage-relationships setup 1. Abra JBDS e importe el proyecto manage-relationships ubicado en el directorio / home/student/JB183/labs/manage-relationships. 1.1. Para abrir JBDS, haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo. Deje el espacio de trabajo predeterminado (/home/ student/JB183/workspace) y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta managerelationships y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Asigne la relación de uno a uno entre Teacher y Classroom con anotaciones JPA. 2.1. Abra la clase Classroom; para ello, expanda el ítem manage-relationships en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en manage-relationships > Java Resources > src/main/ java > com.redhat.training.model y expándalo. Haga doble clic en el archivo Classroom.java. Utilice la anotación JPA @OneToOne para asignar la relación a la entidad Teacher: @Entity public class Classroom { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; JB183-EAP7.0-es-2-20180124 239 Capítulo 5. Administración de relaciones entre entidades private String name; //TODO map relationship @OneToOne(mappedBy="classroom") private Teacher teacher; Use el atributo mappedBy para asignar registros de aula a registros de maestro mediante la columna de la tabla Teacher, que está representada por la variable classroom en la clase de entidad Teacher. nota JBDS debe generar un error en la asignación a Teacher, y esto está previsto. Este error se soluciona en el próximo paso. 2.2. Guarde los cambios usando Ctrl+S. 2.3. Para abrir la clase Teacher, expanda el ítem manage-relationships en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en manage-relationships > Java Resources (Recursos de Java) > src/main/java > com.redhat.training.model y expándalo. Haga doble clic en el archivo Teacher.java. Utilice la anotación JPA @OneToOne para asignar la relación a la entidad Classroom: @Entity public class Teacher { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne @JoinColumn(name="classroomID") private Classroom classroom; Use la anotación @JoinColumn para indicarle a JPA cuál es la columna que debe usar cuando une la tabla Teacher con la tabla Classroom; en este caso, la columna classroomID. 2.4. Guarde los cambios usando Ctrl+S. 3. Asigne la relación de uno a muchos entre las entidades Classroom y Student y capture de manera diligente la lista de estudiantes cuando se carga un objeto Classroom desde la base de datos. 3.1. Para abrir la clase Classroom, expanda el ítem manage-relationships en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en manage-relationships > Java Resources (Recursos de Java) > src/main/ java > com.redhat.training.model y expándalo. Haga doble clic en el archivo Classroom.java. Use la anotación JPA @OneToMany para asignar la relación a la entidad UserGroup: 240 JB183-EAP7.0-es-2-20180124 Solución @Entity public class Classroom { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; //TODO map relationship @OneToOne(mappedBy="classroom") private Teacher teacher; //TODO map relationship @OneToMany(mappedBy="classroom") private Set<Student> students; Use el atributo mappedBy para hacer referencia a la columna que asigna registros de estudiante con registros de aula, ubicada en la tabla Student y representada por la variable classroom en la clase de entidad Student. nota Está previsto que JBDS genere un error en la asignación de Student. Este error se soluciona en el próximo paso. 3.2. Actualice la relación @OneToMany para que se cargue de manera diligente mediante el atributo fetch: //TODO map relationship @OneToMany(mappedBy="classroom", fetch=FetchType.EAGER) private Set<Student> students; 3.3. Guarde los cambios usando Ctrl+S. 3.4. Abra la clase Student; para ello, expanda el ítem manage-relationships en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en manage-relationships > Java Resources > src/main/java > com.redhat.training.model y expándalo. Haga doble clic en el archivo Student.java. Utilice la anotación JPA @ManyToOne para asignar la relación a la entidad Classroom: @Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToOne @JoinColumn(name="classroomID") JB183-EAP7.0-es-2-20180124 241 Capítulo 5. Administración de relaciones entre entidades private Classroom classroom; Utilice la anotación @JoinColumn para indicarle a JPA cuál es la columna que debe usar cuando une la tabla Student con la tabla Classroom. En este caso, use la columna classroomID. 3.5. Guarde los cambios usando Ctrl+S. 4. Inicie JBoss EAP desde dentro de JBDS. Seleccione la pestaña Servers (Servidores) en JBDS. Haga clic con el botón derecho en la entrada del servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en la opción verde Start (Iniciar) para iniciar el servidor. Observe la pestaña Console (Consola) de JBDS hasta que el servidor se inicie y vea el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.2.GA (WildFly Core 2.1.8.Final-redhat-1) started 5. Compile e implemente la aplicación en JBoss EAP con Maven. Abra un nuevo terminal y cambie el directorio por la carpeta /home/student/JB183/ labs/manage-relationships. [student@workstation ~]$ cd /home/student/JB183/labs/manage-relationships Compile e implemente el EJB en JBoss EAP al ejecutar el siguiente comando: [student@workstation manage-relationships]$ mvn clean wildfly:deploy Si la compilación es correcta, se mostrará la siguiente salida: [student@workstation manage-relationships]$ mvn clean package wildfly:deploy ... [INFO] [INFO] <<< wildfly-maven-plugin:1.0.2.Final:deploy (default-cli) < package @ manage-relationships <<< ... [INFO] --- maven-war-plugin:2.1.1:war (default-war) @ manage-relationships --... [INFO] --- wildfly-maven-plugin:1.0.2.Final:deploy (default-cli) @ managerelationships [INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ 6. Pruebe la aplicación en un explorador. 6.1. Abra Firefox en la máquina virtual workstation y diríjase a http:// localhost:8080/manage-relationships para acceder a la aplicación. 6.2. En la pantalla de la aplicación, actualice el aula con las diferentes opciones. Asegúrese de que los datos del maestro y estudiante se completen correctamente para cada aula. 242 JB183-EAP7.0-es-2-20180124 Solución Esto verifica que JPA puede utilizar las anotaciones proporcionadas para asignar relaciones entre las entidades y completar los datos de la relación correctamente. 7. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab manage-relationships grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 8. Realice la limpieza. 8.1. Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/manage-relationships [student@workstation manage-relationships]$ mvn wildfly:undeploy 8.2. Haga clic con el botón derecho en el proyecto manage-relationships en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrar este proyecto. 8.3. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 243 Capítulo 5. Administración de relaciones entre entidades Resumen En este capítulo, aprendió lo siguiente: • Las relaciones entre clases de entidades JPA se asignan mediante anotaciones. • Hay tres tipos principales de relaciones entre entidades: de uno a uno, de uno a muchos y de muchos a muchos. JPA proporciona anotaciones para asignar cada uno de estos tipos de relaciones. • Cuando asigna relaciones entre entidades, es importante tener en cuenta las implicaciones de rendimiento generadas por hacer que JPA cargue entidades relacionadas. Cuando sea posible, aproveche la carga diferida (lazy loading) para evitar un rendimiento más lento cuando no se necesiten entidades relacionadas. • JPA proporciona el atributo fetch, que se puede definir en FetchType.EAGER o FetchType.LAZY cuando asigna entidades relacionadas. Si la captura diferida (lazy fetching) se utiliza para asignar relaciones, intentar cargar los datos de esa relación después de la sesión del administrador de entidades genera una LazyInitializationException. • Una relación de muchos a muchos requiere el uso de una tabla de unión. JPA proporciona la anotación @JoinTable por este motivo, y saca provecho de varias @JoinColumn para asignar relaciones de muchos a muchos entre dos entidades. • Cuando un desarrollador asigna una relación de muchos a muchos entre dos entidades, solo un lado se puede asignar mediante el uso de @JoinTable: el lado propietario. El otro lado de la relación, conocido como el lado secundario, debe asignarse mediante el atributo mappedBy en la anotación @ManyToMany, que hace referencia a una variable en la entidad propietaria. 244 JB183-EAP7.0-es-2-20180124 TRAINING CAPÍTULO 6 CREACIÓN DE SERVICIOS REST Descripción general Meta Crear API REST mediante la especificación de JAX-RS. Objetivos • Describir conceptos de servicios web y enumerar tipos de servicios web. • Crear un servicio REST mediante la especificación de JAX-RS. • Crear una aplicación cliente que pueda invocar las API REST de manera remota. Secciones • Descripción de conceptos de servicios web (y cuestionario) • Creación de servicios REST con JAX-RS (y ejercicio guiado) • Consumo de un servicio REST (y cuestionario) Trabajo de laboratorio JB183-EAP7.0-es-2-20180124 Creación de servicios REST 245 Capítulo 6. Creación de servicios REST Descripción de conceptos de servicios web Objetivo Tras finalizar esta sección, los estudiantes deberán ser capaces de describir conceptos y tipos de servicios web. Servicios web Los servicios web exponen una comunicación estandarizada para la interoperabilidad entre componentes de aplicaciones por HTTP. Al abstraer aplicaciones en componentes individuales que se comunican a través de servicios web, cada sistema queda con un acoplamiento bajo entre sí. Esta separación proporciona una mayor capacidad de modificar aplicaciones o integrar nuevos sistemas en la aplicación. El uso de un formato estándar para la transferencia de datos, como JSON o XML, permite que las aplicaciones consuman servicios web para requerir solo la capacidad de crear una solicitud HTTP al servicio y procesar la respuesta del servicio. En los últimos años, se han vuelto populares las aplicaciones empresariales basadas en servicios web. Uno de los principales motivos de este aumento en la adopción es la necesidad de que las aplicaciones admitan varios dispositivos, como una computadora de escritorio y un dispositivo móvil, al tiempo que reduce el tiempo de desarrollo para admitir varias aplicaciones. Al abstraer la capa de presentación específica del dispositivo, la capa de datos se convierte en una capa de servicio. Esta separación permite a las organizaciones desarrollar rápidamente aplicaciones en una variedad de plataformas, al tiempo que se reutiliza un back-end compartido, compilado con servicios web. Este enfoque reduce el tiempo para desarrollar aplicaciones y mantener cambios al back-end al no requerir la repetición del trabajo en todas las plataformas compatibles. Por ejemplo, considere una aplicación web para un banco. La empresa desea expandirse al mercado móvil con una aplicación móvil totalmente nueva. El primer paso del desarrollador es exponer una API para acceder a los datos de la aplicación. Al exponer el back-end del banco con una capa de servicios web, el front-end de la aplicación web se separa de la lógica de negocio de la aplicación. Como resultado, los desarrolladores de la aplicación del banco pueden crear un front-end de aplicación móvil mediante servicios web sin afectar la aplicación del front-end existente. Otro beneficio de exponer la capa de servicios es que otras aplicaciones de front-end o componentes web que necesiten los datos de la aplicación también pueden invocar los mismos extremos de servicio. El siguiente gráfico demuestra esta arquitectura: 246 JB183-EAP7.0-es-2-20180124 Tipos de servicios web Figura 6.1: Arquitectura de aplicaciones de los servicios web Tipos de servicios web En este curso, se abordan dos implementaciones de servicios web: • Servicios web RESTful JAX-RS • Servicios web JAX-WS Ambas implementaciones proporcionan los mismos beneficios del uso de servicios web, como el bajo acoplamiento y protocolos estandarizados; no obstante, JAX-WS y JAX-RS difieren en una cantidad importante de formas que se deben considerar cuidadosamente, según el enfoque que se use. JB183-EAP7.0-es-2-20180124 247 Capítulo 6. Creación de servicios REST nota Este curso usa JAX-RS principalmente. Los debates sobre JAX-WS se incluyen para proporcionar un contraste con los servicios web RESTful. JAX-RS JAX-RS es la API de Java para crear servicios web RESTful livianos. En Red Hat JBoss EAP 7, la implementación de JAX-RS es RESTEasy, que cumple por completo con la especificación JSR-311, titulada API de Java para servicios web RESTful 2.0, y proporciona funciones adicionales para un desarrollo eficiente de servicios REST. Los desarrolladores pueden compilar servicios web RESTEasy usando anotaciones para marcar ciertas clases y métodos como extremos. Cada extremo representa una URL que una aplicación de cliente puede invocar y, según el tipo de anotación, especifica el tipo de solicitud HTTP. A diferencia de otros enfoques a servicios web, los servicios web RESTful pueden usar un formato de mensajes más pequeño, como JSON, en comparación con XML y otros para crear más sobrecarga para cada solicitud Cada extremo se puede anotar para determinar tanto el formato de los datos recibidos como el formato de los datos devueltos al cliente. Asimismo, los servicios web RESTful no requieren el uso de una WSDL o algo similar a lo que se requiere al consumir servicios JAX-WS. Esto simplifica mucho el uso de servicios web RESTful, ya que los consumidores simplemente pueden realizar solicitudes a extremos individuales en un servicio. JAX-WS JAX-WS es la API de Java para servicios web basados en XML mediante el Protocolo simple de acceso a objetos (SOAP). JBossWS es la implementación que cumple con la especificación API de Java JSR-224 para servicios web basados en XML 2.2 para JAX-WS en Red Hat JBoss EAP 7. Para definir un protocolo estándar para la comunicación entre aplicaciones, los servicios JAX-WS usan un archivo de definición XML escrito mediante el Lenguaje de descripción de servicios web (WSDL). En muchas formas, WSDL simplifica la creación de servicios web permitiendo que una IDE, como JBoss Developer Studio, use la definición de servicios para crear clientes que puedan interactuar con el servicio en forma automática. No obstante, esta definición de servicio no requiere más mantenimiento a los desarrolladores del servicio. Los servicios JAX-WS también requieren de los clientes y consumidores que realicen solicitudes formales en comparación con JAX-RS, que puede realizar solicitudes a extremos individuales simplemente a través de HTTP. Referencias API de Java JSR-311 para servicios web RESTful 2.0 http://www.jcp.org/en/jsr/detail?id=311 API de Java JSR-224 para servicios web basados en XML 2.2 http://www.jcp.org/en/jsr/detail?id=224 248 JB183-EAP7.0-es-2-20180124 JAX-WS Referencias Para obtener más información, consulte la Guía de desarrollo de servicios web para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en-us/ red_hat_jboss_enterprise_application_platform/ JB183-EAP7.0-es-2-20180124 249 Capítulo 6. Creación de servicios REST Cuestionario: Servicios web Una los siguientes ítems con sus equivalentes en la tabla. La especificación JAX-WS. La especificación para JAX-RS. La implementación EAP del servicio SOAP. La implementación del servicio web basado en la anotación. Un formato XML que describe los extremos para un servicio SOAP. Un formato de datos ligero y legible utilizado por los servicios web RESTful. Término Definición WSDL JSON API de Java JSR-311 para servicios web RESTful 2.0 API de Java JSR-224 para servicios web basados en XML 2.2 RESTEasy JBossWS 250 JB183-EAP7.0-es-2-20180124 Solución Solución Una los siguientes ítems con sus equivalentes en la tabla. Término Definición WSDL Un formato XML que describe los extremos para un servicio SOAP. JSON Un formato de datos ligero y legible utilizado por los servicios web RESTful. API de Java JSR-311 para servicios web RESTful 2.0 La especificación para JAX-RS. API de Java JSR-224 para servicios web basados en XML 2.2 La especificación JAX-WS. RESTEasy La implementación del servicio web basado en la anotación. JBossWS La implementación EAP del servicio SOAP. JB183-EAP7.0-es-2-20180124 251 Capítulo 6. Creación de servicios REST Creación de servicios REST con JAX-RS Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de realizar lo siguiente: • Cree servicios REST con JAX-RS. • Consuma los servicios web REST. • Diferencie los métodos HTTP. Servicios web RESTful con JAX-RS Como se describe en la sección anterior, JAX-RS es la API de Java utilizada para crear servicios web RESTful. Los servicios web REST están diseñados para ser lo más simples posible para mejorar el uso, tanto para los desarrolladores de los servicios como para los clientes que los consumen. Para mantener un nivel de simplicidad, los servicios web deben tener conformidad con varios estándares para ser considerados RESTful. Al implementar una capa de servicio web, los desarrolladores pueden abstraer la capa del front-end y crear una aplicación compuesta por muchos componentes con bajo acoplamiento. Este tipo de arquitectura se conoce como arquitectura de servidor de cliente y es un requisito para los servicios web REST. Otra función característica de las aplicaciones web RESTful es que los servicios no poseen estado. Cada solicitud realizada a un servicio web RESTful debe proporcionar una respuesta que contenga toda la información requerida por el cliente, aunque el servicio no puede ser responsable de conservar ninguna información con respecto al estado de la sesión. Esta restricción garantiza que el cliente es responsable de mantener el estado de la sesión en lugar de complicar el servicio web REST de manera innecesaria. La versión JEE de la aplicación To Do List utiliza un servicio web RESTful para abstraer el frontend Angular de la capa de lógica de negocio y datos. Al crear extremos específicos, como el extremo para obtener una lista de todos los ítems de To Do List, los clientes pueden obtener la información en el formato JSON para facilitar el análisis. El siguiente ejemplo ilustra una solicitud HTTP GET en el extremo REST lookupItemById() de la aplicación To Do List, que devuelve un ítem de To Do List de la base de datos MySQL basada en el ID del ítem: [student@workstation todojee]$ curl localhost:8080/todo/api/items/1 El servicio devuelve la siguiente respuesta JSON: {"id":1,"description":"Pick up newspaper","done":false,"user":null} Todo cliente con acceso al servicio puede obtener esta información, lo que permite que varias aplicaciones de front-end aprovechen los mismos datos y lógica de negocio para la aplicación To Do List. Los clientes solo necesitan poder realizar solicitudes HTTP y analizar las respuestas del servicio para consumir el servicio REST. Java EE 7 admite JAX-RS 2.0, que hace muy sencillo el desarrollo de servicios web RESTful y el cumplimiento de sus estándares. JAX-RS utiliza varias anotaciones para definir el comportamiento de los servicios web. Estas anotaciones se colocan directamente en la clase 252 JB183-EAP7.0-es-2-20180124 Creación de servicios web RESTful de servicio para crear diferentes tipos de extremos y definir parámetros. Para facilitar el desarrollo de servicios web, JBoss Developer Studio cuenta con un asistente para crear todos los archivos necesarios para definir el servicio web RESTful. Creación de servicios web RESTful Un servicio web RESTful JAX-RS consta de una o más clases que utilizan las anotaciones JAXRS para crear un servicio web. El primer paso para crear el servicio web es crear una clase que extends (amplíe) la clase javax.ws.rs.core.Application. Además de declarar el servicio web RESTful, la nueva subclase también se utiliza para definir la URI de base para el servicio web con la anotación @ApplicationPath. A continuación se muestra un ejemplo de una clase que amplía javax.ws.rs.core.Application: import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/api") public class Service extends Application { //Can be left empty } La anotación @ApplicationPath establece la base URI para el servicio web. En el ejemplo anterior, la ruta de la aplicación se configura para ser /api. Como resultado, todas las solicitudes a este servicio deben estar precedidas por "/api" en la URI. Por ejemplo, si la ruta de contexto de la aplicación es hello-world y la aplicación está disponible en http:// localhost:8080/helloworld, el servicio REST se expone en http://localhost:8080/ helloworld/api/. Esta URI a menudo se expande al agregar extremos y rutas adicionales. nota De manera opcional, puede usar la nueva subclase para proporcionar implementaciones personalizadas para algunos métodos en la clase principal; no obstante, no se necesita realizar ninguna otra modificación que la del ejemplo dado para implementar un servicio web RESTful. Una alternativa a clasificar la clase javax.ws.rs.core.Application como subclase es utilizar la web.xml en la aplicación para definir la clase javax.ws.rs.core.Application y especificar la URI de base. En el siguiente ejemplo de web.xml se proporciona la misma funcionalidad que el ejemplo anterior basado en Java, sin necesitar la creación de una clase Java adicional: <web-app version="3.0" 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-app_3_0.xsd"> <servlet> <servlet-name>javax.ws.rs.core.Application</servlet-name> </servlet> <servlet-mapping> <servlet-name>javax.ws.rs.core.Application</servlet-name> <url-pattern>/api/*</url-pattern> </servlet-mapping> ... JB183-EAP7.0-es-2-20180124 253 Capítulo 6. Creación de servicios REST </web-app> La clase de recursos raíz RESTful El uso de las anotaciones JAX-RS disponibles es una forma fácil de crear un servicio web RESTful de una clase POJO existente. Por ejemplo, considere un POJO básico, como el siguiente: public class HelloWorld { public String hello() { return "Hello World!"; } } Al marcar esta clase y método con las siguientes anotaciones, el método queda expuesto como extremo dentro del servicio web RESTful: @Stateless @Path("hello") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class HelloWorld { @GET public String hello() { return "Hello World!"; } } Este ejemplo de servicio expone el método hello() a una solicitud HTTP GET estándar. Por lo tanto, acceder a http://localhost:8080/hello-world/api/hello mediante una solicitud HTTP GET (como con un explorador web) devuelve la Cadena Hello World! en JSON. Recuerde que la anotación @ApplicationPath en la clase Aplicación dicta la URI de base para todas las solicitudes a esa aplicación. Asimismo, dentro de la clase de servicio RESTful, la clase y los métodos y extremos individuales se puede designar con su propio valor @Path específico. Esto permite a los usuarios del servicio acceder de una manera más intuitiva a un recurso. Observando el ejemplo anterior, observe que la anotación @Path a nivel de la clase se estableció como hello. Esta anotación crea otra capa en la URI, además de la @ApplicationPath existente. En la siguiente muestra se ve otro ejemplo de la modificación de la anotación @Path: @Stateless @Path("hello") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class HelloWorld { @GET @Path("person/{id}/name") public String hello(String id) { return "Hello" + id + "!"; } 254 JB183-EAP7.0-es-2-20180124 La clase de recursos raíz RESTful } En esta instancia, se invoca este método al realizar una solicitud a la siguiente URI: http:// localhost:8080/hello-world/api/hello/person/1/name. La parte {id} de la ruta es una variable, que se denota por las llaves que la rodean, y es el cliente quien debe presentar su valor en la URI para cada solicitud. En la siguiente tabla se resumen las anotaciones disponibles para lo que resta de la clase de recursos raíz RESTful: Anotaciones de JAX-RS Anotación Descripción @ApplicationPath La anotación @ApplicationPath se aplica a la subclase de la clase javax.ws.rs.core.Application y define la URI de base para el servicio web. @Path La anotación @Path define la URI de base tanto para toda la clase de raíz como para un método individual. La ruta puede contener una ruta estática explícita, como hello o puede contener una variable que se debe enviar en la solicitud. Se hace referencia a este valor utilizando la anotación @PathParam. @Consumes La anotación @Consumes define el tipo de contenido de la solicitud aceptado por la clase o el método de servicio. Si un tipo incompatible se envía al servicio, el servidor devuelve un error 415 de HTTP, "Tipo de medio no compatible". Entre los parámetros aceptables se incluye application/json, application/xml, text/html o cualquier otro tipo de MIME. @Produces La anotación @Produces define el tipo de contenido de la respuesta que la clase o el método de servicio devuelve. Entre los parámetros aceptables se incluye application/json, application/xml, text/html o cualquier otro tipo de MIME. @GET La anotación @GET se aplica a un método para crear un extremo para el tipo de solicitud HTTP GET, generalmente usado para recuperar datos. @POST La anotación @POST se aplica a un método para crear un extremo para el tipo de solicitud HTTP POST, generalmente usado para recuperar datos. @DELETE La anotación @DELETE se aplica a un método para crear un extremo para el tipo de solicitud HTTP DELETE, generalmente usado para recuperar datos. @PUT La anotación @PUT se aplica a un método para crear un extremo para el tipo de solicitud HTTP PUT, generalmente usado para recuperar datos. @PathParam La anotación @PathParam se utiliza para recuperar un parámetro enviado a través de la URI, como http://localhost:8080/ hello-web/api/hello/1. @QueryParam La anotación @QueryParam se utiliza para recuperar un parámetro enviado a través de la URI como parámetro de consulta, como http://localhost:8080/hello-web/api/hello?id=1. JB183-EAP7.0-es-2-20180124 255 Capítulo 6. Creación de servicios REST Personalización de solicitudes y respuestas Uno de los aspectos destacados de JAX-RS es la capacidad de personalizar el tipo MIME de la solicitud y la respuesta. Para las organizaciones que requieren el cumplimiento de un tipo específico de solicitud o respuesta, como XML, las anotaciones @Produces o @Consumes se pueden modificar para hacer cumplir XML como el tipo de respuesta y solicitud, respectivamente. Puede preferir usar JSON, ya que es un tipo MIME liviano, en relación a XML y puede reducir el ancho de banda. La anotación @Produces define el tipo de medio MIME para la respuesta regresada por el servicio. La anotación @Consumes define el tipo de medio MIME para la solicitud requerida por el servicio. Tanto @Produces como @Consumes se pueden aplicar en el nivel de método, el nivel de clase o ambos. Si se aplica a ambos, la anotación a nivel de método se establece como prioritaria y anula la anotación de nivel de clase. Si no se define ninguna anotación para @Produces o @Consumes en el método, el método establece el tipo MIME como predeterminado a nivel de clase. En el siguiente ejemplo se define una clase JAX-RS que demuestra el uso de las anotaciones @Produces y @Consumes: @Stateless @Path("hello") @Produces(MediaType.APPLICATION_JSON) public class HelloWorld { @GET @Produces("text/html") { public String hello() return "<b>Hello World!</b>"; } @GET @Path("newest") public Person getNewestPerson() ...implementation omitted... } { @POST @Consumes(MediaType.APPLICATION_JSON) public String savePerson(Person person) ...implementation omitted... } { } El método hello () devuelve la salida que el cliente debe esperar que esté en HTML. Esto es dictado por la anotación @Produces("text/html") del nivel de método. El método getNewestPerson() devuelve la salida que el cliente debe esperar que esté en JSON. Esto es dictado por @Produces(MediaType.APPLICATION_JSON) de nivel de clase, ya que no hay anotación @Produces de nivel de método. El método savePerson(Person person) requiere que la solicitud esté en JSON; de lo contrario, el cliente recibe un error HTTP 415 de tipo de medio no compatible. 256 JB183-EAP7.0-es-2-20180124 Métodos HTTP Métodos HTTP El protocolo HTTP define varios métodos mediante los cuales el protocolo ejecuta diferentes acciones. El cliente es responsable de especificar el tipo de solicitud, además de la ruta a la solicitud. La siguiente es una lista de métodos: • GET: el método GET recopila datos. • POST: el método POST crea una nueva entidad. • DELETE: el método DELETE quita una entidad. • PUT: El método PUT actualiza una entidad. Cada método HTTP tiene una anotación con nombre similar que se utiliza para anotar métodos en una clase de servicio RESTful. Si dos métodos Java existen en la misma ruta, JAX-RS determina qué método usar haciendo coincidir el método HTTP en la solicitud HTTP realizada por el cliente y la anotación en el método. El siguiente es un ejemplo de una clase de servicio web RESTful: @Stateless @Path("hello") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class HelloWorld { @GET @Path("person") public List<Person> getPersons() { ...implementation omitted... } @POST @Path("person") public String savePerson(Person person) { ...implementation omitted... } @PUT @Path("person") public String updatePerson(Person person) { ...implementation omitted... } @DELETE @Path("person/{id}") public String deletePerson(@PathParam("id") String id) { ...implementation omitted... } } Este método devuelve una representación JSON de la lista Java de objetos Person cuando una solicitud HTTP GET se realiza a la siguiente URI: http://localhost:8080/ hello-world/hello/person. Este método crea un objeto Person cuando una solicitud HTTP POST con la representación JSON de Person se realiza a la siguiente URI: http:// localhost:8080/hello-world/hello/person. JB183-EAP7.0-es-2-20180124 257 Capítulo 6. Creación de servicios REST Este método actualiza un objeto Person cuando una solicitud HTTP POST con la representación JSON de Person existente se realiza a la siguiente URI: http:// localhost:8080/hello-world/hello/person. Este método elimina un objeto Person cuando se realiza una solicitud HTTP DELETE a la siguiente URI: http://localhost:8080/hello-world/hello/person/1. Observe que los métodos GET, POST y PUT se encuentran todos en la misma ruta; no obstante, el cliente puede dictar qué extremo se alcanza en función del método HTTP que se especifica en la solicitud. Inyección de parámetros de la URI En muchas instancias, los clientes que usan los servicios web RESTful necesitan solicitar información específica de un servicio. Esto se logra proporcionando parámetros en la URI, ya sea como parámetro de ruta o parámetro de consulta. Para usar un parámetro de ruta en un método JAX-RS, anótelo con la anotación @PathParam. Esta anotación generalmente se usa cuando el cliente solicita un recurso específico, como los datos del usuario. El siguiente es un ejemplo de parámetro de ruta: @GET @Path("{id}") public Person getPerson(@PathParam("id") Long id) { return entityManager.find(Person.class, id); } El cliente envía el id de variable en la URI como parte de la ruta. Por ejemplo, la siguiente solicitud es compatible con este método: http://localhost:8080/helloweb/api/hello/1. El 1 se envía al método getPerson(). La anotación @PathParam asigna la variable de la ruta al parámetro del método Java. Para usar un parámetro de consulta en un método JAX-RS, anótelo con la anotación @QueryParam. Esta anotación generalmente se usa en búsquedas y al filtrar datos, como el filtrado de usuarios por sus preferencias de correo electrónico. El siguiente es un ejemplo de parámetro de consulta utilizado para determinar qué usuarios desean que se les envíen correos electrónicos en función de la variable sendEmail: @GET @Path("users") public List<Person> getUsers(@QueryParam("sendEmail") String sendEmail ) { return entityManager.find("SELECT * USERS WHERE user.sendEmail=" + sendEmail); } La anotación @QueryParam asigna el valor que se utiliza en la URI para sendEmail a una Cadena Java con el mismo nombre. El siguiente es un ejemplo de URI que envía un indicador false (falso) a la variable sendEmail: http://localhost:8080/helloworld/api/users?sendEmail=false. El método return (devolver) aprovecha el valor asignado a la variable Java utilizándolo para consultar la base de datos y filtrar todos los usuarios por parámetro. En esta instancia, el parámetro de consulta enumera los usuarios en función del valor de la variable sendEmail. En muchas instancias, un parámetro de consulta requiere un valor predeterminado para evitar que la solicitud del cliente falle por no proporcionar un parámetro y para permitir que 258 JB183-EAP7.0-es-2-20180124 Inyección de parámetros de la URI los desarrolladores del servicio creen un único método para buscar y filtrar sin realizar todos los parámetros requeridos. El siguiente es el mismo ejemplo con el valor predeterminado para la variable sendEmail establecida en False: @GET @Path("users") public List<Person> getUsers(@DefaultValue("True") @QueryParam("sendEmail") String sendEmail) { return entityManager.find("SELECT * USERS WHERE user.sendEmail=" + sendEmail); } Con el valor predeterminado establecido como True, el cliente ya no necesita ingresar el valor en la URI; en cambio, puede realizar una solicitud a http://localhost:8080/ hello-world/api/users para recibir una lista de usuarios que están aceptando correos electrónicos. Referencias Para obtener más información, consulte la Guía de desarrollo de servicios web para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en-us/ red_hat_jboss_enterprise_application_platform/ JB183-EAP7.0-es-2-20180124 259 Capítulo 6. Creación de servicios REST Ejercicio guiado: Exposición de un servicio REST En este ejercicio, expondrá una REST API para una aplicación. Resultados Deberá ser capaz de crear un servicio web RESTful y probarlo mediante curl. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos del trabajo de laboratorio necesarios para este taller. [student@workstation ~]$ lab hello-rest setup Pasos 1. Importe el proyecto hello-rest en JBoss Developer Studio IDE (JBDS). 1.1. Haga doble clic en el icono de JBDS del escritorio de la máquina virtual workstation para iniciarlo. 1.2. Ingrese /home/student/JB183/workspace en el campo Workspace de la ventana Eclipse Launcher y, luego, haga clic en Launch (Iniciar). 1.3. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.4. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta hello-rest y haga clic en OK (Aceptar). 1.6. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.7. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Cree el contexto de raíz para el servicio web. 2.1. En el ítem hello-rest de la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione hello-rest > Java Resources > src/main/java > com.redhat.training.rest y expanda el paquete. 2.2. Haga clic con el botón derecho en com.redhat.training.rest y haga clic en New (Nueva) > Class (Clase). 2.3. En el campo Name (Nombre) ingrese Service. Haga clic en Finish (Finalizar). 260 JB183-EAP7.0-es-2-20180124 2.4. En la nueva clase, agregue la anotación @ApplicationPath, importe la librería y especifique la ruta como /api: package com.redhat.training.rest; import javax.ws.rs.ApplicationPath; @ApplicationPath("/api") Public class Service { } 2.5. Complete la clase importando y ampliando la clase javax.ws.rs.core.Application: package com.redhat.training.rest; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/api") Public class Service extends Application { } 2.6. Presione Ctrl+S para guardar sus cambios. 3. Abra y actualice el servicio web RESTful PersonService.java en el paquete com.redhat.training.rest para que no tenga estado, usando la anotación @Stateless. //TODO Add the stateless annotation @Stateless 4. Agregue la anotación @Path para realizar los puntos finales para esta clase de servicio web disponible en http://localhost:8080/hello-rest/api/persons/: //TODO Add a Path for persons @Path("persons") 5. Defina el tipo de medio @Consumes y @Produces para el servicio. 5.1. Agregue la anotación @Consumes para permitir que el servicio consuma JSON: //TODO Add a Consumes annotation for JSON @Consumes(MediaType.APPLICATION_JSON) 5.2. Agregue la anotación @Produces para permitir que el servicio produzca JSON: //TODO Add a Produces annotation for JSON @Produces(MediaType.APPLICATION_JSON) JB183-EAP7.0-es-2-20180124 261 Capítulo 6. Creación de servicios REST 6. Configure los métodos getPerson(), getAllPersons(), deletePerson() y savePerson() en la clase PersonService.java para que estén disponibles como extremos REST. 6.1. Exponga el método getPerson(Long id) agregando la anotación @GET: //TODO add GET annotation @GET public Person getPerson(Long id) { return entityManager.find(Person.class, id); } 6.2. Actualice el método getPerson(Long id) para permitir que los consumidores del servicio REST soliciten una Persona con un ID específico, mediante el extremo REST, agregando las anotaciones @Path y @PathParam: @GET //TODO add path for ID @Path("{id}") public Person getPerson(@PathParam("id") Long id) { return entityManager.find(Person.class, id); } Una solicitud GET a http://localhost:8080/hello-rest/api/persons/3 ahora devuelve la representación JSON del objeto Person con ID 3. 6.3. Agregue la anotación @GET al método getAllPersons() para exponer el método como extremo REST: //TODO add GET annotation @GET public List<Person> getAllPersons() { TypedQuery<Person> query = entityManager.createQuery("SELECT p FROM Person p", Person.class); List<Person> persons = query.getResultList(); return persons; } Una solicitud GET a http://localhost:8080/hello-rest/api/persons/ ahora devuelve la representación JSON de todos los objetos Person en la base de datos. 6.4. Agregue la anotación para @DELETE al método deletePerson(Long id) para permitir que las solicitudes HTTP DELETE eliminen un objeto Person de la base de datos: //TODO add DELETE annotation @DELETE public void deletePerson(Long id) { try { try { tx.begin(); entityManager.remove(getPerson(id)); } finally { tx.commit(); } 262 JB183-EAP7.0-es-2-20180124 } catch (Exception e) { throw new EJBException(); } } 6.5. De manera similar al método que devuelve un objeto Person individual, el método deletePerson requiere un parámetro de ID para eliminar un objeto Person específico de la base de datos. Actualice el método con una anotación @Path y una anotación PathParam para permitir a los usuarios enviar ese parámetro en la solicitud HTTP: @DELETE //TODO add Path for ID @Path("{id}") public void deletePerson(@PathParam("id") Long id) { try { try { tx.begin(); entityManager.remove(getPerson(id)); } finally { tx.commit(); } } catch (Exception e) { throw new EJBException(); } } Una solicitud DELETE a http://localhost:8080/hello-rest/api/persons/3 ahora elimina el objeto Person con ID 3 de la base de datos. 6.6. Agregue una anotación @POST al método savePerson(Person person) para crear un extremo para guardar un objeto Person en la base de datos: //TODO add POST annotation @POST public Response savePerson(Person person) { try { ... } Una solicitud POST a http://localhost:8080/hello-rest/api/persons/ con una representación JSON de un objeto Person ahora envía a esa persona a la base de datos. 6.7. Presione Ctrl+S para guardar sus cambios. 7. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde de inicio para iniciar el servidor. 8. Implemente la aplicación hello-rest mediante los siguientes comandos en la ventana del terminal: [student@workstation ~]$ cd /home/student/JB183/labs/hello-rest [student@workstation hello-rest]$ mvn wildfly:deploy JB183-EAP7.0-es-2-20180124 263 Capítulo 6. Creación de servicios REST 9. Pruebe la API REST mediante curl y el complemento (plug-in) de cliente REST de Firefox. 9.1. Inicie Firefox en la máquina virtual workstation y haga clic en el complemento (plug-in) de cliente REST en la barra de herramientas del explorador. Figura 6.2: Complemento (plug-in) de cliente REST de Firefox 9.2. En la barra de herramientas superior, haga clic en Headers (Encabezados) y seleccione Custom Header (Encabezado personalizado) para agregar un nuevo encabezado personalizado a la solicitud. 9.3. Introduzca la siguiente información en el cuadro de diálogo del encabezado personalizado: • Name (Nombre): Content-Type • Value (Valor): application/json Figura 6.3: Creación de un encabezado de solicitud personalizado en el cliente REST Haga clic en Okay (Aceptar). 9.4. Seleccione POST como el Method (Método). En el formulario URL, ingrese http:// localhost:8080/hello-rest/api/persons. 9.5. En la sección Body (Cuerpo) de la solicitud, agregue la siguiente representación JSON de una entidad Person: {"name":"Shadowman"} Haga clic en Send (Enviar). 9.6. Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 200 OK. 264 JB183-EAP7.0-es-2-20180124 Figura 6.4: Creación de un encabezado de solicitud personalizado en el cliente REST 9.7. Cambie la sección Body de la solicitud con lo siguiente para desencadenar la validación de beans para la entidad Person que requiere que el atributo name (nombre) cuente con más de dos caracteres: {"name":"a"} Haga clic en Send (Enviar) y observe que la respuesta vuelve con un código de estado de 500 para indicar un error de servidor. 9.8. En una ventana de terminal, use el siguiente comando curl en la ventana de terminal para realizar una solicitud HTTP GET para recuperar todos los objetos Person: [student@workstation hello-rest]$ curl \ http://localhost:8080/hello-rest/api/persons Busque el objeto Person creado en el paso anterior en la salida: [{"id":1,"name":"Shadowman"}] 9.9. Elimine el objeto Person recientemente creado mediante la siguiente solicitud DELETE HTTP con el ID de Person correcto: [student@workstation hello-rest]$ curl -X DELETE \ http://localhost:8080/hello-rest/api/persons/1 9.10.Ejecute el siguiente comando para verificar que se haya eliminado el objeto Person: [student@workstation hello-rest]$ curl http://localhost:8080/hello-rest/api/ persons En la salida se muestra que el objeto Person ya no se encuentra en la base de datos. JB183-EAP7.0-es-2-20180124 265 Capítulo 6. Creación de servicios REST 10. Anule la implementación de la aplicación y detenga EAP. 10.1.En la ventana de terminal, ejecute el siguiente comando para anular la implementación de la aplicación de EAP: [student@workstation hello-rest]$ mvn wildfly:undeploy 10.2.Haga clic con el botón derecho en el proyecto hello-rest en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrar el proyecto. 10.3.Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y haga clic en Stop (Detener) para detener la instancia EAP. Esto concluye el ejercicio guiado. 266 JB183-EAP7.0-es-2-20180124 Consumo de un servicio REST Consumo de un servicio REST Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de realizar lo siguiente: • Invoque una API REST de manera remota. • Describa los códigos de estado HTTP. Consumo de Servicios REST Una de las principales ventajas de los servicios REST es que existen muchas formas de interactuar con los servicios o consumirlos. Los desarrolladores no están limitados a un idioma o enfoque específico para utilizar un servicio REST. Como se vio en el ejercicio anterior, puede usar un simple comando curl para comunicarse con un extremo REST o usar un complemento (plug-in) de explorador para probar un servicio REST. Si bien curl y otros enfoques de solicitud HTTP básica proporcionan un enfoque más simple y rápido de interactuar y probar un servicio REST, muchos desarrolladores prefieren utilizar librerías específicas de un idioma para comunicarse mediante programación con los servicios REST para proporcionar una mayor funcionalidad y control de las solicitudes y respuestas. En Java, por ejemplo, JAX-RS 2.0 proporciona una nueva API que puede enviar y recibir solicitudes de HTTP a servicios web RESTful locales y remotos. Existen tres componentes diferenciados de un cliente JAX-RS: • Client: el cliente crea instancias de WebTarget. • WebTarget: el destino URL de un extremo REST. • Response: el objeto que contiene la respuesta del servicio. El primer paso para crear un cliente REST es iniciar un objeto Client mediante ClientBuilder. El siguiente código crea un nuevo Client: Client client = ClientBuilder.newClient(); Al final de la solicitud, asegúrese de cerrar también el Client mediante el siguiente código: client.close(); A continuación, use el Client recientemente creado para crear una instancia WebTarget basada en el extremo REST. El siguiente código crea un WebTarget que se conecta a un servicio REST que se ejecuta a nivel local, disponible en http://localhost:8080/todo/ api/users: Client client = ClientBuilder.newClient(); WebTarget webTarget = client.target("http://localhost:8080/todo/api/users"); Por último, para obtener el objeto Response del servicio web, use el webTarget para configurar y compilar la solicitud. En el siguiente ejemplo se compila y se ejecuta una solicitud GET HTTP al extremo REST de webTarget y se devuelve un objeto Response: JB183-EAP7.0-es-2-20180124 267 Capítulo 6. Creación de servicios REST Client client = ClientBuilder.newClient(); WebTarget webTarget = client.target("http://localhost:8080/todo/api/users"); Response response = webTarget.request().get(); Configuración del objetivo Muchos desarrolladores descubren que un cliente necesita varios WebTarget para interactuar con diferentes extremos REST para cumplir con los requisitos de una solicitud o, más comúnmente, necesitan parametrizar la solicitud. Para ello, los desarrolladores pueden anexar elementos adicionales para ampliar la ruta del WebTarget. En el siguiente ejemplo se demuestra cómo crear una instancia WebTarget de la ruta adicional, que está dirigida a http://localhost:8080/todo/api/items: WebTarget webTarget = client.target("http://localhost:8080/todo/api"); WebTarget items = webTarget.path("items"); El método path() también puede crear una URL parametrizada cuando un servicio REST requiere un parámetro de ruta. Por ejemplo, el servicio REST de la aplicación To Do List requiere un ID al intentar devolver un único ítem to-do. La siguiente es la sintaxis del comando curl para devolver el ítem basado en su ID: [student@workstation ~]$ curl http://localhost:8080/todo/api/items/{id} Y en el siguiente ejemplo se demuestra cómo solicitar el ítem con un ID de 1: [student@workstation ~]$ curl http://localhost:8080/todo/api/items/1 El uso del cliente programático JAX-RS es muy similar al uso de curl. El WebTarget anexa la variable de ruta y, luego, usa el método resolveTemplate para reemplazar la variable con el valor deseado. En el siguiente ejemplo se crea la misma solicitud que el comando curl anterior: WebTarget webTarget = client.target("http://localhost:8080/todo/api"); WebTarget itemTarget = webTarget.path("items/{id}").resolveTemplate("id", "1"); Además de admitir variables de ruta, la librería del cliente JAX-RS también admite parámetros de consulta. La siguiente sintaxis genera una solicitud a la URL http://localhost:8080/ todo/api/items?id=1: WebTarget webTarget = client.target("http://localhost:8080/todo/api"); WebTarget itemTarget = webTarget.path("items").queryParam("id", "1"); Creación de la solicitud El método request() de la clase WebTarget permite a los desarrolladores definir el tipo de solicitud HTTP para realizar al extremo REST. Por ejemplo, la siguiente solicitud representa una solicitud GET en el WebTarget y devuelve un objeto de respuesta: Response response = webTarget.request().get(); 268 JB183-EAP7.0-es-2-20180124 Análisis de la respuesta Una solicitud POST utiliza el método post() para enviar datos en un formato específico, como XML o JSON, al servicio REST. Por ejemplo, el siguiente código realiza una solicitud POST a un WebTarget, enviando una entidad Item formateada como JSON: Item item = new Item(1, "Clean the car", "false") Response response = webTarget.request().post(Entity.json(item)); Cada tipo de solicitud HTTP cuenta con un método correspondiente. Los siguientes métodos están disponibles para solicitudes HTTP: • get() • post() • delete() • put() Análisis de la respuesta Después de realizar una solicitud con el WebTarget, el destino devuelve un objeto Response. Este objeto Response se debe asignar a la entidad o tipo de objeto esperado. Por ejemplo, si la solicitud es para un campo único, la respuesta se puede asignar a una String (Cadena). Si la solicitud es para un objeto complejo, como un Item de la aplicación To Do List, el Item se debe asignar a Item.class. A continuación, se muestra una solicitud de cliente completa para obtener un Item específico con un ID de 1 que asigna el objeto Response a un objeto Item: Client client = ClientBuilder.newBuilder().build(); WebTarget webTarget = client.target("http://localhost:8080/todo/api"); WebTarget itemTarget = webTarget.path("items/{id}").resolveTemplate("id", "1"); Response response = itemTarget.request().get(); Item foundItem = response.readEntity(Item.class); client.close(); Cree una nueva instancia de Client mediante ClientBuilder. Defina la base para una nueva instancia WebTarget. Cree otra instancia WebTarget que compile sobre la ruta del WebTarget de base, solicitando el Item con ID 1. Use el WebTarget para realizar una solicitud GET. Asigne el objeto Response a una entidad Item. Cierre el cliente cuando no existan más WebTarget para inicializar. Autenticación con Clientes REST En muchas instancias, las API REST se protegen con algún tipo de seguridad, como la autenticación Basic o Digest. La autenticación Basic a menudo es la forma más simple y más común de restringir el acceso a las API REST. Para ello se requiere que los desarrolladores provean credenciales cifradas con un Codificador Base 64 al realizar solicitudes a la API REST. El siguiente es un ejemplo de solicitud para un recurso protegido con autenticación Basic: JB183-EAP7.0-es-2-20180124 269 Capítulo 6. Creación de servicios REST String username = "redhat"; String password = "Shadowman"; String userpass = username + ":" + password; String encodedCred = new BASE64Encoder().encode(userpass.getBytes()); Client client = ClientBuilder.newBuilder().build(); WebTarget webTarget = client.target("http://localhost:8080/todo/api"); WebTarget itemTarget = webTarget.path("items/{id}").resolveTemplate("id", "1"); Response response = itemTarget.request().header("Authorization", "Basic " + encodedCred).get(); Item foundItem = response.readEntity(Item.class); client.close(); Una cadena del nombre de usuario que puede acceder al recurso solicitado. La contraseña del nombre de usuario. La cadena que representa el formato requerido para el encabezado de autenticación Basic. La sintaxis es username:password. La cadena que contiene las credenciales codificadas de Base 64. La solicitud requiere el encabezado "Autorización" y la carga debe contener una cadena con el siguiente formato: "Basic <encodedCredentials>. Con motivo de pruebas, el complemento (plug-in) de cliente REST en Firefox proporciona a los desarrolladores la capacidad de agregar encabezados de Autenticación para acceder a recursos protegidos con autenticación Basic. Para agregar autenticación, haga clic en AuthenticationBasic Authentication. Figura 6.5: El diálogo de credenciales de autenticación Basic Ingrese las credenciales de usuario y haga clic en Okay (Aceptar). La sección Headers (Encabezados) cuenta con las credenciales codificadas automáticamente para la solicitud HTTP. 270 JB183-EAP7.0-es-2-20180124 Códigos de Estado HTTP Figura 6.6: El encabezado de autenticación de REST client Códigos de Estado HTTP Al realizar una solicitud HTTP, el servidor devuelve un código de estado para indicarle al cliente si la solicitud se realizó correctamente o si se produjo un error, informando el tipo de error mediante una variedad de códigos numéricos. Los códigos del estado HTTP más comunes: Tabla de Códigos de Estado HTTP Código del Estado HTTP Descripción 200 "OK" (Correcto). La solicitud se realizó correctamente. 400 "Bad Request" (Mala solicitud). La solicitud está dañada o indica el extremo erróneo. 403 "Forbidden" (Prohibido). Ese cliente no ingresó las credenciales correctas. 404 "Not Found" (No encontrado). No se encontró la ruta o extremo o el recurso no existe. 405 "Method Not Allowed" (Método no permitido). El cliente intentó usar un método HTTP en un extremo que no lo admite. 409 "Conflict" (Conflicto). El objeto solicitado no se puede crear porque ya existe. 500 "Internal Server Error" (Error interno del servidor). El servidor no pudo procesar la solicitud. Póngase en contacto con el propietario del servicio REST para investigar el motivo. Referencias API de Java JSR-311 para servicios web RESTful 2.0 http://www.jcp.org/en/jsr/detail?id=311 API de Java JSR-224 para servicios web basados en XML 2.2 http://www.jcp.org/en/jsr/detail?id=224 JB183-EAP7.0-es-2-20180124 271 Capítulo 6. Creación de servicios REST Referencias Para obtener más información, consulte la Guía de desarrollo de servicios web para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en-us/ red_hat_jboss_enterprise_application_platform/ 272 JB183-EAP7.0-es-2-20180124 Cuestionario: Consumo de un servicio REST Cuestionario: Consumo de un servicio REST Una los siguientes ítems con sus equivalentes en la tabla. "Bad Request" (Mala solicitud). "Internal Server Error" (Error interno del servidor). "OK" (Correcto). Crea instancias de objetos que indican extremos REST. Representa un extremo REST. Un objeto devuelto de una solicitud. Término Definición Código del Estado HTTP 200 Código del Estado HTTP 400 Código del Estado HTTP 500 WebTarget Client JB183-EAP7.0-es-2-20180124 273 Capítulo 6. Creación de servicios REST Término Definición Response 274 JB183-EAP7.0-es-2-20180124 Solución Solución Una los siguientes ítems con sus equivalentes en la tabla. Término Definición Código del Estado HTTP 200 "OK" (Correcto). Código del Estado HTTP 400 "Bad Request" (Mala solicitud). Código del Estado HTTP 500 "Internal Server Error" (Error interno del servidor). WebTarget Representa un extremo REST. Client Crea instancias de objetos que indican extremos REST. Response Un objeto devuelto de una solicitud. JB183-EAP7.0-es-2-20180124 275 Capítulo 6. Creación de servicios REST Trabajo de laboratorio: Creación de servicios REST En este trabajo de laboratorio, aprenderá a exponer el servicio REST de la aplicación To Do List Java EE. Resultados Deberá ser capaz de exponer y probar el servicio REST de la aplicación To Do List Java EE. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab todo-rest setup Pasos 1. Importe el proyecto todo-rest en JBoss Developer Studio IDE (JBDS). 2. Cree la clase de contexto de raíz nombrada JaxRsActivator.java en el paquete com.redhat.training.todo.rest para el servicio REST. Establezca la ApplicationPath como /api. 3. Cree y configure la anotación Path (Ruta) para que la clase com.redhat.training.todo.rest.ItemResourceRESTService.java realice los extremos para esta clase de servicio web disponible en http://localhost:8080/ todo-rest/api/items/. 4. Existen cuatro métodos en la clase ItemResourceRESTService.java que necesitan exponerse como extremo REST: • listAllItems() • lookupItemById(long id) • createItem(Item item) • deleteItem(long id) Actualice el método listAllItems() en la clase ItemResourceRESTService.java para cumplir con los siguientes requisitos: • Disponible con una solicitud GET. • Produce JSON. 5. Actualice el método lookupItemById(long id) en la clase ItemResourceRESTService.java para cumplir con los siguientes requisitos: • Disponible con una solicitud GET. • Produce JSON. 276 JB183-EAP7.0-es-2-20180124 • Cree un parámetro de ruta para el id, de manera que un item individual esté disponible con la siguiente URL: http://localhost:8080/todo-rest/apiitems/{id} 6. Actualice el método createItem(Item item) en la clase ItemResourceRESTService.java para cumplir con los siguientes requisitos: • Disponible con la solicitud POST. • Produce y consume JSON. 7. Actualice el método deleteItem(long id) en la clase ItemResourceRESTService.java para cumplir con los siguientes requisitos: • Disponible con la solicitud DELETE. • Cree un parámetro de ruta para el id, de manera que se elimine un item individual con la solicitud DELETE en la siguiente URL: http://localhost:8080/todo-rest/ api-items/{id} 8. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde de inicio para iniciar el servidor. 9. Implemente la aplicación todo-rest mediante los siguientes comandos en la ventana del terminal: [student@workstation ~]$ cd /home/student/JB183/labs/todo-rest [student@workstation todo-rest]$ mvn wildfly:deploy 10. Cree un Item mediante el complemento (plug-in) de cliente REST de Firefox al realizar una solicitud POST a http://localhost:8080/todo-rest/api/items 11. Pruebe la funcionalidad para enumerar todos los ítems y elimine el Item agregado en el paso anterior mediante curl o el complemento (plug-in) de Firefox. 12. Limpieza y calificación. 12.1.Para verificar que ha implementado correctamente la aplicación, abra una nueva ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para calificar este trabajo de laboratorio: [student@workstation ~]$ lab todo-rest grade Si observa fallas después de ejecutar el comando, vea los errores, solucione los problemas de implementación y corrija los errores. Vuelva a ejecutar el script de calificación y verifique que el resultado sea satisfactorio. 12.2.En la ventana del terminal donde ejecutó el comando Maven para implementar la aplicación, ejecute el siguiente comando para anular la implementación de la aplicación de EAP: [student@workstation todo-rest]$ mvn wildfly:undeploy JB183-EAP7.0-es-2-20180124 277 Capítulo 6. Creación de servicios REST 12.3.Haga clic con el botón derecho en el proyecto todo-rest en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 12.4.Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y haga clic en Stop (Detener) para detener la instancia EAP. Esto concluye el trabajo de laboratorio. 278 JB183-EAP7.0-es-2-20180124 Solución Solución En este trabajo de laboratorio, aprenderá a exponer el servicio REST de la aplicación To Do List Java EE. Resultados Deberá ser capaz de exponer y probar el servicio REST de la aplicación To Do List Java EE. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab todo-rest setup Pasos 1. Importe el proyecto todo-rest en JBoss Developer Studio IDE (JBDS). 1.1. Haga clic en el icono JBDS de la máquina virtual workstation para iniciar JBDS. 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), seleccione Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). 1.5. Diríjase al directorio /home/student/JB138/labs/. Seleccione la carpeta todorest y haga clic en OK (Aceptar). 1.6. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.7. Espere hasta que JBDS termine de importar el proyecto. 2. Cree la clase de contexto de raíz nombrada JaxRsActivator.java en el paquete com.redhat.training.todo.rest para el servicio REST. Establezca la ApplicationPath como /api. 2.1. En el ítem todo-rest de la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, seleccione todo-rest > Java Resources > src/main/java > com.redhat.training.todo.rest y expanda el paquete. 2.2. Haga clic con el botón derecho en com.redhat.training.todo.rest y haga clic en New (Nueva) > Class (Clase). 2.3. En el campo Name (Nombre), ingrese JaxRsActivator. Haga clic en Finish (Finalizar). 2.4. En la nueva clase, agregue la anotación @ApplicationPath, importe la librería y especifique la ruta como /api: package com.redhat.training.todo.rest; import javax.ws.rs.ApplicationPath; JB183-EAP7.0-es-2-20180124 279 Capítulo 6. Creación de servicios REST @ApplicationPath("/api") Public class JaxRsActivator { } 2.5. Complete la clase importando y ampliando la clase javax.ws.rs.core.Application: package com.redhat.training.todo.rest; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/api") Public class JaxRsActivator extends Application { } 2.6. Presione Ctrl+S para guardar sus cambios. 3. Cree y configure la anotación Path (Ruta) para que la clase com.redhat.training.todo.rest.ItemResourceRESTService.java realice los extremos para esta clase de servicio web disponible en http://localhost:8080/ todo-rest/api/items/. Abra y actualice el servicio web RESTful ItemResourceRESTService.java en el paquete com.redhat.training.todo.rest para establecer la @Path para el servicio / items: // *Add the path annotation* @Path("/items") @RequestScoped public class ItemResourceRESTService { 4. Existen cuatro métodos en la clase ItemResourceRESTService.java que necesitan exponerse como extremo REST: • listAllItems() • lookupItemById(long id) • createItem(Item item) • deleteItem(long id) Actualice el método listAllItems() en la clase ItemResourceRESTService.java para cumplir con los siguientes requisitos: • Disponible con una solicitud GET. • Produce JSON. 4.1. En la clase ItemResourceRESTService.java, agregue una anotación @GET al método listAllItems(): 280 JB183-EAP7.0-es-2-20180124 Solución // *Add listAllItems() annotations* // @GET public List<Item> listAllItems() { return repository.findAllItemsForUser(currentUser); } 4.2. Establezca el método listAllItems() para producir JSON con la siguiente anotación: // *Add listAllItems() annotations* // @GET @Produces(MediaType.APPLICATION_JSON) public List<Item> listAllItems() { return repository.findAllItemsForUser(currentUser); } 4.3. Presione Ctrl+S para guardar sus cambios. 5. Actualice el método lookupItemById(long id) en la clase ItemResourceRESTService.java para cumplir con los siguientes requisitos: • Disponible con una solicitud GET. • Produce JSON. • Cree un parámetro de ruta para el id, de manera que un item individual esté disponible con la siguiente URL: http://localhost:8080/todo-rest/apiitems/{id} 5.1. En la clase ItemResourceRESTService.java, agregue una anotación @GET al método lookupItemById(long id): // *Add lookupItemById() annotations* // @GET public Item lookupItemById(long id) { 5.2. Cree una anotación @Path y establézcala para ser el parámetro de id configurando la anotación @PathParam en el encabezado del método: // *Add lookupItemById() annotations* // @GET @Path("/{id}") public Item lookupItemById(@PathParam("id") long id) { 5.3. Establezca el método lookupItemById(long id) para producir JSON con la siguiente anotación: // *Add lookupItemById() annotations* // @GET @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) public Item lookupItemById(@PathParam("id") long id) { JB183-EAP7.0-es-2-20180124 281 Capítulo 6. Creación de servicios REST 5.4. Presione Ctrl+S para guardar sus cambios. 6. Actualice el método createItem(Item item) en la clase ItemResourceRESTService.java para cumplir con los siguientes requisitos: • Disponible con la solicitud POST. • Produce y consume JSON. 6.1. En la clase ItemResourceRESTService.java, agregue una anotación @POST al método createItem(Item item): // *Add createItem() annotations* // @POST public Response createItem(Item item) { 6.2. Establezca el método createItem(Item item) para consumir JSON con la siguiente anotación: // *Add createItem() annotations* // @POST @Consumes(MediaType.APPLICATION_JSON) public Response createItem(Item item) { 6.3. Establezca el método createItem(Item item) para producir JSON con la siguiente anotación: // *Add createItem() annotations* // @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response createItem(Item item) { 6.4. Presione Ctrl+S para guardar sus cambios. 7. Actualice el método deleteItem(long id) en la clase ItemResourceRESTService.java para cumplir con los siguientes requisitos: • Disponible con la solicitud DELETE. • Cree un parámetro de ruta para el id, de manera que se elimine un item individual con la solicitud DELETE en la siguiente URL: http://localhost:8080/todo-rest/ api-items/{id} 7.1. En la clase ItemResourceRESTService.java, agregue una anotación @DELETE al método deleteItem(long id): //*Add deleteItem() annotations*// @DELETE public void deleteItem(long id) { itemService.remove(id); } 282 JB183-EAP7.0-es-2-20180124 Solución 7.2. Cree una anotación @Path y establézcala para ser el parámetro de id configurando la anotación @PathParam en el encabezado del método: //*Add deleteItem() annotations*// @DELETE @Path("/{id}") public void deleteItem(@PathParam("id") long id) { itemService.remove(id); } 7.3. Presione Ctrl+S para guardar sus cambios. 8. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde de inicio para iniciar el servidor. 9. Implemente la aplicación todo-rest mediante los siguientes comandos en la ventana del terminal: [student@workstation ~]$ cd /home/student/JB183/labs/todo-rest [student@workstation todo-rest]$ mvn wildfly:deploy 10. Cree un Item mediante el complemento (plug-in) de cliente REST de Firefox al realizar una solicitud POST a http://localhost:8080/todo-rest/api/items 10.1.Inicie Firefox en la máquina virtual workstation y haga clic en el complemento (plug-in) de cliente REST en la barra de herramientas del explorador. Complemento (plug-in) de cliente REST de Firefox 10.2.En la barra de herramientas superior, haga clic en Headers (Encabezados) y en Custom Header (Encabezado personalizado) para agregar un nuevo encabezado personalizado a la solicitud. 10.3.Introduzca la siguiente información en el cuadro de diálogo del encabezado personalizado: • Name (Nombre): Content-Type • Value (Valor): application/json JB183-EAP7.0-es-2-20180124 283 Capítulo 6. Creación de servicios REST Creación de un encabezado de solicitud personalizado en el cliente REST Haga clic en Okay (Aceptar). 10.4.Seleccione POST como el Method (Método). En el formulario URL, ingrese http:// localhost:8080/todo-rest/api/items. 10.5.En la sección Body (Cuerpo) de la solicitud, agregue la siguiente representación JSON de una entidad Item: {"description":"Complete chapter 6 lab"} Haga clic en Send (Enviar). 10.6.Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 200 OK. Creación de un encabezado de solicitud personalizado en el cliente REST 11. Pruebe la funcionalidad para enumerar todos los ítems y elimine el Item agregado en el paso anterior mediante curl o el complemento (plug-in) de Firefox. 11.1.En una ventana de terminal, use el siguiente comando curl para realizar una solicitud HTTP GET para recuperar todos los objetos Item: 284 JB183-EAP7.0-es-2-20180124 Solución [student@workstation todo-rest]$ curl \ http://localhost:8080/todo-rest/api/items Busque el Item creado en el paso anterior en la salida: [{"id":1,"description":"Complete chapter 6 lab","done":false,"user": {"id":1,"username":"Guest"}}] 11.2.Elimine el nuevo Item mediante la siguiente solicitud DELETE HTTP con el ID de Item correcto: [student@workstation todo-rest]$ curl -X DELETE \ http://localhost:8080/todo-rest/api/items/1 11.3.Ejecute el siguiente comando para verificar que se haya eliminado el Item: [student@workstation todo-rest]$ curl http://localhost:8080/todo-rest/api/items En la salida se muestra que el objeto Item ya no se encuentra en la base de datos. 12. Limpieza y calificación. 12.1.Para verificar que ha implementado correctamente la aplicación, abra una nueva ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para calificar este trabajo de laboratorio: [student@workstation ~]$ lab todo-rest grade Si observa fallas después de ejecutar el comando, vea los errores, solucione los problemas de implementación y corrija los errores. Vuelva a ejecutar el script de calificación y verifique que el resultado sea satisfactorio. 12.2.En la ventana del terminal donde ejecutó el comando Maven para implementar la aplicación, ejecute el siguiente comando para anular la implementación de la aplicación de EAP: [student@workstation todo-rest]$ mvn wildfly:undeploy 12.3.Haga clic con el botón derecho en el proyecto todo-rest en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 12.4.Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y haga clic en Stop (Detener) para detener la instancia EAP. Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 285 Capítulo 6. Creación de servicios REST Resumen En este capítulo, aprendió lo siguiente: • JAX-RS es la API de Java para crear servicios web RESTful livianos. • Implementar una capa de servicio web permite a los desarrolladores abstraer la capa de front-end y crear una aplicación compuesta por muchos componentes con bajo acoplamiento. • Un servicio web RESTful JAX-RS consta de una o más clases que utilizan las anotaciones JAXRS para crear un servicio web. • La anotación @ApplicationPath establece la base URI para el servicio web. • El protocolo HTTP define varios métodos mediante los cuales el protocolo ejecuta diferentes acciones. El cliente es responsable de especificar el tipo de solicitud, además de la ruta a la solicitud. • La anotación @PathParam asigna una variable de una ruta al parámetro del método Java. • El método request() de la clase WebTarget permite a los desarrolladores definir el tipo de solicitud HTTP para realizar a un extremo REST. • Un estado de código HTTP de 200 indica que la solicitud se realizó correctamente. 286 JB183-EAP7.0-es-2-20180124 TRAINING CAPÍTULO 7 IMPLEMENTACIÓN DE CONTEXTOS E INYECCIÓN DE DEPENDENCIA (CDI) Descripción general Meta Describir casos de uso típicos para utilizar la CDI e implementarla correctamente en una aplicación. Objetivos • Describir la inyección de recursos, la inyección de dependencias y las diferencias entre ellas. • Aplicar alcances en beans de manera adecuada. Secciones • Contraste entre la inyección de dependencias y la inyección de recursos (y ejercicio guiado) • Aplicación de alcances contextuales (y ejercicio guiado) Trabajo de laboratorio JB183-EAP7.0-es-2-20180124 Implementación de Contextos e Inyección de dependencia (CDI) 287 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) Contraste entre la inyección de dependencias y la inyección de recursos Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de realizar lo siguiente: • Describir la inyección de dependencias. • Describir cómo crear un calificador. • Usar un productor para inyectar elementos que no sean beans. Comprensión de la inyección de dependencias de contexto La especificación Contextos e Inyección de dependencia (CDI) es una de muchas especificaciones secundarias dentro de la especificación Java EE. Aunque CDI se incorporó en Java EE 6, los conceptos detrás de CDI han estado presentes en varios marcos (frameworks) como Spring, Google Guice y otros. El Proceso de la comunidad Java presentó la forma definitiva de la Solicitud de especificación Java 299 en diciembre de 2009. JSR 346 define formalmente CDI para la plataforma Java EE 7. Esto significa que todo servidor de aplicaciones certificado como un servidor que cumple con Java EE 7, como JBoss EAP, debe admitir los contextos y la inyección de dependencia de manera nativa. Hay dos partes primarias de CDI: Contextos e Inyección de dependencia. Dado que se relacionan con CDI, los contextos se refieren a la capacidad de definir aplicaciones por alcance de datos, lo que se debate en mayor detalle en la próxima sección. La inyección de dependencias, según la especifica CDI, es el proceso mediante el cual se pueden crear instancias de objetos automáticamente en otros objetos de aplicación con seguridad de tipos. La decisión de qué implementación específica de un objeto se inyectará puede demorarse hasta la implementación de la aplicación. En otros marcos (frameworks), la inyección se basa en la coincidencia de cadenas. CDI mejora esto a través de una inyección de tipos, en la que los tipos se controlan en el momento de la compilación. Incluir la seguridad de tipos expone los errores de la inyección de manera anticipada en el ciclo de vida de desarrollo y simplifica la depuración. Uno de los principales beneficios de la inyección de dependencias (Dependency Injection, DI) es el bajo acoplamiento de los componentes de la aplicación. Por ejemplo, los componentes de cliente y servidor se acoplan levemente porque se pueden insertar varias versiones diferentes del servidor en el cliente. El cliente trabaja con una interfaz y desconoce con qué servidor está hablando. Es posible aprovechar la inyección durante el tiempo de implementación para usar objetos específicos para diferentes tipos de entornos, como los de producción y pruebas. Por ejemplo, puede inyectar una fuente de datos de producción o de pruebas en función de su entorno de implementación. CDI es similar al uso de la inyección de recursos para inyectar recursos como @PersistenceContext y un archivo persistence.xml. Ambos enfoques crean dependencias de recursos administradas por el contenedor y acoplan de manera flexible los componentes de la aplicación. Sin embargo, se diferencian de varias maneras. Dado que la inyección de recursos usa el nombre JNDI para inyectar un recurso, la inyección de recursos 288 JB183-EAP7.0-es-2-20180124 Uso de la inyección de dependencias no tiene seguridad de tipos como CDI. CDI tiene seguridad de tipos porque las instancias de los objetos se crean según el tipo. Además, CDI puede inyectar clases de Java regulares, mientras que la inyección de recursos no puede inyectar clases regulares y, en cambio, hace referencia al recurso por nombres de JNDI. Uso de la inyección de dependencias CDI no está automáticamente activa en una aplicación web, EJB o librería Java (JAR) porque no sería eficiente que el contenedor analizara cada aplicación y cada librería. Para habilitar CDI en las aplicaciones web, coloque un archivo vacío nombrado beans.xml en el directorio WEB-INF. Para los archivos JAR, incluso los que contienen EJB, coloque el archivo beans.xml en el directorio META-INF. El archivo marcador beans.xml no necesita contener nada para activar CDI. No hay declaración ni anotación especial necesaria para que un bean participe en CDI. Esto se compara con un EJB, que requiere una anotación que marque su tipo como @Stateless, @MessageDriven, etc. Para inyectar una instancia de un bean en una variable de instancia de otra clase, use la anotación @Inject. Cuando el contenedor examine la clase anotada en el momento de la implementación, intentará encontrar un único bean que coincida con el tipo de bean anotado. Si el contenedor encuentra más de una coincidencia, creará un error de dependencia ambigua. Por lo general, la anotación @Inject se utiliza con una declaración de miembro o en el parámetro constructor de la clase Java. La siguiente es una clase de utilidades de ejemplo nombrada EmailValidator con un método checkEmail público: public class EmailValidator { public bool checkEmail(String email) { ... } } Para aprovechar esta clase y el método con CDI, inyecte una instancia de la clase mediante el siguiente código de inyección: public class Form { ... @Inject private EmailValidator emailValidator; public void submitForm(){ ... emailValidator.checkEmail(email); ... } } Observe que nunca se declara una nueva clase EmailValidator. Mediante la inyección, el contenedor crea automáticamente instancias de la clase EmailValidator. Uso de calificadores Un calificador es una anotación personalizada que se puede aplicar a una clase de bean en el lugar de la inyección para definir el tipo de bean que se inyectará. Esto es útil cuando JB183-EAP7.0-es-2-20180124 289 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) un bean inyecta una interfaz, pero hay varias implementaciones que utilizan la misma interfaz. Cuando ocurre este tipo de inyección ambigua, el contenedor no puede elegir cuál es la implementación que debe inyectar. Los calificadores resuelven esta ambigüedad al permitirles a los usuarios crear anotaciones de calificador personalizadas para indicar cuál es la implementación que el contenedor debe utilizar. Los calificadores se definen mediante la siguiente plantilla: @Qualifier @Retention(RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER}) public @interface qualifier-name { } Por ejemplo, el siguiente es un calificador que crea la anotación @SlowBike: @Qualifier @Retention(RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER}) public @interface SlowBike { } Después de crear ese calificador, anote el encabezado de la clase con @SlowBike: @SlowBike public class Moped extends Bike implements Vehicle<Motorized> { Cuando un bean inyecta la interfaz Vehicle y usa la anotación SlowBike, el contenedor crea automáticamente una instancia de la implementación de la clase Moped: public class Inventory { @Inject @SlowBike private Bike myBike; ... } Por defecto, todos los beans de CDI tienen un calificador. Si no se especifica uno, el calificador es @Default. Además, si se anota un bean de manera explícita con la anotación @Default, ese bean funciona como la implementación predeterminada cuando no se especifica otro calificador en el punto de inyección. Uso de productores Una ventaja del uso de CDI es que se pueden retrasar las decisiones de implementación después del tiempo de compilación. Para facilitar este comportamiento, los Productores proporcionan la capacidad de que se tomen decisiones de implementación en el tiempo de ejecución mediante lógica personalizable para determinar cómo tomar estas decisiones. Los productores son métodos o atributos de objeto que producen un objeto inyectable. La ventaja de los productores es que la anotación permite que se puedan inyectar objetos que no sean beans. La anotación @Produces se puede adjuntar a los siguientes objetos: • Beans administrados • Primitivos, como int y long 290 JB183-EAP7.0-es-2-20180124 Comparación entre EJB y CDI • Tipos con parámetros, como Set<String> • Tipos sin formato, como Set El siguiente es un ejemplo de un método anotado con @Produces y la inyección de PaymentStrategy: @Produces @Preferred public PaymentStrategy getStrategicPaymentStrategy() { ... } public class CalculateInterestBean { ... @Inject @Preferred PaymentStrategy strategy; ... En el ejemplo anterior se muestra un productor que se utiliza en una declaración de método. Es muy común utilizar un calificador en un método de productor para distinguir el tipo de objeto disponible para la inyección. Combinar calificadores y productores les permite a los desarrolladores proporcionar varios métodos de productor y, luego, usar inyecciones ambiguas con un calificador para distinguir cuál es el método de productor que se debe utilizar. En el ejemplo anterior, el objeto se inyecta con el calificador @Preferred. Cuando anota un atributo en una clase Java con @Produces, puede inyectarse en un atributo en un bean administrado. Esto es útil para declarar y utilizar recursos Java EE, como fuentes de datos y registradores. Al igual que los métodos productores, los campos productores a menudo se anotan con un calificador. @Produces @AccountsDB Connection connection = new Connection(userId, password); Comparación entre EJB y CDI Diferenciar EJB y CDI es importante porque hay una superposición de características entre las dos especificaciones. En aplicaciones Java EE 7 compiladas para ejecutarse en JBoss EAP, es común que los desarrolladores usen ambas tecnologías en conjunto. Todos los EJB son beans de CDI y, por lo tanto, tienen acceso a la inyección de dependencias y son elegibles para ser inyectados. La especificación de EJB se basa en la especificación de CDI y proporciona incluso más funcionalidades, distinguiendo entre beans con y sin estado. Los EJB también proporcionan otras funcionalidades como características de concurrencia, agrupación de beans y seguridad, así como otras que no están incluidas en CDI. Cuando crea un bean, se recomienda no usar un EJB si no se requieren las características de un EJB. En cambio, use CDI para administrar contextos e inyección de dependencia. Referencias JSR 346 Contextos e inyección de dependencia para Java https://www.jcp.org/en/jsr/detail?id=346 JB183-EAP7.0-es-2-20180124 291 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) Referencias Para obtener más información, consulte el capítulo de CDI de la Guía de desarrollo para Red Hat JBoss EAP: https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/7.0/ 292 JB183-EAP7.0-es-2-20180124 Ejercicio guiado: Inyección de dependencia Ejercicio guiado: Inyección de dependencia En este ejercicio, creará e inyectará una clase de utilidades con un calificador. Resultados Deberá poder crear un calificador e inyectar un bean. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos del trabajo de laboratorio necesarios para este ejercicio guiado. [student@workstation ~]$ lab dependency-injection setup Pasos 1. Importe el proyecto dependency-injection en el IDE de JBoss Developer Studio (JBDS). 1.1. Inicie JBDS haciendo doble clic en el icono del escritorio de la máquina virtual workstation. 1.2. En la ventana Eclipse Launcher, ingrese /home/student/JB183/workspace en el campo Workspace (Espacio de trabajo) y, luego, haga clic en Launch (Iniciar). 1.3. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.4. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta dependencyinjection y haga clic en OK (Aceptar). 1.6. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.7. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Cree una nueva interfaz denominada NameUtil.java con el método sanitizeName(String name). 2.1. En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, haga clic con el botón derecho en dependency-injection > Java Resources > src/main/java > com.redhat.training.util y haga clic en New (Nueva) > Interface (Interfaz). 2.2. Ingrese NameUtil como el nombre de la interfaz y haga clic en Finish (Finalizar). 2.3. Agregue el siguiente código a la nueva interfaz para el método sanitizeName(String name): JB183-EAP7.0-es-2-20180124 293 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) package com.redhat.training.util; public interface NameUtil { public String sanitizeName(String name); } 2.4. Presione Ctrl+S para guardar sus cambios. 3. Cree dos nuevas clases en el paquete com.redhat.com.training.util que implementen la interfaz NameUtil. 3.1. En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, haga clic con el botón derecho en dependency-injection > Java Resources > src/main/java > com.redhat.training.util y haga clic en New (Nueva) > Class (Clase). 3.2. Ingrese AllCaps como el nombre de la clase y haga clic en Finish (Finalizar). 3.3. Actualice el encabezado de la clase para implementar la interfaz NameUtil: package com.redhat.training.util; public class AllCaps implements NameUtil{ } 3.4. Desplace el mouse sobre el nombre de la clase AllCaps y haga clic en Add unimplemented methods (Agregar métodos no implementados) para crear el método sanitizeName(String name) y eliminar el error. El código resultante tiene el siguiente aspecto: @Override public String sanitizeName(String name) { // TODO Auto-generated method stub return null; } 3.5. Actualice la instrucción return para devolver la cadena name en letras mayúsculas: return name.toUpperCase(); 3.6. Presione Ctrl+S para guardar sus cambios. 3.7. En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, haga clic con el botón derecho en dependency-injection > Java Resources > src/main/java > com.redhat.training.util y haga clic en New (Nueva) > Class (Clase). 3.8. Ingrese TitleCase como el nombre de la clase y haga clic en Finish (Finalizar). 3.9. Actualice el encabezado de la clase para implementar la interfaz NameUtil: 294 JB183-EAP7.0-es-2-20180124 package com.redhat.training.util; public class TitleCase implements NameUtil{ } 3.10.Desplace el mouse sobre el nombre de la clase TitleCase y haga clic en Add unimplemented methods (Agregar métodos no implementados) para crear el método sanitizeName(String name) y eliminar el error. El código resultante tiene el siguiente aspecto: @Override public String sanitizeName(String name) { // TODO Auto-generated method stub return null; } 3.11.Actualice la instrucción return para devolver la cadena name con la primera letra en mayúscula: return name.substring(0,1).toUpperCase() + name.substring(1); 3.12.Presione Ctrl+S para guardar sus cambios. 4. Agregue un método @PostConstruct a cada nuevo bean de utilidades que imprima una instrucción de registro para declarar cuándo se inyecta el bean. 4.1. En la clase AllCaps.java, agregue el siguiente método @PostConstruct y la importación javax.annotation.PostConstruct: import javax.annotation.PostConstruct; ... @PostConstruct public void printTitle(){ System.out.println("***AllCaps PostConstruct***"); } 4.2. Presione Ctrl+S para guardar sus cambios. 4.3. En la clase TitleCase.java, agregue el siguiente método @PostConstruct y la importación javax.annotation.PostConstruct: import javax.annotation.PostConstruct; ... @PostConstruct public void printTitle(){ System.out.println("***TitleCase PostConstruct***"); JB183-EAP7.0-es-2-20180124 295 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) } 4.4. Presione Ctrl+S para guardar sus cambios. 5. Inyecte la interfaz NameUtil en la clase PersonService.java y use el método sanitizeName(String name) antes de conservar el nombre en la base de datos. 5.1. En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, haga clic en dependency-injection > Java Resources > src/main/ java > com.redhat.training.ejb y expándalo. Haga doble clic en el archivo PersonService.java. 5.2. Después del encabezado de la clase, agregue el siguiente código para inyectar la interfaz NameUtil en la clase PersonService: ... public class PersonService { @Inject private NameUtil nameUtil; ... 5.3. Agregue la siguiente línea al método hello() para limpiar la entrada del nombre antes de almacenar la entidad Person en la base de datos y mostrar el nombre: //TODO sanitize name name = nameUtil.sanitizeName(name); 5.4. Presione Ctrl+S para guardar sus cambios. Observe que la advertencia del punto de inyección indica Multiple beans are eligible for injection to the injection point (Hay varios beans elegibles para la inyección en el punto de inyección). Esto se debe a que la interfaz no tiene un calificador para indicar cuál es la implementación que se debe usar. 6. Cree un nuevo calificador y use el calificador en la clase de utilidades para resolver el punto de inyección ambiguo. 6.1. En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, haga clic con el botón derecho en dependency-injection > Java Resources > src/main/java > com.redhat.training.util y haga clic en New (Nuevo) > Other… (Otro…). 6.2. Ingrese Qualifier (Calificador) en el cuadro de búsqueda. Seleccione Qualifier Annotation (Anotación de calificador) y haga clic en Next (Siguiente). 6.3. Introduzca Title (Título) en el campo Name (Nombre) y haga clic en Finish (Finalizar). 6.4. En la clase com.redhat.training.util.TitleCase, agregue el calificador al encabezado de la clase: @Title public class TitleCase implements NameUtil{ 296 JB183-EAP7.0-es-2-20180124 ... 6.5. Presione Ctrl+S para guardar sus cambios. 6.6. Vuelva a PersonService.java y observe que la advertencia ya no está presente. 7. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y muestre el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 8. Implemente la aplicación en JBoss EAP con Maven; para ello, ejecute los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/dependency-injection [student@workstation dependency-injection]$ mvn wildfly:deploy 9. Pruebe la aplicación. 9.1. Use un explorador web en la máquina virtual workstation para ir a http:// localhost:8080/dependency-injection/ y acceder a la aplicación dependencyinjection. 9.2. En el cuadro de texto, ingrese shadowman y haga clic en Submit (Enviar). El servidor devuelve lo siguiente: Hello SHADOWMAN! ... Debido a que no hay un calificador especificado en el punto de inyección de NameUtil, se utiliza el bean que tiene la anotación @Default. nota Si no se especifica un calificador, la clase asume el calificador @Default. 9.3. En los registros del servidor EAP, observe que la salida del método posterior a la construcción para la clase AllCaps ocurre solo después de que hace clic en Submit (Enviar), pero antes de que se utilice el método del objeto: ***AllCaps PostConstruct*** ###Before NameUtil method used### Hibernate: insert into Person ... JB183-EAP7.0-es-2-20180124 297 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) 10. Actualice la inyección NameUtil para usar el calificador @Title. 10.1.Haga clic en el archivo PersonService.java para editar la clase PersonService en el panel del editor. 10.2.Agregue la siguiente línea para cambiar la implementación para NameUtil e importe com.redhat.training.util.Title: @Inject @Title private NameUtil nameUtil; 10.3.Presione Ctrl+S para guardar sus cambios. 11. Vuelva a implementar la aplicación con el siguiente comando: [student@workstation dependency-injection]$ mvn wildfly:deploy 12. Pruebe la aplicación nuevamente. 12.1.Use un explorador web en la máquina virtual workstation para ir a http:// localhost:8080/dependency-injection/ y acceder a la aplicación dependencyinjection. 12.2.En el cuadro de texto, ingrese redhat y haga clic en Submit (Enviar). El servidor devuelve lo siguiente: Hello Redhat! ... 12.3.En los registros del servidor EAP, observe que se imprime la salida del método posterior a la construcción para la clase TitleCase: ***TitleCase PostConstruct*** ###Before NameUtil method used### Hibernate: insert into Person ... 13. Anule la implementación de la aplicación y detenga EAP. 13.1.En la ventana del terminal donde ejecutó el comando Maven para implementar la aplicación, ejecute el siguiente comando para anular la implementación de la aplicación: [student@workstation dependency-injection]$ mvn wildfly:undeploy 13.2.Haga clic con el botón derecho en el proyecto dependency-injection en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrar este proyecto. 298 JB183-EAP7.0-es-2-20180124 13.3.Haga clic con el botón derecho en Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) y haga clic en Stop (Detener) para detener la instancia EAP. Esto concluye el ejercicio guiado. JB183-EAP7.0-es-2-20180124 299 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) Aplicación de alcances contextuales Objetivo Tras finalizar esta sección, los estudiantes deberán ser capaces de aplicar alcances en los beans de manera adecuada. Comprensión de la importancia del alcance en las aplicaciones de Java EE El contenedor de Red Hat JBoss EAP administra el ciclo de vida de todos los beans de CDI definidos en las aplicaciones que se implementan en el servidor. Cuando administra los ciclos de vida de beans con estados, JBoss necesita comprender cuándo se deben conservar las diferentes instancias de un bean en la memoria y cuándo es seguro destruir la instancia de un bean. Además, el contenedor necesita comprender cuándo debe crear una nueva instancia de un bean en lugar de utilizar una existente. La cantidad de tiempo en la que el contenedor mantiene activa una instancia específica de un bean es conocida como el alcance de un bean. Por lo general, cuando se utiliza la inyección de dependencias para inyectar un bean, la instancia inyectada de ese bean necesita poder mantener su estado durante la interacción del usuario con la aplicación. El contenedor determina durante cuánto tiempo la instancia debe mantener su estado según el alcance del bean. Los alcances pueden definirse como duraderos, donde el estado de un bean se conserva en diferentes usuarios y solicitudes. De manera alternativa, los beans pueden destruirse y recrearse con cada solicitud entrante. Hay varios alcances disponibles en la especificación de CDI. Para definir el alcance de un bean, utilice una anotación de nivel de clase como se muestra en el siguiente ejemplo: @ApplicationScoped public class HelloCounterBean { En la siguiente lista se resume cada alcance disponible: Alcance de solicitud • Usa la anotación @RequestScoped. • El contenedor mantiene cada instancia de un bean con alcance de solicitud solo para una única solicitud HTTP. • Una vez que se completa la solicitud, se destruye la instancia del bean. Alcance de sesión • Usa la anotación @SessionScoped. • El contenedor mantiene cada instancia de un bean con alcance de sesión para cada sesión del usuario. Una sesión abarca varias solicitudes pero, por lo general, vence después de una cantidad configurable de inactividad o después de que se borra la caché del explorador. • Una vez que vence la sesión, se destruye la instancia del bean. 300 JB183-EAP7.0-es-2-20180124 Asignación de nombres para beans Alcance de conversación • Usa la anotación @ConversationScoped. • El contenedor maneja al alcance de conversación de manera similar al alcance de sesión en cuanto a que mantiene el estado asociado con un usuario del sistema y abarca varias solicitudes en el servidor. • Sin embargo, a diferencia del alcance de sesión, el alcance de conversación mantiene el estado asociado con una pestaña particular del explorador web en una aplicación web (por lo general, los exploradores comparten cookies entre las pestañas, como la cookie de sesión; este no es el caso del alcance de sesión). • La aplicación debe delimitar manualmente el inicio y final de una conversación. Las conversaciones abarcan todas las solicitudes en una única pestaña del explorador hasta que la aplicación finaliza explícitamente la aplicación o hasta que se agota el tiempo de espera. • Un usuario puede tener varias conversaciones simultáneas en varias pestañas. Alcance de aplicación • Usa la anotación @ApplicationScoped. • El contenedor mantiene cada instancia de un bean con alcance de aplicación durante toda la implementación de la aplicación. • Solo cuando se apaga el contenedor, o se vuelve a implementar la aplicación, se destruye la instancia del bean. Singleton • Usa la anotación @Singleton. • El contenedor mantiene solo una única instancia de los beans singleton. Cualquier bean que inyecta un singleton obtiene la misma instancia debido a que solo hay una. Asignación de nombres para beans Otra característica útil de la especificación de CDI es la capacidad de asignarles nombres simples a los beans. Estos nombres brindan un mecanismo para hacer referencia a los beans mediante el lenguaje de expresión (Expression Language, EL) encontrado en librerías frontend, como páginas de Caras de servidor Java (JSF). Esta funcionalidad les proporciona a los desarrolladores una manera sencilla de inyectar beans directamente a la capa de IU de una aplicación. Para asignar un nombre a un bean administrado por CDI, utilice la anotación @Named. De manera opcional, especifique el nombre para el bean de manera explícita, o la propiedad del nombre utiliza el nombre de la clase con una primera letra en minúsculas de manera predeterminada. Por ejemplo, la siguiente clase es un bean con alcance de sesión con la anotación @Named: @SessionScoped @Named public class Hello implements Serializable{ private static final long serialVersionUID = 1L; JB183-EAP7.0-es-2-20180124 301 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) private String name Para hacer referencia a los atributos del bean Hello con JSF, utilice la sintaxis #{beanName.attributeName}. Esto hace referencia directa a la variable de miembro y JSF puede convocar automáticamente los métodos getter y setter relacionados para mantener el valor en el bean actualizado automáticamente con el valor del campo de entrada del formulario. El siguiente es un fragmento de una página de JSF que utiliza EL con el bean nombrado hello, que hace referencia directa a la variable name y la asigna a un campo de entrada en un formulario de JSF: <h:inputText value="#{hello.name}" id="name" required="true" requiredMessage="Name is required"/> Referencias Para obtener más información, consulte el capítulo de CDI de la Guía de desarrollo para Red Hat JBoss EAP: https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/7.0/ 302 JB183-EAP7.0-es-2-20180124 Ejercicio guiado: Aplicación de alcances Ejercicio guiado: Aplicación de alcances En este ejercicio, aplicará diferentes alcances en los EJB y probará los efectos en una aplicación. Resultado Deberá poder implementar diferentes alcances en clases de EJB mediante anotaciones. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este ejercicio: [student@workstation ~]$ lab apply-scopes setup Pasos 1. Abra JBDS e importe el proyecto de Maven. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta apply-scopes y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Revise la página de JSF que utiliza la aplicación. Abra la página de JSF index.xhtml; para ello, expanda el ítem apply-scopes de la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en apply-scopes > src > main > webapp y expándalo. Haga doble clic en el archivo index.xhtml y, luego, haga clic en Source (Fuente) en la parte inferior del editor. <h:form id="form"> <p class="input"> <h:outputLabel value="Enter your name:" for="name" /> <h:inputText value="#{hello.name}"> requiredMessage="Name is required"/> </p> JB183-EAP7.0-es-2-20180124 id="name" required="true" 303 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) <br class="clear"/> <br class="clear"/> <p class="input"> <h:commandButton action="#{hello.sayHello()}" value="Submit" styleClass="btn" / > </p> <br class="clear"/> <br class="clear"/> <h:messages styleClass="messages"/> <p>Greeted <h:outputText value="#{counter.currentCount}"/> server restart. </p> </h:form> person(s) since last Un EJB nombrado hello se utiliza para brindar el nombre de la variable, así como el método sayHello(). Un EJB nombrado counter se utiliza para proporcionar la variable currentCount. 3. Actualice la clase de EJB PersonService para que no tenga un estado. 3.1. Abra la clase PersonService; para ello, expanda el ítem apply-scopes en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en apply-scopes > Java Resources > src/main/ java > com.redhat.training.ejb y expándalo. Haga doble clic en el archivo PersonService.java. La clase de EJB se mantiene sin estado. No tiene variables de miembro que necesiten especificarse para cada instancia de la clase. El contenedor de aplicaciones puede usar indistintamente cada instancia. 3.2. Actualice la clase de EJB para incluir la anotación @Stateless para indicarle al contenedor que el bean no tiene un estado y que se puede administrar como tal. //TODO mark as a stateless EJB @Stateless //TODO assign the name "personService" to this EJB public class PersonService { 3.3. Actualice la clase de EJB para incluir la anotación @Named para indicarle al contenedor que el bean debe estar disponible con el nombre personService. //TODO mark as a stateless EJB @Stateless //TODO assign the name "personService" to this EJB @Named("personService") public class PersonService { 3.4. Guarde los cambios en el archivo con Ctrl+S. 4. Actualice HelloCounterBean para que esté en el alcance de la aplicación y utilice el nombre counter. 4.1. Abra la clase HelloCounterBean; para ello, expanda el ítem apply-scopes en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en apply-scopes > Java Resources > src/main/ 304 JB183-EAP7.0-es-2-20180124 java > com.redhat.training.ejb y expándalo. Haga doble clic en el archivo HelloCounterBean.java. La clase de EJB mantiene un estado. Se debe mantener la variable count. En este caso, configúrela para que use el alcance de la aplicación para que el estado de este EJB se mantenga durante la vida útil de la aplicación. Además, debido a que el conteo debe ser un valor único para la aplicación, solo se requiere una instancia de HelloCounterBean. 4.2. Actualice la clase de EJB para que utilice la anotación @ApplicationScoped para indicarle al contenedor que el estado del bean debe mantenerse activo durante la vida útil de la aplicación. //TODO make application scoped @ApplicationScoped //TODO make a singleton //TODO assign the name "counter" to this EJB public class HelloCounterBean { 4.3. Actualice la clase de EJB para que utilice la anotación @Singleton para indicarle al contenedor que solo una única instancia del bean debe mantenerse activa durante la vida útil de la aplicación. //TODO make application scoped @ApplicationScoped //TODO make a singleton @Singleton //TODO assign the name "counter" to this EJB public class HelloCounterBean { 4.4. Además, recuerde que la página de JSF hace referencia a este bean con el nombre counter. Actualice la clase de EJB para incluir la anotación @Named para indicarle al contenedor que el bean debe estar disponible con el nombre counter. //TODO make application scoped @ApplicationScoped //TODO make a singleton @Singleton //TODO assign the name "counter" to this EJB @Named("counter") public class HelloCounterBean { 4.5. Guarde los cambios en el archivo con Ctrl+S. 5. Actualice el bean de respaldo Hello para que esté en el alcance de la sesión y use el nombre hello. 5.1. Abra la clase Hello; para ello, expanda el ítem apply-scopes en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga JB183-EAP7.0-es-2-20180124 305 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) clic en apply-scopes > Java Resources > src/main/java > com.redhat.training.ui y expándalo. Haga doble clic en el archivo Hello.java. Esta clase también mantiene el estado. Se debe mantener la variable name. En este caso, configúrela para que use el alcance de la sesión para que el estado de esta clase se mantenga durante la vida útil de la sesión de cada usuario en la aplicación. 5.2. Actualice la clase para que utilice la anotación @SessionScoped para indicarle al contenedor que el estado del bean debe mantenerse activo durante la vida útil de la sesión de cada usuario. //TODO set the scope @SessionScoped //TODO assign the name "hello" to this EJB public class Hello implements Serializable{ 5.3. Además, recuerde que la página de JSF hace referencia a este bean con el nombre hello. Actualice la clase para incluir la anotación @Named para indicarle al contenedor que el bean debe estar disponible con el nombre hello. //TODO set the scope @SessionScoped //TODO assign the name "hello" to this EJB @Named("hello") public class Hello implements Serializable{ 5.4. Guarde los cambios en el archivo con Ctrl+S. 6. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y muestre el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 7. Implemente la aplicación en JBoss EAP con Maven; para ello, ejecute los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/apply-scopes [student@workstation apply-scopes]$ mvn wildfly:deploy Confirme la implementación correcta en el registro del servidor que aparece en la pestaña Console (Consola) en JBDS. Cuando se implementa la aplicación, aparece lo siguiente en el registro: INFO [org.jboss.as.server] (management-handler-thread - 9) WFLYSRV0010: Deployed "apply-scopes.war" (runtime-name : "apply-scopes.war") 306 JB183-EAP7.0-es-2-20180124 8. Pruebe los alcances de la aplicación en un explorador. 8.1. Abra http://localhost:8080/apply-scopes en un explorador en la máquina virtual workstation. 8.2. Ingrese Shadowman en el cuadro de texto denominado Enter your name: (Ingrese su nombre:) y haga clic en Submit (Enviar). La página se actualiza con el mensaje Hello SHADOWMAN!. Time on the server is: Nov 02 2017 03:55:03 PM El mensaje del contador se actualiza a Greeted 1 person(s) since the last server restart (Se saludó a 1 persona desde el último reinicio del servidor). 8.3. Diríjase a http://localhost:8080/apply-scopes nuevamente en el explorador en la máquina workstation. Observe que el nombre Shadowman aún aparece en el campo de texto. Este campo se mantiene en las actualizaciones de la página. Esto se debe a que el bean que respalda el campo del formulario está en el alcance de la sesión. Además, el contador aún muestra 1 persona, debido a que el bean del contador está en el alcance de la aplicación. 9. Borre la caché y actualice la página. 9.1. En Firefox, diríjase a about:preferences en la URL para acceder a la página de preferencias de Firefox. 9.2. Haga clic en Privacy (Privacidad) y, luego, en clear your recent history (borrar su historial reciente). Haga clic en Clear Now (Borrar ahora) para que el explorador cree una nueva sesión. 9.3. Actualice la página con Ctrl+R. Observe que el nombre Shadowman no aparece en el campo de texto. Este campo se mantiene en las actualizaciones de la página, pero no en una nueva sesión. Sin embargo, el contador aún muestra 1 persona, debido a que el bean del contador está en el alcance de la aplicación. 10. Actualice la clase de EJB Hello para que esté al alcance de la solicitud. 10.1.Abra la clase Hello; para ello, expanda el ítem apply-scopes en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en apply-scopes > Java Resources > src/main/java > com.redhat.training.ui y expándalo. Haga doble clic en el archivo Hello.java. 10.2.Actualice la clase para que utilice la anotación @RequestScoped para indicarle al contenedor que el estado del bean debe mantenerse activo durante la vida útil de la solicitud de cada usuario. //TODO set the scope @RequestScoped //TODO assign the name "hello" to this EJB @Named("hello") public class Hello implements Serializable{ JB183-EAP7.0-es-2-20180124 307 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) 10.3.Guarde los cambios en el archivo con Ctrl+S. 11. Vuelva a implementar la aplicación en JBoss EAP con Maven; para ello, ejecute los siguientes comandos: [student@workstation apply-scopes]$ mvn wildfly:deploy Confirme la implementación correcta en el registro del servidor que aparece en la pestaña Console (Consola) en JBDS. Cuando se implementa la aplicación, aparece lo siguiente en el registro: INFO [org.jboss.as.server] (management-handler-thread - 9) WFLYSRV0010: Deployed "apply-scopes.war" (runtime-name : "apply-scopes.war") 12. Pruebe los alcances de la aplicación en un explorador. 12.1.Abra http://localhost:8080/apply-scopes en un explorador en la máquina virtual workstation. 12.2.Ingrese Shadowman en el cuadro de texto denominado Enter your name: (Ingrese su nombre:) y haga clic en Submit (Enviar). La página se actualiza con el mensaje Hello SHADOWMAN!. Time on the server is: Nov 02 2017 03:55:03 PM El mensaje del contador se actualiza a Greeted 1 person(s) since the last server restart (Se saludó a 1 persona desde el último reinicio del servidor). 12.3.Diríjase a http://localhost:8080/apply-scopes nuevamente en un explorador en la máquina virtual workstation. Observe que el nombre Shadowman fue borrado del campo de texto. Este campo ya no se mantiene en las actualizaciones de la página. Esto se debe a que el bean que respalda el campo del formulario ahora está en el alcance de la solicitud. Además, el contador aún muestra 1 persona, debido a que el bean del contador está en el alcance de la aplicación. 13. Anule la implementación de la aplicación y detenga JBoss EAP. 13.1.Ejecute el siguiente comando para anular la implementación de la aplicación: [student@workstation apply-scopes]$ mvn wildfly:undeploy 13.2.Para cerrar el proyecto, haga clic con el botón derecho en el proyecto apply-scopes en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 13.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). 308 JB183-EAP7.0-es-2-20180124 Esto concluye el ejercicio guiado. JB183-EAP7.0-es-2-20180124 309 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) Trabajo de laboratorio: Implementación de Contextos e Inyección de dependencia En este trabajo de laboratorio, utilizará conceptos de CDI para completar la aplicación To Do List. Resultado Deberá poder inyectar recursos y definir contextos para los beans. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab cdi-lab setup 1. Abra JBDS e importe el proyecto cdi-lab ubicado en el directorio /home/student/ JB183/labs/cdi-lab. 2. En la clase Resources.java, defina el contexto de persistencia para que EntityManager sea la persistencia predeterminada y para que este contexto se pueda inyectar en otras clases. Actualice el método produceLog() para que el Logger configurable se pueda inyectar. 3. Actualice ItemRepository y UserRepository para que no tengan estado y para que usen el administrador de entidades con el contexto de persistencia predeterminado. 4. Actualice las clases ItemService.java y UserService.java para que no tengan estado y para inyectar Logger y EntityManager. 5. Actualice ItemResourceRESTService para que tenga alcance de solicitud e inyecte UserRepository, ItemRepository, ItemService, UserService y Logger. 6. Inicie JBoss EAP desde dentro de JBDS. 7. Compile e implemente la aplicación en JBoss EAP con Maven. 8. Pruebe la aplicación en un explorador. 8.1. Abra Firefox en la máquina virtual workstation y diríjase a http:// localhost:8080/cdi-lab para acceder a la aplicación. 8.2. Agregue al menos dos nuevos ítems pendientes mediante la interfaz de la aplicación To Do List y verifique que la aplicación funcione de la manera prevista. 9. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab cdi-lab grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 310 JB183-EAP7.0-es-2-20180124 10. Realice la limpieza. 10.1.Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/cdi-lab [student@workstation cdi-lab]$ mvn wildfly:undeploy 10.2.Haga clic con el botón derecho en el proyecto cdi-lab en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 10.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 311 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) Solución En este trabajo de laboratorio, utilizará conceptos de CDI para completar la aplicación To Do List. Resultado Deberá poder inyectar recursos y definir contextos para los beans. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab cdi-lab setup 1. Abra JBDS e importe el proyecto cdi-lab ubicado en el directorio /home/student/ JB183/labs/cdi-lab. 1.1. Para abrir JBDS, haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo. Deje el espacio de trabajo predeterminado (/home/ student/JB183/workspace) y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta cdi-lab y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. En la clase Resources.java, defina el contexto de persistencia para que EntityManager sea la persistencia predeterminada y para que este contexto se pueda inyectar en otras clases. Actualice el método produceLog() para que el Logger configurable se pueda inyectar. 2.1. Abra la clase Resources; para ello, expanda el ítem cdi-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en cdi-lab > Java Resources > src/main/java > com.redhat.training.todo.util y expándalo. Haga doble clic en el archivo Resources.java. 2.2. Agregue la anotación @PersistenceContext a EntityManager y la importación de javax.persistence.PersistenceContext correspondiente a la clase Resources: import javax.persistence.PersistenceContext; 312 JB183-EAP7.0-es-2-20180124 Solución @PersistenceContext private EntityManager em; 2.3. Agregue la anotación @Produces a EntityManager y la importación de javax.enterprise.inject.Produces correspondiente a la clase Resources: import javax.enterprise.inject.Produces; @PersistenceContext @Produces private EntityManager em; 2.4. Agregue la anotación @Produces al método produceLog(): @Produces public Logger produceLog(InjectionPoint injectionPoint) { ... 2.5. Guarde los cambios en el archivo con Ctrl+S. 3. Actualice ItemRepository y UserRepository para que no tengan estado y para que usen el administrador de entidades con el contexto de persistencia predeterminado. 3.1. Abra la clase ItemRepository; para ello, expanda el ítem cdi-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en cdi-lab > Java Resources > src/main/java > com.redhat.training.todo.data y expándalo. Haga doble clic en el archivo ItemRepository.java. 3.2. Agregue la anotación @Stateless para que la clase ItemRepository no tenga estado y agregue la importación de javax.ejb.Stateless correspondiente: import javax.ejb.Stateless; @Stateless public class ItemRepository { 3.3. Agregue una anotación @Inject a la instancia de EntityManager y agregue la importación de javax.inject.Inject correspondiente: import javax.inject.Inject; @Inject private EntityManager em; 3.4. Guarde los cambios en el archivo con Ctrl+S. JB183-EAP7.0-es-2-20180124 313 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) 3.5. Abra la clase UserRepository en el paquete com.redhat.training.todo.data; para ello haga doble clic en el archivo UserRepository.java. 3.6. Agregue la anotación @Stateless para que la clase UserRepository no tenga estado y agregue la importación de javax.ejb.Stateless correspondiente: import javax.ejb.Stateless; @Stateless public class UserRepository { 3.7. Agregue una anotación @Inject a la instancia de EntityManager y agregue la importación de javax.inject.Inject correspondiente: import javax.inject.Inject; @Inject private EntityManager em; 3.8. Guarde los cambios en el archivo con Ctrl+S. 4. Actualice las clases ItemService.java y UserService.java para que no tengan estado y para inyectar Logger y EntityManager. 4.1. Abra la clase ItemService; para ello, expanda el ítem cdi-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en cdi-lab > Java Resources > src/main/java > com.redhat.training.todo.service y expándalo. Haga doble clic en el archivo ItemService.java. 4.2. Agregue la anotación @Stateless para que la clase ItemService no tenga estado y agregue la importación de javax.ejb.Stateless correspondiente: import javax.ejb.Stateless; @Stateless public class ItemService { 4.3. Agregue una anotación @Inject a la instancia EntityManager: @Inject private EntityManager em; 4.4. Agregue una anotación @Inject a la instancia Logger: @Inject private Logger log; 4.5. Guarde los cambios en el archivo con Ctrl+S. 314 JB183-EAP7.0-es-2-20180124 Solución 4.6. Abra la clase UserService en cdi-lab > Java Resources > src/main/java > com.redhat.training.todo.service; para ello, haga doble clic en el archivo UserService.java. 4.7. Agregue la anotación @Stateless para que la clase UserService no tenga estado y agregue la importación de javax.ejb.Stateless correspondiente: import javax.ejb.Stateless; @Stateless public class UserService { 4.8. Agregue una anotación @Inject a la instancia EntityManager: @Inject private EntityManager em; 4.9. Agregue una anotación @Inject a la instancia Logger: @Inject private Logger log; 4.10.Guarde los cambios en el archivo con Ctrl+S. 5. Actualice ItemResourceRESTService para que tenga alcance de solicitud e inyecte UserRepository, ItemRepository, ItemService, UserService y Logger. 5.1. Abra la clase ItemResourceRESTService; para ello, expanda el ítem cdilab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en cdi-lab > Java Resources > src/main/ java > com.redhat.training.todo.rest y expándalo. Haga doble clic en el archivo ItemResourceRESTService.java. 5.2. Agregue la anotación @RequestScoped para que la clase ItemResourceRESTService no tenga estado y agregue la importación de javax.enterprise.context.RequestScoped correspondiente: import javax.enterprise.context.RequestScoped; @RequestScoped public class ItemResourceRESTService { 5.3. Agregue una anotación @Inject a Logger y agregue la importación de javax.inject.Inject correspondiente: import javax.inject.Inject; @Inject JB183-EAP7.0-es-2-20180124 315 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) private Logger log; 5.4. Agregue una anotación @Inject a la instancia ItemRepository: @Inject private ItemRepository repository; 5.5. Agregue una anotación @Inject a la instancia UserRepository: @Inject private UserRepository userRepo; 5.6. Agregue una anotación @Inject a la instancia ItemService: @Inject private ItemService itemService; 5.7. Agregue una anotación @Inject a la instancia UserService: @Inject private UserService userService; 5.8. Guarde los cambios en el archivo con Ctrl+S. 6. Inicie JBoss EAP desde dentro de JBDS. Seleccione la pestaña Servers (Servidores) en JBDS. Haga clic con el botón derecho en la entrada del servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en la opción verde Start (Iniciar) para iniciar el servidor. Observe la pestaña Console (Consola) de JBDS hasta que el servidor se inicie y vea el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.2.GA (WildFly Core 2.1.8.Final-redhat-1) started 7. Compile e implemente la aplicación en JBoss EAP con Maven. Abra un nuevo terminal y cambie el directorio por la carpeta /home/student/JB183/ labs/cdi-lab. [student@workstation ~]$ cd /home/student/JB183/labs/cdi-lab Compile e implemente el EJB en JBoss EAP al ejecutar el siguiente comando: [student@workstation cdi-lab]$ mvn clean wildfly:deploy 8. Pruebe la aplicación en un explorador. 8.1. Abra Firefox en la máquina virtual workstation y diríjase a http:// localhost:8080/cdi-lab para acceder a la aplicación. 316 JB183-EAP7.0-es-2-20180124 Solución 8.2. Agregue al menos dos nuevos ítems pendientes mediante la interfaz de la aplicación To Do List y verifique que la aplicación funcione de la manera prevista. 9. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab cdi-lab grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 10. Realice la limpieza. 10.1.Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/cdi-lab [student@workstation cdi-lab]$ mvn wildfly:undeploy 10.2.Haga clic con el botón derecho en el proyecto cdi-lab en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 10.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 317 Capítulo 7. Implementación de Contextos e Inyección de dependencia (CDI) Resumen En este capítulo, aprendió lo siguiente: • Uno de los principales beneficios de la inyección de dependencias (Dependency Injection, DI) es el bajo acoplamiento de los componentes de la aplicación. Los componentes de cliente y servidor se acoplan levemente porque se pueden insertar varias versiones diferentes del servidor en el cliente. • CDI puede inyectar clases de Java regulares, mientras que la inyección de recursos no puede inyectar clases regulares y, en cambio, hace referencia al recurso por nombres de JNDI. • Para habilitar CDI en las aplicaciones web, coloque el archivo marcador beans.xml en el directorio WEB-INF. Para los archivos JAR, incluso los que contienen EJB, coloque el archivo beans.xml en el directorio META-INF. • Si no se especifica un calificador para un bean, el calificador utiliza @Default como valor predeterminado y el bean se convierte en la implementación predeterminada para el tipo o los tipos de bean. • La ventaja de los productores es que la anotación permite que se puedan inyectar objetos que no sean beans. • Los siguientes alcances están disponibles en CDI: ◦ @RequestScoped ◦ @SessionScoped ◦ @ConversationScoped ◦ @ApplicationScoped ◦ @Singleton • La anotación @Named brinda un mecanismo para hacer referencia a los EJB mediante el lenguaje de expresión (Expression Language, EL) encontrado en librerías front-end, como páginas de Caras de servidor Java (JSF). 318 JB183-EAP7.0-es-2-20180124 TRAINING CAPÍTULO 8 CREACIÓN DE APLICACIONES DE MENSA JERÍA CON JMS Descripción general Meta Crear clientes de mensajería que envían y reciben mensajes mediante la API de JMS. Objetivos • Describir la API de JMS y nombrar a los objetos que se utilizan para enviar y recibir mensajes. • Describir los componentes que constituyen la API de JMS. • Crear un cliente JMS que produce y consume mensajes mediante la API de JMS. • Crear, comprimir e implementar un bean controlado por mensaje. Secciones • Descripción de conceptos de mensajería (y cuestionario) • Descripción de la arquitectura de JMS (y cuestionario) • Creación de un cliente JMS (y ejercicio guiado) • Creación de beans controlados por mensajes (y ejercicio guiado) Trabajo de laboratorio JB183-EAP7.0-es-2-20180124 Creación de aplicaciones de mensajería con JMS 319 Capítulo 8. Creación de aplicaciones de mensajería con JMS Descripción de conceptos de mensajería Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de realizar lo siguiente: • Describir la API JMS • Identificar los objetos usados para enviar y recibir mensajes Comprensión de conceptos de mensajería asíncrona La tecnología de mensajería o message-oriented-middleware (MOM) (middleware orientado a mensajes) es una herramienta fundamental utilizada por muchos desarrolladores en empresas alrededor del mundo. Al activar el procesamiento asíncrono entre sistemas acoplados de forma flexible, la mensajería permite que los desarrolladores compilen aplicaciones que sean más eficientes, más fácilmente escaladas y más confiables. Las soluciones de mensajería usan un concepto denominado colas o destinos para facilitar la transferencia de datos de una aplicación a otra. Los desarrolladores usan colas como un intermedio entre aplicaciones. Las colas almacenan datos de un mensaje a medida que se transfiere de un remitente a un receptor. Mediante una cola, la aplicación que envía (remitente) no espera por una respuesta de la aplicación que recibe (receptor). En cambio, al usar la mensajería, el remitente envía un mensaje en una cola y el receptor recupera el mensaje de la cola y, luego, procesa el mensaje. Un ejemplo práctico de comunicación asíncrona entre aplicaciones es una aplicación web de front-end para un negocio de comercio electrónico que envía datos de pedidos a un sistema de procesamiento de pedidos de back-end. Cuando un cliente realiza un pedido, la aplicación web no necesita el sistema de procesamiento para completar el pedido. En cambio, la aplicación solo necesita enviar un mensaje con la información del pedido a la cola de pedidos, y el sistema de back-end puede procesarla posteriormente. A continuación, la aplicación web envía al cliente a una pantalla de confirmación de pedidos, mientras la aplicación de back-end procesa el pedido. Otro beneficio significativo de usar la mensajería para facilitar la integración de aplicaciones es que la mensajería permite un acoplamiento flexible. Se dice que las aplicaciones que usan la mensajería tienen un acoplamiento flexible porque el único requisito para que se comuniquen es una conexión al destino y un formato de mensaje convenido. En un sistema de mensajería, los componentes de las aplicaciones se pueden escribir en muchos idiomas diferentes, siempre y cuando los componentes adhieran al mismo formato de mensaje. La flexibilidad que proporciona esta separación clara de aplicaciones también favorece la compatibilidad de la escalabilidad. Por ejemplo, en el ejemplo anterior de aplicación de comercio electrónico, cuando crece la cola de pedidos, nuevas instancias de la aplicación de back-end de procesamiento de pedidos se pueden crear fácilmente y pueden consumir mensajes de la cola en forma simultánea, lo que permite un procesamiento de datos mucho más rápido. Uso de una cola para mensajería punto a punto Point-to-point messaging (mensajería punto a punto) se produce cuando dos aplicaciones están conectadas mediante una cola, y cada mensaje que envía un productor es recibido por un solo consumidor. Si bien pueden haber varios productores o consumidores conectados a 320 JB183-EAP7.0-es-2-20180124 Uso de un tema para la mensajería de publicación/suscripción una cola en particular, un único par de productores y consumidores procesa cada mensaje. Los desarrolladores de aplicaciones a menudo usan el modelo de mensajería punto a punto por la simplicidad y escalabilidad que proporciona. Figura 8.1: Una cola en uso por aplicaciones del productor y el consumidor Los consumidores de una cola usan un modelo basado en demanda para recuperar nuevos mensajes. Esto implica que cualquier cliente receptor generalmente necesita sondear la cola para nuevos mensajes. Asimismo, en el modelo punto a punto, un consumidor de cola generalmente debe reconocer el procesamiento correcto del mensaje o se devuelve a la cola para volver a intentarlo. Parte de este comportamiento de reintentos depende del código de aplicaciones y la tecnología de mensajería. Uso de un tema para la mensajería de publicación/ suscripción Publish-subscribe messaging se produce cuando varias aplicaciones se suscriben a un tema en particular, y todo mensaje enviado a ese tema se duplica y se copia a cada uno de los suscriptores. Los temas son muy similares a las colas, siendo el número de consumidores la única diferencia. Los desarrolladores se refieren a los consumidores de un tema como suscriptores. Cuando una nueva aplicación se suscribe a un tema, pasa a recibir todo nuevo mensaje que se envía a ese tema, pero no recibe ningún mensaje anterior enviado al tema antes de la suscripción. Figura 8.2: Un tema en uso por un productor y un conjunto de aplicaciones del consumidor A diferencia de la mensajería punto a punto, que usa un modelo basado en demanda, la mensajería de publicación/suscripción usa un modelo basado en demanda. En un modelo JB183-EAP7.0-es-2-20180124 321 Capítulo 8. Creación de aplicaciones de mensajería con JMS basado en demanda, el proveedor de mensajería es responsable de enviar mensajes entrantes a todos los suscriptores. Cuando una aplicación crea una nueva suscripción a un tema para recibir mensajes, existen dos tipos de suscripciones: duradera y no duradera. Durante el proceso de suscripción, la aplicación debe especificar si su suscripción es duradera o no duradera. Al usar suscripciones durables, si la aplicación se desconecta del tema temporalmente, todo mensaje enviado al tema mientras la aplicación esté desconectada se guarda y se entrega la próxima vez que el suscriptor durable se vuelve a conectar. Alternativamente, una suscripción no durable no guarda ningún mensaje recibido mientras el suscriptor está desconectado. Revisión de la Especificación JMS en JSR-343 La especificación Java EE define la API Java Message Service (JMS) (Servicio de mensajes de Java) para crear un método estandarizado para que las aplicaciones de Java se conecten a, y utilicen, soluciones de mensajería empresarial. JMS se introdujo por primera vez en 2002 como JSR-914 y la versión actual usada por Java EE 7, v2.0, se mantiene bajo JSR-343. Los desarrolladores usan un conjunto de terminología común al desarrollar aplicaciones JMS. Estos términos y sus definiciones se encuentran en la siguiente tabla: Conceptos JMS Término Definición Cliente JMS Programas o aplicaciones de Java que envían o reciben mensajes. Mensaje Generalmente, datos en formato estandarizado, enviados entre aplicaciones o clientes. Proveedor JMS El componente o la tecnología de mensajería que implementa la API JMS. Al trabajar con JBoss EAP, un proveedor JMS se compila en el servidor o se puede configurar un proveedor externo. Objeto administrado Una aplicación usa un objeto JMS previamente configurado, definido en la configuración del contenedor. Generalmente, las fábricas de conexiones y los destinos son objetos administrados, definidos a nivel del servidor y puestos a disposición por el contenedor para su uso por aplicaciones implementadas. Fábrica de conexiones Un objeto Java que el cliente usa para crear nuevas conexiones al proveedor JMS. Destino Un objeto Java usado por un cliente para especificar la cola o el tema donde el cliente envía o recibe mensajes. Referencias Para obtener más información, consulte el capítulo JMS de la Guía de desarrollo para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ 322 JB183-EAP7.0-es-2-20180124 Revisión de la Especificación JMS en JSR-343 Referencias JMS 2.0 JSR https://jcp.org/en/jsr/detail?id=343 JB183-EAP7.0-es-2-20180124 323 Capítulo 8. Creación de aplicaciones de mensajería con JMS Cuestionario: Descripción de conceptos de mensajería Una los siguientes ítems con sus equivalentes en la tabla. El componente de middleware orientado a mensajes usado por una aplicación JMS. Objeto Java usado por un cliente JMS que especifica dónde el client intercambia mensajes. Tipo de destino usado con la mensajería de publicación/suscripción. Tipo de destino usado con mensajería punto a punto. Un objeto de Java usado para proporcionar a la aplicación conexiones al proveedor JMS. Un programa de Java que envía o recibe mensajes. Una suscripción que guarda mensajes de temas cuando la aplicación no está conectada. Término Definición Cola Tema Proveedor JMS Destino Cliente JMS 324 JB183-EAP7.0-es-2-20180124 Término Definición Suscripción durable Fábrica de conexiones JB183-EAP7.0-es-2-20180124 325 Capítulo 8. Creación de aplicaciones de mensajería con JMS Solución Una los siguientes ítems con sus equivalentes en la tabla. Término Definición Cola Tipo de destino usado con mensajería punto a punto. Tema Tipo de destino usado con la mensajería de publicación/suscripción. Proveedor JMS El componente de middleware orientado a mensajes usado por una aplicación JMS. Destino Objeto Java usado por un cliente JMS que especifica dónde el client intercambia mensajes. Cliente JMS Un programa de Java que envía o recibe mensajes. Suscripción durable Una suscripción que guarda mensajes de temas cuando la aplicación no está conectada. Fábrica de conexiones Un objeto de Java usado para proporcionar a la aplicación conexiones al proveedor JMS. 326 JB183-EAP7.0-es-2-20180124 Descripción de la arquitectura de JMS Descripción de la arquitectura de JMS Objetivo Después de completar esta sección, los estudiantes deberán ser capaces de describir componentes que integran la API JMS. Uso de JMS en JBoss EAP 7 con el Agente de mensajes Apache Artemis incorporado Para proporcionar funcionalidad de mensajería JMS a sus usuarios, JBoss Enterprise Application Platform 7 aprovecha un agente de mensajería Apache ActiveMQ Artemis como proveedor JMS incorporado. Este proveedor JMS incorporado permite a los desarrolladores compilar, implementar e integrar rápidamente aplicaciones de mensajería en JBoss EAP sin el costo o gastos generales de un proveedor de mensajería externo. Si bien Apache ActiveMQ Artemis se lanzó y se vende como un producto independiente de Red Hat JBoss AMQ 7, se incluye una única instancia de agente incorporado, que se basa en una versión similar de Artemis, con cada instancia de JBoss EAP 7. La implementación de Artemis reemplaza al agente HornetQ usado por JBoss EAP 6, y es completamente compatible con versiones anteriores. JBoss EAP 7 implementa y gestiona Artemis como el subsistema messaging-activemq. Después de instalar JBoss EAP, el único perfil que permite el subsistema de mensajería es el archivo de configuración standalone-full.xml. Por lo tanto, es necesario usar este archivo de configuración o habilitar en forma manual el subsistema messaging-activemq en un archivo de configuración standalone.xml personalizado. nota El uso del agente de mensajería ActiveMQ Artemis incorporado requiere prácticamente la misma configuración y administración que un agente Artemis independiente. Si bien estas tareas no se encuentran dentro del alcance de este curso, se puede consultar más información sobre estos temas y otros materiales relacionados con Artemis en el curso de capacitación de Red Hat JB440: Red Hat JBoss AMQ Administration. Uso de objetos administrados para JMS con JBoss EAP 7 Una práctica común al trabajar con mensajería en un servidor de aplicaciones como JBoss EAP 7 es mantener los destinos JMS y las fábricas de conexión de manera administrativa en la configuración del servidor, en lugar de en el propio código de aplicaciones. La razón para ello es que la fábrica de conexiones y los objetos de destino generalmente incluyen una configuración específica del entorno, como hosts y puertos, así como información potencialmente confidencial, como nombres de usuario y contraseñas. Asimismo, según el proveedor JMS que se use, los parámetros de configuración necesarios para conectarse a ese proveedor pueden cambiar drásticamente. El uso de objetos JB183-EAP7.0-es-2-20180124 327 Capítulo 8. Creación de aplicaciones de mensajería con JMS administrados simplifica el código de aplicaciones al proporcionar una capa de abstracción para extraer esta configuración específica de proveedores del desarrollador. Los clientes JMS acceden a estos objetos administrativos mediante las interfaces API JMS que son independientes del proveedor JMS, lo que permite que el cliente JMS se ejecute posiblemente con muchos proveedores de JMS diferentes, con pocas o ninguna modificación al código durante el switching. Al usar JBoss EAP 7, los objetos administrados por JM se crean como parte del subsistema messaging-activemq. El siguiente snippet del standalone-full.xml incluye algunos ejemplos de objetos administrados: <subsystem xmlns="urn:jboss:domain:messaging-activemq:1.0"> <server name="default"> ...Some configuration omitted... <jms-queue name="ExpiryQueue" entries="java:/jms/queue/ExpiryQueue"/> <jms-queue name="DLQ" entries="java:/jms/queue/DLQ"/> <jms-queue name="helloWorldQueue" entries="queue/helloWorldQueue java:jboss/jms/ queue/helloWorldQueue"/> <jms-queue name="TodoListQueue" entries="queue/TodoListQueue java:jboss/jms/queue/ TodoListQueue"/> <connection-factory name="InVmConnectionFactory" entries="java:/ConnectionFactory" connectors="in-vm"/> <connection-factory name="RemoteConnectionFactory" entries="java:jboss/exported/ jms/RemoteConnectionFactory" connectors="http-connector"/> <pooled-connection-factory name="activemq-ra" transaction="xa" entries="java:/JmsXA java:jboss/DefaultJMSConnectionFactory" connectors="in-vm"/> </server> </subsystem> Esto define una cola JMS como un objeto administrado con los nombres JNDI queue/ TodoListQueue y java:jboss/jms/queue/TodoListQueue. Esto define una fábrica de conexiones JMS como un objeto administrado con el nombre JNDI java:/ConnectionFactory. nota La creación de objetos administrados para JBoss EAP 7 no está dentro del alcance de este curso. Más información sobre este tema y otros temas relacionados con EAP se puede consultar en el curso de capacitación de Red Hat JB248: JBoss Administration I Revisión de componentes de un Cliente JMS Existe una serie de componentes de la API JMS que conforman un cliente JMS. Algunos de estos componentes se proporcionan como objetos administrados y otros deben ser creados mediante programación por un desarrollador utilizando la API JMS. En la siguiente lista se resumen los componentes de alto nivel que son necesarios para compilar un cliente JMS: Fábrica de conexiones Una fábrica de conexiones JMS es un objeto administrado que el cliente usa para crear una conexión al proveedor JMS. Generalmente, la fábrica de conexiones incluye los parámetros de conexión o autenticación necesarios predefinidos por un administrador de servidores. Cada fábrica de conexiones es una instancia de las interfaces 328 JB183-EAP7.0-es-2-20180124 Uso de objetos administrados para JMS con JBoss EAP 7 ConnectionFactory, QueueConnectionFactory o TopicConnectionFactory de Java. Generalmente, la fábrica de conexiones gestionada, provista por el contenedor de aplicaciones, se inyecta en un cliente JMS usando la anotación CDI de @Resource. @Resource(lookup = "java:jboss/ConnectionFactory") private static ConnectionFactory connectionFactory; También es posible realizar la búsqueda de JNDI en forma manual si no se define un objeto administrado: final Properties env = new Properties(); env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY); env.put(Context.PROVIDER_URL, System.getProperty(Context.PROVIDER_URL, PROVIDER_URL)); env.put(Context.SECURITY_PRINCIPAL, userName); env.put(Context.SECURITY_CREDENTIALS, password); namingContext = new InitialContext(env); // Perform the JNDI lookups String cfString = System.getProperty("connection.factory", DEFAULT_CONNECTION_FACTORY); ConnectionFactory connectionFactory = (ConnectionFactory) namingContext.lookup(cfString); Destino Un destino JMS es un objeto administrado que un cliente usa para especificar la dirección de destino para los mensajes que envía o la dirección de origen en la que debe recibir mensajes. Dependiendo del tipo de mensajería, un destino puede ser una queue (cola) o un topic (tema). Algunos clientes JMS inclusive interactúan con varios destinos. De manera similar a la fábrica de conexiones, el destino generalmente se inyecta en el cliente a través de la anotación CDI @Resource o mediante una búsqueda JNDI para encontrar el objeto administrado. @Resource(lookup = "jms/MyQueue") private static Queue queue; @Resource(lookup = "jms/MyTopic") private static Topic topic; Conexión Una conexión JMS es la conexión virtual al proveedor JMS y generalmente se crea en el cliente usando la API JMS para crear un contexto JMS. La implementación de la conexión no afecta el código del cliente JMS, y el administrador de servidores la gestiona por completo en la configuración de la fábrica de conexiones. Sesión Una sesión JMS es un recurso uniproceso tanto para enviar como para recibir mensajes, y se crea mediante una conexión. La clase Session (Sesión) es generalmente la que crea el listener, el productor y los objetos del mensaje. Las sesiones de JMS también proporcionan el comportamiento transaccional, lo que permite a los clientes JMS proporcionar transacciones locales y agrupar un conjunto de acciones de mensajes, ya JB183-EAP7.0-es-2-20180124 329 Capítulo 8. Creación de aplicaciones de mensajería con JMS sea enviar o recibir, en una única unidad de trabajo que se puede confirmar si funciona correctamente o revertir en caso de que se produzca una falla. Contexto Un contexto JMS es la combinación de una sesión y una conexión que proporciona tanto una conexión directa al proveedor de JMS como una clase uniproceso que se puede usar para enviar mensajes al, y recibir mensajes del, proveedor. Al escribir en un cliente JMS, un desarrollador generalmente crea un objeto JMSContext de la ConnectionFactory que se inyectó. JMSContext context = connectionFactory.createContext(); Productor de mensajes Un objeto de productor de mensajes JMS se crea por un contexto JMS y se puede usar para enviar mensajes a un destino. La API JMS v2.0 proporciona la interfaz JMSProducer para representar productores de mensajes. El contexto JMS proporciona un método para crear un productor mediante un contexto: JMSProducer producer = context.createProducer(); Los desarrolladores pueden usar una invocación simple de método para enviar un mensaje a un destino: producer.send(dest, message); Consumidor de mensajes Un objeto de consumidor de mensajes JMS se crea por un contexto JMS y se puede usar para recibir mensajes de un destino en forma asíncrona. La API JMS v2.0 proporciona la interfaz JMSConsumer para representar consumidores de mensajes. El contexto JMS proporciona un método para crear un consumidor conectado a un destino mediante un contexto: JMSConsumer consumer = context.createConsumer(dest); Los desarrolladores pueden usar una invocación simple de método para recibir mensajes: Message m = consumer.receive(); El método receive() toma un parámetro de tiempo límite opcional, que define por cuánto tiempo debe bloquear la ejecución antes de arrojar el resultado null (nulo). En cambio, para la entrega asíncrona, se debe usar un MessageListener o un bean controlado por mensajes. Esto se aborda en detalle en una sección posterior. Comprensión de los componentes de un mensaje JMS Los mensajes JMS usan un formato básico estandarizado para permitir que los mensajes sean lo suficientemente flexibles como para adaptarse a la mayoría de los casos de uso, así como proporcionan potencial de compatibilidad con tecnologías de mensajería no 330 JB183-EAP7.0-es-2-20180124 Comprensión de los componentes de un mensaje JMS pertenecientes a JMS. La estructura básica de un mensaje de JMS está conformada por tres partes: encabezados, propiedades y un cuerpo. Encabezados Los encabezados de JMS son usados por clientes y proveedores para enrutar y clasificar mensajes. Los encabezados son pares de claves/valores, pero existe un conjunto predeterminado de claves posibles definidas por la especificación de JMS. La interfaz Message (Mensaje) proporciona métodos para obtener y establecer todos los valores de encabezado posibles. En la siguiente tabla se resumen los campos de encabezados JMS y cómo los desarrolladores de aplicaciones y proveedores de JMS suelen usarlos: Campos de encabezado JMS Nombre del encabezado Descripción JMSMessageID El identificador único para cada mensaje enviado por un proveedor se establece automáticamente. JMSDestination El destino al que se envía el mensaje. JMSPriority El nivel de prioridad del mensaje. Este se puede usar para reordenar mensajes y hacer avanzar mensajes más importantes al tope de la cola. JMSDeliveryMode Las configuraciones de persistencia del mensaje controlan cómo el proveedor gestiona el mensaje. JMSDeliveryTime El sello de hora cuando el proveedor de JMS entregó el mensaje. JMSTimeStamp El sello de hora cuando el mensaje se entregó al proveedor de JMS. JMSExpiration La hora en que el mensaje se debe considerar caducado. JMSCorrelationID El ID de otro mensaje, usado para relacionar dos mensajes, generalmente establecido por el cliente. JMSReplyTo El destino al que se pueden enviar las respuestas a mensajes. JMSRedelivered El estado de si actualmente, el mensaje es o no es una reentrega. El método send (enviar) del cliente JMS establece automáticamente la mayoría de los encabezados de JMS, pero el cliente debe establecer algunos encabezados en forma manual, por lo que estos quedan en blanco si no se establecen. Estos encabezados específicos del cliente incluyen JMSReplyTo, JMSCorrelationID y JMSType. Propiedades Las propiedades proporcionan la capacidad de establecer valores adicionales en un mensaje JMS, que no fueron provistos por encabezados estándares. De manera similar a los encabezados, las propiedades son pares de claves/valores, sin limitaciones sobre las posibles claves que puede usar un desarrollador. Las propiedades se deben establecer antes de enviar un mensaje y son de solo lectura en mensajes JMS recibidos por un cliente. La interfaz Message proporciona los siguientes métodos para obtener y establecer las propiedades específicas para todos los tipos primitivos, como int y double. La superclase Object (Objeto) también se admite, y permite que las propiedades sean JB183-EAP7.0-es-2-20180124 331 Capítulo 8. Creación de aplicaciones de mensajería con JMS de cualquier tipo. También hay un método getPropertyNames() que devuelve una Enumeration (Enumeración) de todos los posibles nombres de propiedad para un mensaje JMS específico. Cuerpo del mensaje La API JMS incluye seis tipos diferentes de mensajes basados en el tipo de objeto del cuerpo del mensaje. Esto permite a los desarrolladores enviar y recibir diferentes tipos de datos fácilmente mediante una variedad de objetos de Java para representar el cuerpo del mensaje, que permite una mayor flexibilidad. En la siguiente tabla se resumen los seis tipos y el contenido de sus cuerpos: Tipos de cuerpos de mensaje JMS Tipo Cuerpo del mensaje Message Un cuerpo vacío. Este tipo de mensaje contiene solo encabezados y propiedades, y se usa cuando la aplicación no necesita el cuerpo de un mensaje. BytesMessage Un flujo de bytes, generalmente usado para codificar un cuerpo para que coincida con un formato de mensaje existente. StreamMessage Un flujo de primitivos de Java que debe leerse de manera secuencial. TextMessage Un objeto String de Java que puede incluir datos de JSON o XML. ObjectMessage Cualquier objeto de Java serializable. MapMessage Un conjunto de pares claves/valores en el que las claves son objetos String y los valores son primitivos de Java. La API JMS proporciona métodos en Context para crear cada tipo de mensaje. A continuación, se muestra un ejemplo de la creación de un TextMessage: TextMessage hello = context.createTextMessage(); hello.setText("Hello World!"); context.createProducer().send(hello); Referencias Para obtener más información, consulte el capítulo JMS de la Guía de desarrollo para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ 332 JB183-EAP7.0-es-2-20180124 Cuestionario: Descripción de la arquitectura de JMS Cuestionario: Descripción de la arquitectura de JMS Una los siguientes ítems con sus equivalentes en la tabla. Conexión Consumidor Fábrica de conexiones Contexto Productor Destino Sesión Descripción Componente JMS Un objeto administrado usado por clientes para establecer la dirección de destino u origen para los mensajes. Un objeto administrado que el cliente usa para crear una conexión al proveedor JMS. Un recurso uniproceso tanto para enviar como para recibir mensajes, creado mediante una conexión. La combinación de una sesión y una conexión, que proporciona tanto una conexión al proveedor de JMS como una clase uniproceso para enviar mensajes al proveedor y recibir mensajes de este. La conexión virtual al proveedor JMS, generalmente creada en el cliente usando primero la API JMS para crear un contexto JMS. JB183-EAP7.0-es-2-20180124 333 Capítulo 8. Creación de aplicaciones de mensajería con JMS Descripción Componente JMS Creado por un contexto JMS y utilizado para enviar mensajes a un destino. Creado por un contexto JMS y utilizado para recibir mensajes de un destino en forma asíncrona. 334 JB183-EAP7.0-es-2-20180124 Solución Solución Una los siguientes ítems con sus equivalentes en la tabla. Descripción Componente JMS Un objeto administrado usado por clientes para establecer la dirección de destino u origen para los mensajes. Destino Un objeto administrado que el cliente usa para crear una conexión al proveedor JMS. Fábrica de conexiones Un recurso uniproceso tanto para enviar como para recibir mensajes, creado mediante una conexión. Sesión La combinación de una sesión y una conexión, que proporciona tanto una conexión al proveedor de JMS como una clase uniproceso para enviar mensajes al proveedor y recibir mensajes de este. Contexto La conexión virtual al proveedor JMS, generalmente creada en el cliente usando primero la API JMS para crear un contexto JMS. Conexión Creado por un contexto JMS y utilizado para enviar mensajes a un destino. Productor Creado por un contexto JMS y utilizado para recibir mensajes de un destino en forma asíncrona. Consumidor JB183-EAP7.0-es-2-20180124 335 Capítulo 8. Creación de aplicaciones de mensajería con JMS Creación de un Cliente JMS Objetivo Después de completar esta sección, los estudiantes deberán ser capaces de crear un cliente JMS que produce y consume mensajes usando la API JMS. Acceso a objetos administrados en un Cliente JMS En cualquier empresa que usa middleware orientado a mensajes, los administradores del sistema y, en algunas ocasiones, los desarrolladores, son responsables de configurar servidores de aplicaciones para tener los objetos administrados necesarios para admitir mensajería. Estos objetos administrados contienen toda la configuración de conexión relevante requerida para comunicarse con el proveedor de JMS. Al usar Red Hat JBoss EAP 7, los nombres de JNDI para estos objetos se encuentra en el archivo de configuración standalone.xml. Al eliminar estos detalles de conexión del código de la aplicación, esta se vuelve menos frágil y fácil de mover de un entorno a otro. Después de definir los objetos administrados, estos quedan disponibles directamente del código de cliente JMS mediante técnicas como la inyección de dependencias. Por ejemplo, las aplicaciones usan JNDI para encontrar los objetos de la fábrica de destino y conexión necesarios para crear un objeto JMSContext. La anotación @Resource inyecta directamente un objeto de destino, como una Cola (Queue) o Tema (Topic), así como un objeto ConnectionFactory que usa solo el nombre JNDI definido en la configuración del servidor. En el siguiente ejemplo se muestra la anotación @Resource para inyectar un objeto Queue (Cola): @Resource(mappedName = "java:jboss/jms/queue/helloWorldQueue") private Queue helloWorldQueue; La anotación @Resource también puede inyectar unaConnectionFactory, como se muestra en el siguiente ejemplo: @Resource(mappedName = "java:comp/DefaultJMSConnectionFactory") private static ConnectionFactory connectionFactory; private JMSContext context; @PostConstruct public void init(){ context = connectionFactory.createContext(); } Después de usar la anotación @Resource para inyectarla, se usa ConnectionFactory para crear el objeto JMSContext. JMSContext crea otros objetos JMS, como mensajes, productores o consumidores. De manera alternativa, se puede crear un objeto JMSContext mediante una anotación @Inject. Este enfoque elimina el código innecesario para crear el objeto JMSContext cuando la fábrica de conexiones ya no requiere más personalización en el código de la aplicación. Existen dos opciones: especificar el nombre JNDI de la fábrica de conexiones para 336 JB183-EAP7.0-es-2-20180124 Uso de la Interfaz del productor de mensajes para enviar mensajes usar al crear el objeto JMSContext, o usar la fábrica de conexiones predeterminada para el servidor de aplicaciones. Para usar la fábrica de conexiones predeterminada para el servidor de aplicaciones, use la anotación @Inject como se muestra en el siguiente ejemplo, antes de declarar el objeto JMSContext: @Inject private JMSContext context; Si se requiere una fábrica de conexiones específica además de la predeterminada, use la anotación @JMSConnectionFactory junto con la anotación @Inject como se muestra en el siguiente ejemplo: @Inject @JMSConnectionFactory("jms/MyConnectionFactory") private JMSContext context; Uso de la Interfaz del productor de mensajes para enviar mensajes Un productor de mensajes es un tipo de objeto JMS creado por el objeto JMSContext que envía mensajes JMS de un destino. La interfaz JMSProducer, introducida en JMS v2.0 encapsula la funcionalidad de los productores de mensajes. La clase del productor es liviana y no consume muchos recursos del sistema cuando se crea o se encuentra en uso. Por este motivo, una única instancia de JMSProducer se puede volver a utilizar para enviar varios mensajes o el productor se puede crear para cada mensaje. En el siguiente ejemplo se muestra cómo se crea una instancia JMSProducer, se la almacena en una variable y, luego, se usa esa variable para enviar un mensaje a un destino: JMSProducer producer = context.createProducer(); TextMessage message = context.createTextMessage(msg); producer.send(helloWorldQueue, message); Cree la instancia JMSProducer mediante el objeto JMSContext y almacénela en la variable producer (productor). Use el objeto JMSContext para crear una nueva instancia TextMessage de un objeto String nombrado msg. Envíe el mensaje al objeto de destino helloWorldQueue mediante la variable producer (productor). Uso de la Interfaz del consumidor de mensajes para recibir mensajes Un consumidor de mensajes es un tipo de objeto JMS creado por el objeto JMSContext que recibe mensajes JMS de un destino de manera asíncrona, a través de una invocación de método. La interfaz JMSConsumer, introducida en JMS v2.0 se usa para encapsular la funcionalidad de los consumidores de mensajes. JB183-EAP7.0-es-2-20180124 337 Capítulo 8. Creación de aplicaciones de mensajería con JMS La principal funcionalidad de un consumidor de mensajes se proporciona mediante el método receive(), que incluye un parámetro de timeout (tiempo límite) opcional para establecer el tiempo que debe esperar una aplicación para recibir un mensaje del destino antes del tiempo límite. Para crear un consumidor de mensajes flexible, asegúrese de manejar las excepciones de JMS que se generan durante la entrega de mensajes. Use un bloque try-catch al recibir mensajes de un destino y maneje todas las excepciones JMS relevantes. El proveedor de JMS puede asignar algunos recursos en nombre de un JMSConsumer. Los clientes JMS deben cerrar consumidores cuando no son necesarios, en lugar de depender de la recopilación de residuos, que puede no realizarse en forma oportuna, aumentando así el peso que la aplicación coloca en el sistema. Generalmente, el consumidor invoca el método close() en el bloque finally una vez que ya no se necesite el consumidor. En el siguiente ejemplo se muestra cómo se crea una instancia JMSConsumer, se almacena el consumidor en una variable y, luego, se usa esa variable para recibir un mensaje de un destino: JMSConsumer consumer = context.createConsumer(helloWorldQueue); try { TextMessage msg = (TextMessage) consumer.receiveNoWait(); if(msg != null) { System.out.println("Received Message: "+ msg); return msg.getBody(String.class); }else { return null; } } catch (Exception e) { e.printStackTrace(); return null; } finally { consumer.close(); } Cree la instancia JMSConsumer usando el objeto JMSContext y envíe la helloWorldQueue como destino; luego, almacénelo en la variable consumer. Use receiveNoWait para verificar un nuevo mensaje en la cola, pero no bloquee si no hay ningún mensaje disponible. Cierre el objeto del consumidor para liberar la conexión y conservar recursos. Demostración: Creación de un Cliente JMS 1. Ejecute el siguiente comando para preparar archivos usados por esta demostración. [student@workstation ~]$ demo hello-jms setup 2. Inicie JBDS e importe el proyecto hello-jms. Este proyecto es una aplicación web simple que usa una página JSF respaldada por un EJB con alcance de solicitud y estado para imprimir un mensaje de bienvenida simple para el usuario después de ingresar su nombre. También envía un mensaje JMS a una 338 JB183-EAP7.0-es-2-20180124 Demostración: Creación de un Cliente JMS cola cada vez que saluda al usuario. También existe la opción de recuperar el mensaje más antiguo de la cola y mostrarlo en la página. 3. Inspeccione el archivo pom.xml y observe la dependencia en la librería JBoss que el servidor usa para la especificación JMS. <dependency> <groupId>org.jboss.spec.javax.jms</groupId> <artifactId>jboss-jms-api_2.0_spec</artifactId> <scope>provided</scope> </dependency> 4. Actualice la clase EJB JMSClient en el paquete messaging para inyectar un objeto JMSContext conectado al agente Artemis incorporado y asigne la Cola al objeto administrado usando JNDI: @Startup @Singleton public class JMSClient { //TODO Map the destination queue to the admin object using JNDI and @Resource @Resource(mappedName = "java:jboss/jms/queue/helloWorldQueue") private Queue helloWorldQueue; //TODO Inject a JMSContext to get a Connection and Session to the embedded broker @Inject JMSContext context; ... 5. En la clase EJB JMSClient, termine el método sendMessage y cree un MessageProducer para enviar cada mensaje de saludo entrante a la cola: public void sendMessage(String msg) { try { //TODO Create a JMSProducer JMSProducer producer = context.createProducer(); //TODO Create a TextMessage TextMessage message = context.createTextMessage(msg); //TODO Send the message producer.send(helloWorldQueue, message); System.out.println("Sent Message: "+ msg); } catch (Exception e) { e.printStackTrace(); } } 6. En la clase EJB JMSClient, finalice el método getMessage, cree una clase MessageConsumer y, luego, reciba el mensaje más antiguo de la cola: public String getMessage() { //TODO Create a JMS Consumer JB183-EAP7.0-es-2-20180124 339 Capítulo 8. Creación de aplicaciones de mensajería con JMS JMSConsumer consumer = context.createConsumer(helloWorldQueue); try { //TODO receive a message without waiting TextMessage msg = (TextMessage) consumer.receiveNoWait(); if(msg != null) { System.out.println("Received Message: "+ msg); return msg.getBody(String.class); }else { return null; } } catch (Exception e) { e.printStackTrace(); return null; } finally { //TODO close the consumer consumer.close(); } } 7. Inicie el servidor JBoss EAP local dentro de JBDS. 8. Implemente la aplicación en el servidor JBoss EAP local y pruébela en un explorador. En una ventana de terminal, ejecute el siguiente comando: [student@workstation ~]$ cd /home/student/JB183/labs/hello-jms [student@workstation hello-jms]$ mvn wildfly:deploy Abra http://localhost:8080/hello-jms/ en su explorador, luego, pruebe la aplicación y asegúrese de que muestre el saludo correctamente para cada nombre que se ingresa y que pueda recuperar esos saludos de la cola correctamente. 9. Anule la implementación de la aplicación y detenga el servidor. [student@workstation hello-jms]$ mvn wildfly:undeploy Referencias Para obtener más información, consulte el capítulo JMS de la guía Configuración de mensajería para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ 340 JB183-EAP7.0-es-2-20180124 Ejercicio guiado: Creación de un cliente JMS Ejercicio guiado: Creación de un cliente JMS En este ejercicio, escribirá un cliente JMS que usa la API JMS y la cola ubicada en el agente Artemis en JBoss EAP para enviar y recibir mensajes JMS. Resultado Deberá ser capaz de usar la API JMS y los objetos administrados provistos por JBoss EAP para compilar una instancia de MessageProducer y una interfaz MessageConsumer para enviar y recibir mensajes de una cola. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab jms-client setup Pasos 1. Abra JBDS e importe el proyecto de Maven. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta jms-client y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Revise los nombres JNDI de los objetos JMS administrados, definidos en la configuración del servidor JBoss EAP 7 local. 2.1. Abra una ventana de terminal en la máquina virtual workstation y, usando su editor de texto preferido, abra el archivo de configuración EAP en /opt/eap/ standalone/configuration/standalone-full.xml. [student@workstation ~]$ less /opt/eap/standalone/configuration/\ standalone-full.xml 2.2. Desplácese hacia abajo en el archivo hasta ver el subsistema urn:jboss:domain:messaging-activemq:1.0: JB183-EAP7.0-es-2-20180124 341 Capítulo 8. Creación de aplicaciones de mensajería con JMS <subsystem xmlns="urn:jboss:domain:messaging-activemq:1.0"> <server name="default"> ... <jms-queue name="ExpiryQueue" entries="java:/jms/queue/ExpiryQueue"/> <jms-queue name="DLQ" entries="java:/jms/queue/DLQ"/> <jms-queue name="helloWorldQueue" entries="queue/helloWorldQueue java:jboss/ jms/queue/helloWorldQueue"/> <jms-queue name="TodoListQueue" entries="queue/TodoListQueue java:jboss/jms/ queue/TodoListQueue"/> <connection-factory name="InVmConnectionFactory" entries="java:/ ConnectionFactory" connectors="in-vm"/> <connection-factory name="RemoteConnectionFactory" entries="java:jboss/ exported/jms/RemoteConnectionFactory" connectors="http-connector"/> <pooled-connection-factory name="activemq-ra" transaction="xa" entries="java:/ JmsXA java:jboss/DefaultJMSConnectionFactory" connectors="in-vm"/> </server> </subsystem> Note que la helloWorldQueue tiene una entrada JNDI de java:jboss/jms/ queue/helloWorldQueue. 2.3. Cierre el editor de texto y no guarde los cambios al archivo standalone-full.xml. Advertencia Pueden producirse problemas en el ejercicio si se introducen cambios inesperados en el archivo de configuración. 3. Configure el contexto JMS y el destino. 3.1. Abra la clase JMSClient; para ello, expanda el ítem jms-client en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en jms-client > Java Resources > src/main/java > com.redhat.training.messaging y expándalo. Haga doble clic en el archivo JMSClient.java. 3.2. Use la anotación @Inject para inyectar el objeto JMSContext predeterminado, que proporciona una conexión al agente Artemis incorporado que se ejecuta en el servidor JBoss local. //TODO Inject a JMSContext to get a Connection and Session to the embedded broker @Inject JMSContext context; 3.3. Use la anotación @Resource para inyectar el destino helloWorldQueue: /TODO Map the destination queue to the admin object using JNDI and @Resource @Resource(mappedName = "java:jboss/jms/queue/helloWorldQueue") private Queue helloWorldQueue; Asegúrese de que el atributo mappedName esté configurado correctamente en el nombre JNDI de la cola. 342 JB183-EAP7.0-es-2-20180124 3.4. Guarde los cambios en el archivo con Ctrl+S. 4. Cree un productor JMS que coloque los mensajes en el helloWorldQueue. 4.1. Use el método createProducer provisto por la interfaz JMSContext para compilar una instancia de MessageProducer en el método sendMessage: //TODO Create a JMSProducer JMSProducer producer = context.createProducer(); 4.2. Cree un TextMessage usando la interfaz JMSContext para asignar el valor del parámetro msg en el cuerpo del mensaje JMS: //TODO Create a TextMessage TextMessage message = context.createTextMessage(msg); 4.3. Envíe el mensaje al destino, usando el productor: //TODO Send the message producer.send(helloWorldQueue, message); 5. Cree un consumidor JMS que lea un mensaje de la helloWorldQueue. 5.1. Use el método createConsumer provisto por la interfaz JMSContext para compilar una instancia de MessageConsumer para el destino helloWorldQueue en el método getMessage: //TODO Create a JMS Consumer JMSConsumer consumer = context.createConsumer(helloWorldQueue); 5.2. Intente leer un mensaje de la cola, sin esperar si hay mensajes disponibles. Use el método receiveNoWait provisto por la interfaz MessageConsumer y difunda el resultado en una instancia TextMessage: //TODO receive a message without waiting TextMessage msg = (TextMessage) consumer.receiveNoWait(); 5.3. Cierre el consumidor después de completar todo mediante el método close (cerrar): //TODO close the consumer consumer.close(); 6. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y muestre el siguiente mensaje: JB183-EAP7.0-es-2-20180124 343 Capítulo 8. Creación de aplicaciones de mensajería con JMS INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 7. Implemente la aplicación en JBoss EAP con Maven; para ello, ejecute los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/jms-client [student@workstation jms-client]$ mvn wildfly:deploy Una vez finalizado, aparece BUILD SUCCESS, como se muestra en el próximo ejemplo: [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] -----------------------------------------------------------------------BUILD SUCCESS -----------------------------------------------------------------------Total time: 17.116 s Finished at: 2016-12-01T07:26:55-05:00 Final Memory: 35M/210M ------------------------------------------------------------------------ Valide la implementación en el registro de servidores que se muestra en la pestaña Console (Consola) en JBDS. Cuando se implementa la aplicación, aparece lo siguiente en el registro: INFO [org.jboss.as.server] (management-handler-thread - 9) WFLYSRV0010: Deployed "jms-client.war" (runtime-name : "jms-client.war") 8. Pruebe la aplicación en un explorador. 8.1. Abra http://localhost:8080/jms-client en un explorador en la máquina virtual workstation. Figura 8.3: Página de inicio de la aplicación 8.2. Intente leer un mensaje de la cola usando el botón Get Oldest Message On Queue (Obtener el mensaje más antiguo de la cola). Aparece el mensaje Queue is empty! (¡La cola está vacía!). 344 JB183-EAP7.0-es-2-20180124 8.3. Ingrese su nombre en el campo de texto y presione el botón Submit (Enviar). La aplicación lo saluda y muestra la hora actual en el servidor: Figura 8.4: Aplicación saludando al usuario por su nombre con la hora actual del servidor 8.4. Intente leer un mensaje de la cola usando el botón Get Oldest Message On Queue (Obtener el mensaje más antiguo de la cola). En este momento, se muestra el último saludo: Figura 8.5: Obtener el mensaje más antiguo de la cola 9. Anule la implementación de la aplicación y detenga JBoss EAP. 9.1. Ejecute el siguiente comando para anular la implementación de la aplicación: [student@workstation jms-client]$ mvn wildfly:undeploy 9.2. Para cerrar el proyecto, haga clic con el botón derecho en el proyecto jms-client del Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 9.3. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 de la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el ejercicio guiado. JB183-EAP7.0-es-2-20180124 345 Capítulo 8. Creación de aplicaciones de mensajería con JMS Creación de MDB Objetivo Tras finalizar esta sección, los estudiantes deberán ser capaces de crear, empaquetar e implementar un bean controlado por mensajes. Comprensión del ciclo de vida de un bean controlado por mensajes La especificación Java EE incluye beans controlados por mensajes (MDB) como mecanismo para permitir un consumo asíncrono de mensajes de un destino JMS. A diferencia de otros beans de Java, los MDB no exponen funciones o métodos públicos para que otros beans accedan a través de la inyección de dependencias. En cambio, toda la comunicación con un MDB se lleva a cabo a través de un JMS. Cada MDB está configurado para escuchar en un destino JMS específico, a través de un objeto administrado. El servidor de aplicaciones, JBoss EAP, es responsable de gestionar el ciclo de vida de un MDB. El servidor de aplicaciones define un conjunto (pool) de MDB, que permite un procesamiento simultáneo de mensajes. El procesamiento de mensajes simultáneos proporciona una mejora considerable en el rendimiento de mensajes. El servidor crea los MDB en el conjunto (pool) automáticamente durante el inicio. Cuando el destino en el que está escuchando el MDB recibe un nuevo mensaje, el contenedor de aplicaciones invoca automáticamente el método onMessage en una de las instancias MDB previamente creadas. La interfaz MessageListener, que todos los MDB deben implementar, requiere este método onMessage. Una vez que el MDB termina el procesamiento, la instancia del MDB se devuelve al conjunto (pool) para su reutilización. El uso de un conjunto (pool) de MDB mejora el rendimiento de la aplicación, ya que, cuando el destino recibe mensajes, ya se crearon instancias de la clase MDB que está lista para procesar el mensaje de inmediato. Figura 8.6: Los MDB escuchan colas específicas Debido a la ejecución asíncrona y el comportamiento de agrupación del MDB, su implementación tiene varios requisitos. Los MDB deben estar completamente sin estado, y no deben conservar el estado de conversación o los datos del mensaje. No obstante, los MDB pueden usar la inyección para acceder a otros recursos con estado, como información de conexión del proveedor de JMS, una conexión de base de datos o una instancia de otro EJB. Asimismo, todas las instancias de una clase de MDB deben ser iguales y no tienen relación con ningún cliente específico, lo que permite que el contenedor de la aplicación elija entre todas ellas; esto hace posible la simultaneidad. 346 JB183-EAP7.0-es-2-20180124 Revisión de la Interfaz de Listener de Mensajes La interfaz MessageConsumer de JMS difiere de una MDB en varias formas importantes. El desarrollador debe invocar manualmente el MessageConsumer, mientras se crea una MDB de manera automática. Asimismo, el consumidor de JMS solo permite una verificación asíncrona para nuevos mensajes y generalmente es uniproceso, a menos que el desarrollador implemente manualmente la simultaneidad. En contraste, el MDB es asíncrono y multiproceso. Por estos motivos, los MDB son una solución más sólida para las aplicaciones Java EE que necesitan consumir mensajes de un destino de manera asíncrona. Revisión de la Interfaz de Listener de Mensajes Todas las MDB deben implementar la interfaz MessageListener. El único método público requerido por esta interfaz es el método onMessage, que toma un mensaje JMS como argumento y tiene un tipo de devolución void (nulo). El servidor de aplicaciones invoca automáticamente este método y envía el mensaje de JMS que se recibió en el destino como el argumento. El desarrollador de MDB es responsable de implementar la lógica de procesamiento de mensajes personalizados que se ejecuta al invocar onMessage. En el siguiente ejemplo de MessageListener se muestra un método onMessage que verifica el tipo de mensaje y registra el contenido del mensaje: public class QueueListener implements MessageListener { private final static Logger LOGGER = Logger.getLogger(this.class.getName()); public void onMessage(Message rcvMessage) { TextMessage msg = null; try { if (rcvMessage instanceof TextMessage) { msg = (TextMessage) rcvMessage; LOGGER.info("Received Message from helloWorldQueue ===> " + msg.getText()); } else { LOGGER.warn("Incorrect Message Type!"); } } catch (JMSException e) { throw new RuntimeException(e); } } ... Observe que el método de ejemplo es completamente sin estado y no conserva datos de mensajes. El ejemplo tampoco realiza suposiciones acerca del tipo de mensaje, ya que esto no se puede garantizar. El MDB debe fallar correctamente si recibe un tipo de mensaje inesperado. Uso de anotaciones para configurar un MDB Por último, debe activar el MDB, registrarlo con el contenedor y configurar los objetos administrados que el MDB usa para determinar el destino que se debe escuchar. Para activar un MDB y relacionarlo con un destino, use la anotación @MessageDriven. Esta anotación usa propiedades de configuración de activación para configurar el MDB. Los MDB requieren una sola propiedad, destinationLookup, que es el nombre de JNDI del objeto administrado de destino. Los MDB también admiten algunas propiedades opcionales usadas para personalizar la conexión o el comportamiento del MDB. Envíe estas propiedades en la anotación @MessageDriven usando el parámetro activationConfig. Use una JB183-EAP7.0-es-2-20180124 347 Capítulo 8. Creación de aplicaciones de mensajería con JMS anotación @ActivationConfigProperty para cada propiedad, especificando el nombre de la propiedad con el atributo propertyName y el valor de la propiedad con el atributo propertyValue. El siguiente es un ejemplo de la configuración de un MDB por parte de estas anotaciones: @MessageDriven(name = "QueueListener" , activationConfig = { @ActivationConfigProperty(propertyName = "destinationLookup" "queue/helloWorldQueue"), @ActivationConfigProperty(propertyName = "destinationType" "javax.jms.Queue") }) public class QueueListener implements MessageListener { , propertyValue = , propertyValue = El atributo name (nombre) establece el nombre del MDB como QueueListener. Este es el nombre que el MDB usa para registrarse en el árbol JNDI. Este atributo es opcional y el nombre predeterminado es el nombre de clase si no se especifica lo contrario. El atributo activationConfig envía un conjunto de anotaciones @ActivationConfigProperty para configurar el MDB. La propiedad destinationLookup establece el nombre JNDI del destino por el que el MDB debe escuchar. La propiedad destinationType especifica si el destino es una queue (cola) o topic (tema). Esta propiedad es opcional. En la siguiente tabla se resumen algunas de las demás propiedades de configuración de activación disponibles: Propiedades de configuración de activación de JMS Nombre de propiedad Descripción de propiedad destinationLookup El nombre de JNDI de la queue (cola) o topic (tema). Esta propiedad es obligatoria. connectionFactoryLookup El nombre de JNDI de la fábrica de conexiones que se debe usar para conectarse al destino. Esta propiedad es opcional y, si no se especifica, se usa la fábrica de conexiones agrupadas, nombrada activemq-ra. destinationType El tipo de destino, ya sea javax.jms.Queue o javax.jms.Topic. Esta propiedad es obligatoria. messageSelector Una cadena usada para seleccionar un subconjunto de mensajes disponibles. La sintaxis es similar a la sintaxis SQL y se describe en detalle en la especificación JMS, y está más allá del alcance de este curso. Esta propiedad es opcional. acknowledgeMode El tipo de reconocimiento cuando no se usa la JMS gestionada. Los valores válidos son Autoacknowledge o Dups-ok-acknowledge. Esta propiedad es opcional. De no especificarse, el valor predeterminado es Auto-acknowledge. 348 JB183-EAP7.0-es-2-20180124 Uso de anotaciones para configurar un MDB Referencias Para obtener más información, consulte el capítulo JMS de la guía Configuración de mensajería para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ JB183-EAP7.0-es-2-20180124 349 Capítulo 8. Creación de aplicaciones de mensajería con JMS Ejercicio guiado: Creación de un Bean controlado por mensajes En este ejercicio, creará un bean controlado por mensajes para leer mensajes de una cola de manera asíncrona. Resultado Deberá ser capaz de implementar un bean controlado por mensajes para leer mensajes de una cola. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este ejercicio. [student@workstation ~]$ lab create-mdb setup Pasos 1. Abra JBDS e importe el proyecto de Maven. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta create-mdb y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Revise el conjunto (pool) MDB y los objetos administrados definidos en el archivo de configuración JBoss EAP. 2.1. Usando su editor de texto preferido, abra el archivo de configuración EAP en /opt/ eap/standalone/configuration/standalone-full.xml. [student@workstation ~]$ less /opt/eap/standalone/configuration/\ standalone-full.xml 2.2. Consulte la configuración del conjunto (pool) de hilos MDB en el archivo standalone-full.xml navegando al subsistema urn:jboss:domain:ejb3:4.0: 350 JB183-EAP7.0-es-2-20180124 <subsystem xmlns="urn:jboss:domain:ejb3:4.0"> ... <mdb> <resource-adapter-ref resource-adapter-name="${ejb.resource-adaptername:activemq-ra.rar}"/> <bean-instance-pool-ref pool-name="mdb-strict-max-pool"/> </mdb> <pools> <bean-instance-pools> <strict-max-pool name="slsb-strict-max-pool" derive-size="fromworker-pools" instance-acquisition-timeout="5" instance-acquisition-timeoutunit="MINUTES"/> <strict-max-pool name="mdb-strict-max-pool" derive-size="from-cpu-count" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/ > </bean-instance-pools> </pools> ... </subsystem> Configure el conjunto (pool) nombrado mdb-strict-max-pool como el conjunto de hilos que debe usar JBoss para todos los MDB. Cree el conjunto (pool) de hilos nombrado mdb-strict-max-pool, estableciendo su tamaño en relación al conteo total de CPU y definiendo un valor de tiempo límite de espera cuando hay MDB disponibles antes de arrojar un error. 2.3. Vea el objeto administrado creado para la cola helloWorldQueue al navegar al subsistema urn:jboss:domain:messaging-activemq:1.0: <subsystem xmlns="urn:jboss:domain:messaging-activemq:1.0"> <server name="default"> ... <jms-queue name="ExpiryQueue" entries="java:/jms/queue/ExpiryQueue"/> <jms-queue name="DLQ" entries="java:/jms/queue/DLQ"/> <jms-queue name="helloWorldQueue" entries="queue/helloWorldQueue java:jboss/ jms/queue/helloWorldQueue"/> <jms-queue name="TodoListQueue" entries="queue/TodoListQueue java:jboss/jms/ queue/TodoListQueue"/> <connection-factory name="InVmConnectionFactory" entries="java:/ ConnectionFactory" connectors="in-vm"/> <connection-factory name="RemoteConnectionFactory" entries="java:jboss/ exported/jms/RemoteConnectionFactory" connectors="http-connector"/> <pooled-connection-factory name="activemq-ra" transaction="xa" entries="java:/JmsXA java:jboss/DefaultJMSConnectionFactory" connectors="invm"/> </server> </subsystem> Note que la helloWorldQueue tiene una entrada JNDI de java:jboss/jms/ queue/helloWorldQueue. 2.4. Cierre el editor de texto y no guarde los cambios al archivo standalone-full.xml. JB183-EAP7.0-es-2-20180124 351 Capítulo 8. Creación de aplicaciones de mensajería con JMS Advertencia Pueden producirse problemas en el trabajo de laboratorio si se introducen cambios inesperados en el archivo de configuración. 3. Revise la clase EJB PersonService. Abra la clase PersonService; para ello, expanda el ítem create-mdb en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en create-mdb > Java Resources > src/main/java > com.redhat.training.service y expándalo. Haga doble clic en el archivo PersonService.java. @Inject JMSClient jmsUtil; ... // Send a JMS message to the 'helloWorldQueue' jmsUtil.sendMessage("Said Hello to " + name.toUpperCase() + " at " + fdate); ... Observe que esta clase de servicio usa una instancia del bean JMSClient para enviar un mensaje a una cola, cada vez que una persona es saludada por la aplicación. 4. Revise la clase EJB JMSClient. Abra la clase JMSClient; para ello, expanda el ítem create-mdb en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en create-mdb > Java Resources > src/main/java > com.redhat.training.messaging y expándalo. Haga doble clic en el archivo JMSClient.java. @Resource(mappedName = "java:jboss/jms/queue/helloWorldQueue") private Queue helloWorldQueue; @Inject JMSContext context; ... JMSProducer producer = context.createProducer(); Observe que esta clase crea un JMSProducer y usa una anotación @Resource para acceder al objeto administrado para la helloWorldQueue, mediante su entrada JNDI. 5. Cree la nueva clase de beans controlados por mensajes. 5.1. Haga clic con el botón derecho en com.redhat.training.messaging y haga clic en New (Nueva) > Class (Clase). 5.2. En el campo Name (Nombre), ingrese QueueListener. Haga clic en Finish (Finalizar). 5.3. Haga que la nueva clase implemente la interfaz MessageListener: package com.redhat.training.messaging; import javax.jms.MessageListener; 352 JB183-EAP7.0-es-2-20180124 public class QueueListener implements MessageListener{ } 5.4. Resuelva el error de compilación agregando el método onMessage(Message message) requerido por la interfaz: package com.redhat.training.messaging; import javax.jms.MessageListener; import javax.jms.Message; public class QueueListener implements MessageListener{ @Override public void onMessage(Message rcvMessage) { } } 5.5. Implemente el método onMessage para realizar una verificación simple del tipo de mensaje y registre el resultado en el registro del servidor: package com.redhat.training.messaging; import import import import javax.jms.MessageListener; javax.jms.Message; javax.jms.JMSException; javax.jms.TextMessage; public class QueueListener implements MessageListener{ public void onMessage(Message rcvMessage) { TextMessage msg = null; try { if (rcvMessage instanceof TextMessage) { msg = (TextMessage) rcvMessage; System.out.println("Received Message from helloWorldQueue ===> " + msg.getText()); } else { System.out.println("Message of wrong type: " + rcvMessage.getClass().getName()); } } catch (JMSException e) { throw new RuntimeException(e); } } } nota Se requiere un bloque try/catch, ya que la llamada msg.getText() puede arrojar una JMSException si el mensaje está dañado. Verificar el tipo de mensaje ayuda a mitigar problemas con tipos de mensaje inesperados. JB183-EAP7.0-es-2-20180124 353 Capítulo 8. Creación de aplicaciones de mensajería con JMS 5.6. Configure el MDB utilizando la anotación @MessageDriven, configurando las instancias @ActivationConfigProperty necesarias para leer de la helloWorldQueue: package com.redhat.training.messaging; import import import import import import javax.ejb.ActivationConfigProperty; javax.ejb.MessageDriven; javax.jms.MessageListener; javax.jms.Message; javax.jms.JMSException; javax.jms.TextMessage; @MessageDriven(name = "QueueListener", activationConfig = { @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "queue/helloWorldQueue"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue") }) public class QueueListener implements MessageListener { ... Use la anotación @MessageDriven para registrar esta clase como un MDB. Establezca la propiedad destinationLookup con el nombre JNDI del objeto administrado para la cola. Establezca la propiedad destinationType con javax.jms.Queue, ya que el destino es una cola JMS, no un tema (topic). 5.7. Guarde los cambios usando Ctrl+S. 6. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y muestre el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 7. Implemente la aplicación en JBoss EAP con Maven; para ello, ejecute los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/create-mdb [student@workstation create-mdb]$ mvn wildfly:deploy Confirme la implementación correcta en el registro del servidor que aparece en la pestaña Console (Consola) en JBDS. Cuando se implementa la aplicación, aparece lo siguiente en el registro: INFO [org.jboss.as.server] (management-handler-thread - 9) WFLYSRV0010: Deployed "create-mdb.war" (runtime-name : "create-mdb.war") 354 JB183-EAP7.0-es-2-20180124 8. Pruebe la aplicación en un explorador. 8.1. Abra http://localhost:8080/create-mdb en un explorador en la máquina virtual workstation. 8.2. Ingrese Shadowman en el cuadro de texto denominado Enter your name: (Ingrese su nombre:) y haga clic en Submit (Enviar). La página se actualiza con el mensaje Hello SHADOWMAN!. Time on the server is: Nov 02 2017 03:55:03 PM 8.3. Verifique los registros de JBoss para ver si el MDB recibió el mensaje. En JBDS, abra la pestaña de la Console (Consola) para ver los registros del servidor. Si el MDB funciona correctamente, aparece el siguiente mensaje: ...Received Message from helloWorldQueue ===> Said Hello to SHADOWMAN at Nov 02 2017 03:55:03 PM 9. Anule la implementación de la aplicación y detenga JBoss EAP. 9.1. Ejecute el siguiente comando para anular la implementación de la aplicación: [student@workstation create-mdb]$ mvn wildfly:undeploy 9.2. Para cerrar el proyecto, haga clic con el botón derecho en el proyecto create-mdb en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 9.3. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el ejercicio guiado. JB183-EAP7.0-es-2-20180124 355 Capítulo 8. Creación de aplicaciones de mensajería con JMS Trabajo de laboratorio: Creación de aplicaciones de mensajería con JMS En este trabajo de laboratorio, usará un productor de mensajes para enviar mensajes a una cola cada vez que un ítem se actualiza en la aplicación To Do List. Resultado Deberá ser capaz de compilar una aplicación JMS que use un productor JMS para colocar mensajes en una cola y un bean controlado por mensajes para escuchar en la misma cola y registrar los mensajes en un archivo especial. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab messaging-lab setup 1. Abra JBDS e importe el proyecto messaging-lab ubicado en el directorio /home/ student/JB183/labs/messaging-lab. 2. Revise el nombre JNDI asignado al objeto administrado, definido para la TodoListQueue en el archivo de configuración JBoss. 3. Cree una nueva clase EJB sin estado, nombrada JMSClient, que proporcione un método público denominado sendMessage(String msg), para enviar un mensaje a la TodoListQueue mediante un productor de mensajes JMS. 4. Actualice la EJB JMSClient para inyectar el objeto JMSContext predeterminado, inyecte también el objeto administrado para la TodoListQueue y, luego, use ese contexto para crear un JMSProducer para enviar un mensaje a la cola. 4.1. Inyecte el objeto JMSContext predeterminado para acceder a la fábrica de conexiones predeterminadas. 4.2. Inyecte el objeto administrado TodoListQueue con una anotación @Resource. 4.3. Implemente el método sendMessage(String msg) para colocar un nuevo mensaje en la cola usando la interfaz JMSProducer, manejando excepciones mediante la impresión de su seguimiento de pila (stack) en la consola. 4.4. Guarde los cambios usando Ctrl+S. 5. Actualice el ItemService para inyectar el EJB JMSClient. Agregue una invocación del método update() en la clase ItemService para usar la instancia JMSClient para enviar un mensaje JMS cada vez que se actualiza un ítem. 5.1. Abra la clase ItemService; para ello, expanda el ítem messaging-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en messaging-lab > Java Resources > src/main/java > com.redhat.training.todo.service para expandirlo. Haga doble clic en el archivo ItemService.java. 356 JB183-EAP7.0-es-2-20180124 5.2. Importe el JMSClient e inyecte la instancia predeterminada. 5.3. En el método update (actualizar), agregue una invocación del método sendMessage utilizando el siguiente mensaje: Item with ID:" + item.getId() + " was updated with status Done="+ item.isDone(): 5.4. Guarde los cambios usando Ctrl+S. 6. Actualice la clase QueueListener para que sea un bean controlado por mensaje que escucha la TodoListQueue y envía mensajes a un archivo de registro especial mediante el método writeMessageToFile. 6.1. Abra la clase QueueListener; para ello, expanda el ítem messaging-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en messaging-lab > Java Resources > src/main/java > com.redhat.training.todo.messaging para expandirlo. Haga doble clic en el archivo QueueListener.java. 6.2. Haga que la clase QueueListener implemente la interfaz MessageListener e implemente el método onMessage() para corregir errores de recopilación: 6.3. Implemente una simple verificación del tipo de mensaje para asegurarse de que sea una instancia de TextMessage y registre el resultado en el archivo de registro personalizado, mediante el método writeMessageToFile(String message) provisto. Asegúrese de usar un bloque try-catch para manejar las JMSExceptions: 6.4. Configure el MDB mediante la anotación @MessageDriven, estableciendo las instancias @ActivationConfigProperty necesarias para leer de la TodoListQueue: 6.5. Guarde los cambios usando Ctrl+S. 7. Inicie JBoss EAP desde dentro de JBDS. 8. Compile e implemente la aplicación en JBoss EAP con Maven. 9. Pruebe la aplicación en un explorador. 9.1. Abra Firefox en la máquina virtual workstation y diríjase a http:// localhost:8080/messaging-lab para acceder a la aplicación. 9.2. Agregue al menos dos nuevos ítems pendientes mediante la interfaz de la aplicación To Do List. 9.3. Después de haber agregado correctamente los nuevos ítems, actualice su estado a Listos. 9.4. Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para navegar al directorio del proyecto y ver el archivo de registro ItemStatusLog.txt: [student@workstation ~]$ cd /home/student/JB183/labs/messaging-lab [student@workstation messaging-lab]$ cat ItemStatusLog.txt JB183-EAP7.0-es-2-20180124 357 Capítulo 8. Creación de aplicaciones de mensajería con JMS Un MDB que funciona adecuadamente imprime un texto similar al siguiente en el registro: Item with ID:11 was updated with status Done=true Item with ID:12 was updated with status Done=true 10. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab messaging-lab grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 11. Realice la limpieza. 11.1.Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/messaging-lab [student@workstation messaging-lab]$ mvn wildfly:undeploy 11.2.Haga clic con el botón derecho en el proyecto messaging-lab en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrarlo. 11.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el trabajo de laboratorio. 358 JB183-EAP7.0-es-2-20180124 Solución Solución En este trabajo de laboratorio, usará un productor de mensajes para enviar mensajes a una cola cada vez que un ítem se actualiza en la aplicación To Do List. Resultado Deberá ser capaz de compilar una aplicación JMS que use un productor JMS para colocar mensajes en una cola y un bean controlado por mensajes para escuchar en la misma cola y registrar los mensajes en un archivo especial. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab messaging-lab setup 1. Abra JBDS e importe el proyecto messaging-lab ubicado en el directorio /home/ student/JB183/labs/messaging-lab. 1.1. Para abrir JBDS, haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo. Deje el espacio de trabajo predeterminado (/home/ student/JB183/workspace) y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta messaging-lab y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Revise el nombre JNDI asignado al objeto administrado, definido para la TodoListQueue en el archivo de configuración JBoss. 2.1. Usando su editor de texto preferido, abra el archivo de configuración EAP en /opt/ eap/standalone/configuration/standalone-full.xml: [student@workstation ~]$ less /opt/eap/standalone/configuration/\ standalone-full.xml 2.2. Navegue al subsistema urn:jboss:domain:messaging-activemq:1.0: <subsystem xmlns="urn:jboss:domain:messaging-activemq:1.0"> <server name="default"> ... JB183-EAP7.0-es-2-20180124 359 Capítulo 8. Creación de aplicaciones de mensajería con JMS <jms-queue name="ExpiryQueue" entries="java:/jms/queue/ExpiryQueue"/> <jms-queue name="DLQ" entries="java:/jms/queue/DLQ"/> <jms-queue name="helloWorldQueue" entries="queue/helloWorldQueue java:jboss/ jms/queue/helloWorldQueue"/> <jms-queue name="TodoListQueue" entries="queue/TodoListQueue java:jboss/jms/ queue/TodoListQueue"/> <connection-factory name="InVmConnectionFactory" entries="java:/ ConnectionFactory" connectors="in-vm"/> <connection-factory name="RemoteConnectionFactory" entries="java:jboss/ exported/jms/RemoteConnectionFactory" connectors="http-connector"/> <pooled-connection-factory name="activemq-ra" transaction="xa" entries="java:/ JmsXA java:jboss/DefaultJMSConnectionFactory" connectors="in-vm"/> </server> </subsystem> Note que la TodoListQueue tiene la entrada JNDI dejava:jboss/jms/queue/ TodoListQueue. 2.3. Cierre el editor de texto y no guarde los cambios al archivo standalone-full.xml. Advertencia Pueden producirse problemas en el trabajo de laboratorio si se introducen cambios inesperados en el archivo de configuración. 3. Cree una nueva clase EJB sin estado, nombrada JMSClient, que proporcione un método público denominado sendMessage(String msg), para enviar un mensaje a la TodoListQueue mediante un productor de mensajes JMS. 3.1. A continuación, en la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS, haga clic en messaging-lab > Java Resources (Recursos de Java) > src/main/java > com.redhat.training.todo.messaging, haga clic con el botón derecho en com.redhat.training.messaging y seleccione New (Nueva) > Class (Clase). 3.2. En el campo Name (Nombre) ingrese JMSClient. Haga clic en Finish (Finalizar). 3.3. Edite la clase JMSClient recientemente creada y agregue la anotación @Stateless para marcarla como una EJB disponible para inyección. import javax.ejb.Stateless; @Stateless public class JMSClient { 3.4. Guarde los cambios usando Ctrl+S. 4. Actualice la EJB JMSClient para inyectar el objeto JMSContext predeterminado, inyecte también el objeto administrado para la TodoListQueue y, luego, use ese contexto para crear un JMSProducer para enviar un mensaje a la cola. 4.1. Inyecte el objeto JMSContext predeterminado para acceder a la fábrica de conexiones predeterminadas. 360 JB183-EAP7.0-es-2-20180124 Solución import javax.ejb.Stateless; import javax.inject.Inject; import javax.jms.JMSContext; @Stateless public class JMSClient { @Inject JMSContext context; 4.2. Inyecte el objeto administrado TodoListQueue con una anotación @Resource. import import import import import javax.ejb.Stateless; javax.inject.Inject; javax.jms.JMSContext; javax.jms.Queue; javax.annotation.Resource; @Stateless public class JMSClient { @Inject JMSContext context; @Resource(mappedName = "java:jboss/jms/queue/TodoListQueue") private Queue todoListQueue; 4.3. Implemente el método sendMessage(String msg) para colocar un nuevo mensaje en la cola usando la interfaz JMSProducer, manejando excepciones mediante la impresión de su seguimiento de pila (stack) en la consola. import import import import import import import javax.ejb.Stateless; javax.inject.Inject; javax.jms.JMSContext; javax.jms.Queue; javax.annotation.Resource; javax.jms.JMSProducer; javax.jms.TextMessage; @Stateless public class JMSClient { @Inject JMSContext context; @Resource(mappedName = "java:jboss/jms/queue/TodoListQueue") private Queue todoListQueue; public void sendMessage(String msg) { try { JMSProducer producer = context.createProducer(); TextMessage message = context.createTextMessage(msg); producer.send(todoListQueue, message); System.out.println("Sent Message: "+ msg); } catch (Exception e) { e.printStackTrace(); } } JB183-EAP7.0-es-2-20180124 361 Capítulo 8. Creación de aplicaciones de mensajería con JMS ... 4.4. Guarde los cambios usando Ctrl+S. 5. Actualice el ItemService para inyectar el EJB JMSClient. Agregue una invocación del método update() en la clase ItemService para usar la instancia JMSClient para enviar un mensaje JMS cada vez que se actualiza un ítem. 5.1. Abra la clase ItemService; para ello, expanda el ítem messaging-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en messaging-lab > Java Resources > src/main/java > com.redhat.training.todo.service para expandirlo. Haga doble clic en el archivo ItemService.java. 5.2. Importe el JMSClient e inyecte la instancia predeterminada. ... import com.redhat.training.todo.messaging.JMSClient; @Stateless public class ItemService { @Inject private JMSClient jmsClient; 5.3. En el método update (actualizar), agregue una invocación del método sendMessage utilizando el siguiente mensaje: Item with ID:" + item.getId() + " was updated with status Done="+ item.isDone(): public void update(Item item) { jmsClient.sendMessage("Item with ID:" + item.getId() + " was updated with status Done="+ item.isDone()); em.merge(item); } 5.4. Guarde los cambios usando Ctrl+S. 6. Actualice la clase QueueListener para que sea un bean controlado por mensaje que escucha la TodoListQueue y envía mensajes a un archivo de registro especial mediante el método writeMessageToFile. 6.1. Abra la clase QueueListener; para ello, expanda el ítem messaging-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en messaging-lab > Java Resources > src/main/java > com.redhat.training.todo.messaging para expandirlo. Haga doble clic en el archivo QueueListener.java. 6.2. Haga que la clase QueueListener implemente la interfaz MessageListener e implemente el método onMessage() para corregir errores de recopilación: package com.redhat.training.messaging; import javax.jms.MessageListener; 362 JB183-EAP7.0-es-2-20180124 Solución public class QueueListener implements MessageListener{ @Override public void onMessage(Message rcvMessage) { } } 6.3. Implemente una simple verificación del tipo de mensaje para asegurarse de que sea una instancia de TextMessage y registre el resultado en el archivo de registro personalizado, mediante el método writeMessageToFile(String message) provisto. Asegúrese de usar un bloque try-catch para manejar las JMSExceptions: import import import import javax.jms.JMSException; javax.jms.Message; javax.jms.MessageListener; javax.jms.TextMessage; public class QueueListener implements MessageListener{ public void onMessage(Message rcvMessage) { TextMessage msg = null; try { if (rcvMessage instanceof TextMessage) { msg = (TextMessage) rcvMessage; System.out.println("Received Message from TodoListQueue ===> " + msg.getText()); writeMessageToFile(msg.getText()); } else { System.out.println("Message of wrong type: " + rcvMessage.getClass().getName()); } } catch (JMSException e) { e.printStackTrace(); } } } 6.4. Configure el MDB mediante la anotación @MessageDriven, estableciendo las instancias @ActivationConfigProperty necesarias para leer de la TodoListQueue: import javax.ejb.ActivationConfigProperty; import javax.ejb.MessageDriven; @MessageDriven(name = "QueueListener", activationConfig = { @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "queue/TodoListQueue"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue") }) public class QueueListener implements MessageListener { ... 6.5. Guarde los cambios usando Ctrl+S. 7. Inicie JBoss EAP desde dentro de JBDS. JB183-EAP7.0-es-2-20180124 363 Capítulo 8. Creación de aplicaciones de mensajería con JMS Seleccione la pestaña Servers (Servidores) en JBDS. Haga clic con el botón derecho en la entrada del servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en la opción verde Start (Iniciar) para iniciar el servidor. Observe la pestaña Console (Consola) de JBDS hasta que el servidor se inicie y vea el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.2.GA (WildFly Core 2.1.8.Final-redhat-1) started 8. Compile e implemente la aplicación en JBoss EAP con Maven. Abra un nuevo terminal y cambie el directorio por la carpeta /home/student/JB183/ labs/messaging-lab. [student@workstation ~]$ cd /home/student/JB183/labs/messaging-lab Compile e implemente el EJB en JBoss EAP al ejecutar el siguiente comando: [student@workstation messaging-lab]$ mvn clean wildfly:deploy Si la compilación es correcta, se mostrará la siguiente salida: [student@workstation messaging-lab]$ mvn clean package wildfly:deploy ... [INFO] [INFO] <<< wildfly-maven-plugin:1.0.2.Final:deploy (default-cli) < package @ messaging-lab <<< ... [INFO] --- maven-war-plugin:2.1.1:war (default-war) @ messaging-lab --... [INFO] --- wildfly-maven-plugin:1.0.2.Final:deploy (default-cli) @ messaging-lab [INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ 9. Pruebe la aplicación en un explorador. 9.1. Abra Firefox en la máquina virtual workstation y diríjase a http:// localhost:8080/messaging-lab para acceder a la aplicación. 9.2. Agregue al menos dos nuevos ítems pendientes mediante la interfaz de la aplicación To Do List. 9.3. Después de haber agregado correctamente los nuevos ítems, actualice su estado a Listos. 9.4. Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para navegar al directorio del proyecto y ver el archivo de registro ItemStatusLog.txt: [student@workstation ~]$ cd /home/student/JB183/labs/messaging-lab [student@workstation messaging-lab]$ cat ItemStatusLog.txt 364 JB183-EAP7.0-es-2-20180124 Solución Un MDB que funciona adecuadamente imprime un texto similar al siguiente en el registro: Item with ID:11 was updated with status Done=true Item with ID:12 was updated with status Done=true 10. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab messaging-lab grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 11. Realice la limpieza. 11.1.Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/messaging-lab [student@workstation messaging-lab]$ mvn wildfly:undeploy 11.2.Haga clic con el botón derecho en el proyecto messaging-lab en Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto) para cerrarlo. 11.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 365 Capítulo 8. Creación de aplicaciones de mensajería con JMS Resumen En este capítulo, aprendió lo siguiente: • Al activar el procesamiento asíncrono entre sistemas acoplados de forma flexible, la mensajería permite que los desarrolladores compilen aplicaciones que sean más eficientes, más fácilmente escaladas y más confiables. • Point-to-point messaging (mensajería punto a punto) se produce cuando dos aplicaciones están conectadas mediante una cola, y cada mensaje que envía un productor es recibido por un solo consumidor. • La mensajería de Publicación/Suscripción se produce cuando varias aplicaciones se suscriben a un tema y todo mensaje enviado a ese tema se duplica y se copia a cada uno de los suscriptores. • Una práctica común al trabajar con mensajería en un servidor de aplicaciones como JBoss EAP 7 es mantener los destinos JMS y las fábricas de conexión de manera administrativa en la configuración del servidor, en lugar de en el propio código de aplicaciones. • Existen tres partes para una estructura básica de un mensaje JMS: encabezados, propiedades y un cuerpo. • La anotación @Resource inyecta directamente un objeto de destino, como una Queue (Cola) o Topic (Tema), así como un objeto ConnectionFactory que usa solo el nombre JNDI definido en la configuración del servidor. • Un productor de mensajes es un tipo de objeto JMS creado por el objeto JMSContext que envía mensajes JMS de un destino. • Un consumidor de mensajes es un tipo de objeto JMS creado por el objeto JMSContext que recibe mensajes JMS de un destino de manera asíncrona, a través de una invocación de método. • Los beans controlados por mensajes (MDB) permiten un consumo asíncrono de mensajes de un destino JMS. 366 JB183-EAP7.0-es-2-20180124 TRAINING CAPÍTULO 9 PROTECCIÓN DE APLICACIONES JAVA EE Descripción general Meta Proteger una aplicación de Java EE con JAAS. Objetivos • Describir la especificación de JAAS. • Configurar un dominio de seguridad en el servidor de aplicaciones de JBoss EAP. • Proteger una API REST con autenticación y autorización basada en roles. Secciones • Descripción de la especificación de JAAS (y cuestionario) • Configuración de un dominio de seguridad en JBoss EAP (y ejercicio guiado) • Protección de una API REST (y ejercicio guiado) Trabajo de laboratorio JB183-EAP7.0-es-2-20180124 Protección de aplicaciones Java EE 367 Capítulo 9. Protección de aplicaciones Java EE Descripción de la especificación de JAAS Objetivo Tras finalizar esta sección, los estudiantes deberán ser capaces de describir la especificación JAAS. Seguridad Java EE Al compilar aplicaciones empresariales que acceden a información confidencial, la seguridad debe ser una prioridad para los desarrolladores. Los desarrolladores deben tener una comprensión clara de quién puede acceder a la aplicación y qué partes de esta aplicación están autorizados esos usuarios a utilizar. La definición de qué usuarios tienen acceso a una aplicación se conoce como authentication (autenticación), mientras que los permisos dentro de la aplicación para esos usuarios se conoce como authorization (autorización). Al definir las restricciones de acceso a diferentes componentes de aplicaciones, los usuarios están limitados únicamente a la cantidad mínima de acceso que requiere cada usuario. Para personalizar la autorización en una aplicación, se aplican restricciones a un user (usuario), que representa un individuo, o a un role (rol) que se refiere a un grupo definido de usuarios. Por ejemplo, considere una aplicación web de tienda de libros en la que los clientes compran libros en línea y el propietario gestiona el inventario. El usuario shadowman es un cliente que accede al sitio y tiene el rol customer (cliente). El administrador del sitio con el nombre de usuario redhat tiene el rol admin. El servidor autentica a los usuarios shadowman y redhat para garantizar que cada usuario coincida con su contraseña. Una vez autenticados, los métodos EJB se anotan para restringir el acceso a roles de usuario individual. Debido a que los clientes no tienen permitido gestionar el inventario de la tienda, los usuarios con el rol customer no pueden invocar métodos que gestionen el inventario, mientras que los usuarios con el rol admin pueden realizar cambios de inventario. Figura 9.1: Proteja la autorización EJB El Servicio de Autenticación y Autorización de Java (JAAS) es una API de seguridad que se usa para implementar aplicaciones de autenticación y autorización en Java (JSR-196). La API amplía la arquitectura de control de acceso de Java Enterprise Edition para admitir la autorización basada en usuarios. Java EE aplica la API como mecanismo para permitir y garantizar el acceso. JAAS proporciona seguridad declarativa basada en roles en la JBoss Enterprise Application Platform. La Declarative security (Seguridad declarativa) separa las preocupaciones en materia de seguridad del código de la aplicación utilizando el contenedor para gestionar la seguridad. El contenedor proporciona un sistema de autorización basado en 368 JB183-EAP7.0-es-2-20180124 Seguridad declarativa anotaciones y descriptores XML dentro del código de aplicaciones que protege los recursos. Este enfoque se encuentra en contraste con la seguridad programática, que requiere que cada aplicación contenga código que gestione la seguridad. Seguridad declarativa El uso de la seguridad declarativa requiere de los desarrolladores y administradores que aprovechen las anotaciones y los descriptores de implementación para definir el comportamiento de seguridad de una aplicación. Por ejemplo, un EJB puede restringir los aspectos de la aplicación que se basan en el rol de un usuario que solo utiliza anotaciones. No requiere que una aplicación gestione el contexto de seguridad. Para gestionar aspectos de seguridad, como la administración de la autenticación y la autorización, necesita descriptores de implementación, responsables de dictar la forma en que un servidor de aplicaciones implementa la aplicación y cómo el servidor protege la aplicación. Esto se establece en el web.xml de la aplicación o al desarrollar con Red Hat JBoss EAP en jboss-web.xml. Los desarrolladores usan el archivo web.xml para definir qué recursos se deben proteger en una aplicación, cómo se los debe proteger y qué datos se usan para validar las credenciales. El siguiente es un fragmento del web.xml que define la autenticación BASIC: <security-constraint> <web-resource-collection> <web-resource-name>All resources</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>*</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>basicRealm</realm-name> </login-config> Los recursos a los que se aplica la restricción de seguridad. La "/*" indica que todos los recursos están protegidos. Los roles autorizados a acceder a los recursos. En esta instancia, todos los roles pueden acceder a la aplicación. El método de aplicación utilizado para acceder a las credenciales de un usuario. En una ventana emergente, BASIC avisa al usuario ni bien se accede a la aplicación. El nombre del dominio que almacena la información de credencial del usuario. Este tema se analizará en más detalle en la siguiente sección. El archivo jboss-web.xml agrega descripciones específicas de JBoss adicionales, como la forma en que el servidor lleva a cabo la autenticación y la autorización para la aplicación. En muchos casos, este archivo se usa para definir un dominio de seguridad, que es un conjunto de configuraciones de seguridad declarativa de JAAS. Este archivo y otras configuraciones de seguridad específicas de JBoss se analizarán en la siguiente sección. El uso de los descriptores de implementación para definir aspectos de seguridad puede ser útil, aunque también son muy restrictivos, en especial en cualquier aplicación que cuente con más que los requisitos de seguridad más básicos. Las anotaciones que se colocan directamente en el código de aplicaciones EJB proporcionan un enfoque más flexible y personalizable a la seguridad. Este enfoque es útil para los métodos de protección de API JB183-EAP7.0-es-2-20180124 369 Capítulo 9. Protección de aplicaciones Java EE REST o para limitar ciertos roles al uso exclusivo de ciertas invocaciones de métodos dentro de una aplicación. Anotaciones que se pueden usar para proteger un EJB: • @SecurityDomain: ubicada al comienzo de la clase, esta anotación define el dominio de seguridad por nombre para usar para el EJB. • @DeclareRoles: ubicada al comienzo de la clase, esta anotación define los roles probados para permisos en la clase. Si no se utiliza esta anotación, los roles se verifican en función de la presencia de las anotaciones @RolesAllowed. • @RolesAllowed: ubicada al comienzo de la clase o antes del encabezado del método, esta anotación define una lista de uno o más roles que tienen permiso de acceso a un método. Si se los coloca antes del encabezado de la clase, los métodos en la clase sin una anotación adoptan esta anotación de manera predeterminada. • @PermitAll: ubicada al comienzo de la clase o antes del encabezado del método, esta anotación especifica que todos los roles tienen permiso de acceso a un método. • @DenyAll: ubicada al comienzo de la clase o antes del encabezado del método, esta anotación especifica que ningún rol tiene permiso de acceso a un método. • @RunAs: ubicada al comienzo de la clase o antes del encabezado del método, esta anotación especifica el rol usado al ejecutar un método. Esta anotación es útil cuando un EJB invoca otro EJB y debe adoptar un nuevo rol por restricciones de seguridad en otro EJB. El siguiente es un ejemplo de una clase EJB que usa el dominio de seguridad other para restringir la autorización de acceso a sus métodos: @Stateless @RolesAllowed({"admin, qa"}) @SecurityDomain("other") public class HelloWorldEJB implements HW { @PermitAll public String HelloWorld(String msg) { return "Hello " + msg; } @RolesAllowed("admin") public String GoodbyeAdmin(String msg) { return "See you later, " + msg; } public String GoodbyeSecure(String msg) { return "Adios, " + msg; } } De manera predeterminada, la clase HelloWorldEJB pasa a restringir todos sus métodos a solo estar disponibles a los usuarios admin y qa. La clase está aprovechando el other dominio de seguridad. En este caso, este dominio de seguridad utiliza los archivos de propiedad que almacenan la información de rol. El método HelloWorld está disponible para todos los roles, no solo admin y qa. El método GoodbyeAdmin está disponible solo a los usuarios autenticados, como el admin. 370 JB183-EAP7.0-es-2-20180124 Seguridad Programática El método GoodbyeSecure está disponible solo para admin y qa, ya que el método se establece de manera predeterminada en RolesAllowed, definido a nivel de clase. Seguridad Programática En algunos casos, la seguridad declarativa no es suficiente para cumplir con todos los requisitos de seguridad para una aplicación. En estas instancias, los desarrolladores pueden preferir usar seguridad programática para tener más control sobre las decisiones de autenticación y autorización dentro de la aplicación. El objeto EJBContext cuenta con información acerca del contexto de seguridad actual y proporciona los siguientes dos métodos útiles para acceder a la información acerca del usuario autenticado que se puede usar para realizar decisiones de autorización: • isCallerInRole(String role): devuelve un boolean que indica si un usuario pertenece a un rol específico. • getCallerPrincipal(): devuelve el usuario actualmente autenticado. En el siguiente ejemplo se demuestra el uso de EJBContext para identificar un usuario y el rol del usuario: @Stateless public class HelloWorldEJB implements HW { @Resource EJBContext context; public String HelloWorld() { if (context.isCallerInRole("admin")) { return "Hello " + context.getCallerPrincipal().getName(); } else { return "Unauthorized user."; } } } En este ejemplo, el método HelloWorld() usa el EJBContext para ver si el usuario que invoca el método pertenece al rol admin. Si el usuario pertenece a este rol, se devuelve una respuesta con el nombre de usuario autenticado. Además de utilizar el EJBContext, la interfaz HttpServletRequest proporciona métodos para gestionar en forma programática la autenticación de usuarios. Los siguientes métodos están disponibles para autenticar usuarios con la interfaz HttpServletRequest: • authenticate(HttpServletResponse): solicita al usuario que presente credenciales para la autenticación. • login(String username, String password): inicie sesión con el usuario ingresando el username (nombre de usuario) y la password (contraseña). • logout(): cierre la sesión del usuario autenticado actual. JB183-EAP7.0-es-2-20180124 371 Capítulo 9. Protección de aplicaciones Java EE Referencias Interfaz de proveedor de servicio de autenticación de Java JSR-196 para contenedores https://www.jcp.org/en/jsr/detail?id=196 Referencias Para obtener más información, consulte el capítulo Seguridad de la Guía de desarrollo para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ 372 JB183-EAP7.0-es-2-20180124 Cuestionario: Descripción de la especificación de JAAS Cuestionario: Descripción de la especificación de JAAS Una los siguientes ítems con sus equivalentes en la tabla. Define el tipo de <login-config>, como BASIC. Especifica la configuración de seguridad que se debe usar en la aplicación. La anotación que define el rol que ejecuta un método. La anotación que define los roles que pueden acceder a un método. La anotación que define para qué roles verificar permisos. Un conjunto de configuraciones de seguridad declarativas de JAAS en EAP. Verificación de que un usuario es quien dice ser. Verificación de que un usuario tiene el privilegio de realizar una acción específica. Término Definición Dominio de seguridad Autenticación Autorización web.xml JB183-EAP7.0-es-2-20180124 373 Capítulo 9. Protección de aplicaciones Java EE Término Definición jboss-web.xml @DeclareRoles @RolesAllowed @RunAs 374 JB183-EAP7.0-es-2-20180124 Solución Solución Una los siguientes ítems con sus equivalentes en la tabla. Término Definición Dominio de seguridad Un conjunto de configuraciones de seguridad declarativas de JAAS en EAP. Autenticación Verificación de que un usuario es quien dice ser. Autorización Verificación de que un usuario tiene el privilegio de realizar una acción específica. web.xml Define el tipo de <login-config>, como BASIC. jboss-web.xml Especifica la configuración de seguridad que se debe usar en la aplicación. @DeclareRoles La anotación que define para qué roles verificar permisos. @RolesAllowed La anotación que define los roles que pueden acceder a un método. @RunAs La anotación que define el rol que ejecuta un método. JB183-EAP7.0-es-2-20180124 375 Capítulo 9. Protección de aplicaciones Java EE Configuración de un dominio de seguridad en JBoss EAP Objetivo Después de completar esta sección, los estudiantes deberán ser capaces de configurar un dominio de seguridad en EAP. Dominios de seguridad en EAP El uso de un servidor de aplicaciones como Red Hat JBoss EAP simplifica la configuración e implementación de la seguridad para desarrolladores y administradores de aplicaciones. EAP y otros servidores de aplicaciones proporcionan utilidades y configuraciones predefinidas que ayudan a gestionar la autenticación y autorización. EAP gestiona la información de seguridad del usuario en security realms (dominios de seguridad). De manera predeterminada, EAP define ApplicationRealm, que usa los siguientes archivos para almacenar los usuarios y sus roles, respectivamente: • application-users.properties • application-roles.properties Aunque se pueden agregar dominios de seguridad a EAP, este curso usa principalmente el ApplicationRealm para autenticación y autorización. La siguiente es la configuración predeterminada para el ApplicationRealm: <security-realm name="ApplicationRealm"> <authentication> <local default-user="$local" allowed-users="*" skip-grouploading="true"/> <properties path="application-users.properties" relativeto="jboss.server.config.dir"/> </authentication> <authorization> <properties path="application-roles.properties" relativeto="jboss.server.config.dir"/> </authorization> </security-realm> Observe que los dominios tienen una etiqueta cada uno para <authentication> y <authorization>. La etiqueta <authentication> define la ruta al archivo de propiedades del usuario. En este caso, ese archivo es application-users.properties en el directorio de configuración del servidor EAP. Este archivo almacena nombres de usuario y contraseñas como par de claves/valores, por ejemplo: <username>=<password> nota El script EAP_HOME/bin/add-user.sh es un script de utilidad que actualiza el archivo application-users.properties predeterminado con nuevos usuarios. 376 JB183-EAP7.0-es-2-20180124 Módulos de inicio de sesión La etiqueta <authorization> define la ruta al archivo de propiedades del usuario. En este caso, ese archivo es application-roles.properties, que se ubica en el directorio de configuración del servidor EAP. Este archivo almacena usuarios y roles como pares de claves/ valores mediante la siguiente sintaxis: <role>=<user1>,<user2>... Módulos de inicio de sesión Si bien los dominios de seguridad en EAP se usan para configurar las credenciales del usuario, un security realm (dominio de seguridad) define un conjunto de configuraciones de seguridad declarativas JAAS. La mayor ventaja de usar un enfoque declarativo para la seguridad es que separa muchos de los detalles de seguridad de la propia aplicación. Esto proporciona flexibilidad adicional y mantiene el código de lógica de negocio de la aplicación con acceso de lectura y más sencillo de mantener al eliminar los detalles de implementación para la tecnología de seguridad que utiliza el servidor de aplicaciones. EAP incluye varios módulos de inicio de sesión incorporados, que los desarrolladores pueden utilizar para la autenticación dentro de un dominio de seguridad. Estos módulos de inicio de sesión incluyen la capacidad de leer información del usuario de una base de datos relacional, un servidor LDAP o archivos planos. También es posible compilar un módulo personalizado, dependiendo de los requisitos de seguridad de la aplicación. El método para la autenticación del usuario se define en el dominio de seguridad. De manera predeterminada, EAP define el other (otro) dominio de seguridad con la siguiente configuración: <security-domain name="other" cache-type="default"> <authentication> ... <login-module code="RealmDirect" flag="required"> <module-option name="password-stacking" value="useFirstPass"/> </login-module> </authentication> </security-domain> El módulo de inicio de sesión RealmDirect usa un dominio de aplicaciones al realizar las decisiones de autenticación y autorización. Si no se especificó un dominio, el módulo usa el ApplicationRealm, y, por lo tanto, los archivos de propiedad de usuarios y roles para la autenticación y autorización. Para activar el other dominio de seguridad en la aplicación, agregue la siguiente etiqueta al src/main/webapp/WEB-INF/jboss-web.xml de la aplicación: <security-domain>other</security-domain> Por último, para completar la autenticación, configure el archivo WEB-INF/web.xml de la aplicación para definir el <login-config>. En el siguiente ejemplo se define el <login-config> para usar la autenticación BASIC con el ApplicationRealm. Con la implementación de esta configuración, a los usuarios se les solicita credenciales al acceder a un recurso de servidor y el servidor verifica las credenciales en relación al ApplicationRealm. <login-config> JB183-EAP7.0-es-2-20180124 377 Capítulo 9. Protección de aplicaciones Java EE <auth-method>BASIC</auth-method> <realm-name>ApplicationRealm</realm-name> </login-config> </web-app> Módulo de inicio de sesión UsersRoles El módulo de inicio de sesión UsersRoles es un módulo simple, que sirve para probar algunas de las funcionalidades de seguridad básicas de una aplicación. El módulo proporciona una forma de que los desarrolladores autentiquen a usuarios rápidamente y verifiquen que las restricciones de autorización estén correctamente configuradas. De manera similar a depender del ApplicationRealm como lo hace el other dominio de seguridad, el módulo UsersRoles usa archivos de propiedades para almacenar las credenciales de usuario y los datos de roles. El siguiente es un ejemplo del módulo de inicio de sesión de UsersRoles: <security-domain name="loginTest" <authentication> ... cache-type="default"> <login-module code="UsersRoles" flag="required" > <module-option name="usersProperties" value="users.properties" <module-option name="rolesProperties" value="roles.properties" </login-module> </authentication> </security-domain> /> /> El nombre del dominio de seguridad. Se hace referencia a este nombre en el archivo jboss-web.xml. El código para definir qué módulo de inicio de sesión se está usando. En este caso, se está configurando el módulo de inicio de sesión UsersRoles. El indicador para definir el comportamiento del módulo de inicio de sesión. required (requerido) indica que el módulo es una autenticación requerida para iniciar sesión correctamente. La propiedad para definir el nombre del archivo que almacena todos los usuarios y contraseñas como un par de claves/valores. La propiedad para definir el nombre del archivo que almacena todos los roles de usuarios como un par de claves/valores. Módulo de inicio de sesión de la base de datos En un entorno de producción, es muy poco común ver credenciales de usuario e información de roles almacenada en archivos de propiedades almacenados a nivel local. Estos módulos y técnicas se utilizan principalmente con motivos de prueba. Una de las soluciones que es más práctica que los archivos de propiedad locales para gestionar las credenciales de usuarios es almacenar la información en una base de datos. Existen muchos beneficios de utilizar una base de datos en lugar de un archivo para almacenar la información del usuario. Las bases de datos se pueden compartir fácilmente en varios servidores de aplicaciones; incluyen una seguridad de datos sólida y soluciones de copia de seguridad, y son eficientes con un gran conjunto de datos. Si una aplicación utiliza el módulo de inicio de sesión Database (Base de datos), los usuarios de la aplicación se almacenan en una base de datos junto con los roles con los que están relacionados los usuarios. 378 JB183-EAP7.0-es-2-20180124 Módulo de inicio de sesión de la base de datos El siguiente es un ejemplo del módulo de inicio de sesión de Database: <security-domain name="db" cache-type="default"> <authentication> ... <login-module code="Database" flag="required"> <module-option name="dsJndiName" value="java:/DataSource" /> <module-option name="principalsQuery" value="select password from Users where username=?" /> <module-option name="rolesQuery" value="select role, 'Roles' from UserRoles where username=?" /> </login-module> </authentication> </security-domain> El código para definir qué módulo de inicio de sesión se usa. En este caso, se está configurando el módulo de inicio de sesión Database. La propiedad para definir el nombre JNDI utilizado para acceder a la fuente de datos. Observe que la fuente de datos ya está configurada. La propiedad para definir la consulta usada para obtener la contraseña para un usuario dado. Esta consulta depende de cómo está configurada la base de datos. La propiedad para definir la consulta usada para obtener el rol para un usuario dado. Esta consulta depende de cómo está configurada la base de datos. Referencias Para obtener más información, consulte la Guía de referencia del módulo de inicio de sesión para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ Referencias Para obtener más información, consulte el capítulo Seguridad de la Guía de desarrollo para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ JB183-EAP7.0-es-2-20180124 379 Capítulo 9. Protección de aplicaciones Java EE Ejercicio guiado: Configuración de un dominio de seguridad en JBoss EAP En este ejercicio, protegerá una aplicación JEE creando un dominio de seguridad. Resultado Deberá ser capaz de usar un dominio de seguridad en EAP para garantizar la autenticación y la autorización en una aplicación JEE. Antes de comenzar Abra una ventana de terminal en workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab security-domain setup Pasos 1. Abra JBDS e importe el proyecto de Maven. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta security-domain y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y muestre el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 3. 380 Cree el dominio de seguridad UsersRoles en JBoss EAP y verifique que use los archivos hello-users.properties y hello-roles.properties para autenticación y autorización. JB183-EAP7.0-es-2-20180124 3.1. En la ventana del terminal, ejecute el siguiente script para crear el dominio de seguridad en el servidor EAP: [student@workstation ~]$ cd JB183/labs/security-domain [student@workstation security-domain]$ ./create-sd.sh 3.2. Mediante un editor de texto, abra el archivo de configuración /opt/jbosseap-7.0/standalone/configuration/standalone-full.xml y observe el nuevo dominio de seguridad que controla al servidor de aplicaciones para utilizar los archivos de propiedad de roles y usuarios del directorio de proyectos. <security-domain name="userroles" cache-type="default"> <authentication> <login-module code="UsersRoles" flag="required"> <module-option name=usersProperties" value="file:///home/student/JB183/labs/ securty-domain/hello-users.properties"/> <module-option name="rolesProperties" value="file:///home/student/JB183/labs/ security-domain/hello-users.properties"/> </login-module> </authentication> </security-domain> 4. Actualice el archivo jboss-web.xml para utilizar el nuevo dominio de seguridad. 4.1. Abra el archivo jboss-web.xml; para ello, expanda el ítem security-domain en la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS y, luego, haga clic en security-domain > src/main/webapp > WEB-INF y expándalo. Haga doble clic en el archivo jboss-web.xml. 4.2. Actualice el archivo jboss-web.xml para utilizar el nuevo dominio de seguridad nombrado userroles: <?xml version="1.0" encoding="ISO-8859-1"?> <jboss-web> <security-domain>userroles</security-domain> </jboss-web> 4.3. Presione Ctrl+S para guardar sus cambios. 5. Actualice el archivo web.xml para usar la autenticación BASIC y restringir el acceso al admin.jsf de la aplicación. 5.1. Abra el archivo web.xml; para ello, expanda el ítem security-domain en la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo de JBDS y, luego, haga clic en security-domain > src/main/webapp > WEB-INF y expándalo. Haga doble clic en el archivo web.xml. 5.2. La primera restricción de seguridad se refiere al index.html. Esta es la página principal de la aplicación web. Agregue la siguiente <auth-constraint> a la restricción de seguridad index.html para limitar el acceso a este recurso únicamente a usuarios con el rol guest y admin. JB183-EAP7.0-es-2-20180124 381 Capítulo 9. Protección de aplicaciones Java EE <security-constraint> <web-resource-collection> <web-resource-name>Index</web-resource-name> <url-pattern>/index.html</url-pattern> </web-resource-collection> <!-- Add auth constraint here --> <auth-constraint> <role-name>guest</role-name> <role-name>admin</role-name> </auth-constraint> </security-constraint> 5.3. Actualice la segunda restricción de seguridad para limitar el acceso a la página admin.jsf únicamente a usuarios con el rol admin. Agregue una nueva authconstraint y actualice el url-pattern. <security-constraint> <web-resource-collection> <web-resource-name>Secure resources</web-resource-name> <!-- Update url pattern --> <url-pattern>/admin.jsf</url-pattern> </web-resource-collection> <!-- Add auth constraint --> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint> 5.4. Establezca el auth-method de la login-config como autenticación BASIC: <login-config> <!-- Update the auth method --> <auth-method>BASIC</auth-method> </login-config> 5.5. Presione Ctrl+S para guardar sus cambios. 6. Cree un usuario con el rol guest y un usuario con el rol admin en hellousers.properties y hello-roles.properties para acceder a la aplicación web. 6.1. En una nueva ventana de terminal, navegue al directorio /home/student/ JB183/labs/security-domain y cree un nuevo archivo nombrado hellousers.properties: [student@workstation ~]$ cd /home/student/JB183/labs/security-domain [student@workstation security-domain]$ gedit hello-users.properties 6.2. Actualice el archivo para que cuente con el siguiente contenido para crear dos nuevos usuarios, customer (cliente) y owner (propietario), ambos con la contraseña redhat1!: customer=redhat1! 382 JB183-EAP7.0-es-2-20180124 owner=redhat1! 6.3. Guarde el archivo y cierre el editor. 6.4. Cree un archivo nombrado hello-roles.properties en el directorio /home/ student/JB183/labs/security-domain para relacionar a los nuevos usuarios con los roles admin y guest: [student@workstation security-domain]$ gedit hello-roles.properties 6.5. Actualice el archivo que cuenta con el siguiente contenido para asociar al rol guest con el usuario customer y el rol admin con el usuario owner: customer=guest owner=admin 6.6. Guarde el archivo y cierre el editor. 7. Implemente la aplicación security-domain mediante los siguientes comandos en la ventana del terminal: [student@workstation bin]$ cd /home/student/JB183/labs/security-domain [student@workstation security-domain]$ mvn wildfly:deploy 8. Pruebe el security-domain como usuario customer y owner navegando a http:// localhost:8080/security-domain. 8.1. En Firefox, en la máquina virtual workstation, navegue a http:// localhost:8080/security-domain. 8.2. Cuando se lo solicita, ingrese las siguientes credenciales para iniciar sesión como customer (cliente): • Nombre de usuario: customer • Password (Contraseña): redhat1! 8.3. Haga clic en Admin Page para acceder a la página del admin. El servidor devuelve un mensaje "Forbidden" (Prohibido) ya que el usuario customer no cuenta con la autorización para acceder a esta página. 8.4. En Firefox, diríjase a about:preferences en la URL para acceder a la página de preferencias de Firefox. 8.5. Haga clic en Privacy (Privacidad) y, luego, en clear your recent history (borrar su historial reciente). Haga clic en Clear Now (Borrar ahora) para forzar a la autenticación BASIC a volver a solicitarle credenciales. 8.6. En Firefox, navegue a http://localhost:8080/security-domain e ingrese las siguientes credenciales para iniciar sesión como el usuario owner: • Nombre de usuario: owner • Password (Contraseña): redhat1! JB183-EAP7.0-es-2-20180124 383 Capítulo 9. Protección de aplicaciones Java EE 8.7. En la aplicación security-domain, haga clic en Admin para acceder a la página protegida de la aplicación para ver una lista de usuarios. Si la seguridad se configuró correctamente, la página es accesible. 9. Anule la implementación de la aplicación y detenga JBoss EAP. 9.1. Ejecute el siguiente comando para anular la implementación de la aplicación: [student@workstation security-domain]$ mvn wildfly:undeploy 9.2. Para cerrar el proyecto, haga clic con el botón derecho en el proyecto securitydomain del Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 9.3. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el ejercicio guiado. 384 JB183-EAP7.0-es-2-20180124 Protección de una API REST Protección de una API REST Objetivo Después de completar esta sección, los estudiantes deberán ser capaces de proteger una API rest con autorización y autenticación. Seguridad con RESTEasy Uno de los principales beneficios de exponer la API REST de una aplicación es que la API y los datos posteriores se vuelven disponibles a varios componentes de aplicaciones. No obstante, exponer la API y datos confidenciales de las aplicaciones crea el inherente riesgo adicional de manipulación de datos por usuarios no deseados. Para garantizar la integridad de los datos de las aplicaciones, la API REST se debe proteger tanto para la autenticación como para la autorización. Esto significa que la API debe permitir únicamente usuarios autorizados, y debe validar los privilegios de cada usuario antes de manipular datos. De manera similar a la definición de seguridad en EJB, una API REST se protege con anotaciones o programas. Si bien la seguridad programática proporciona más flexibilidad y control de las restricciones de autorización, el uso de anotaciones para definir las restricciones de seguridad permite más acceso de lectura y son más sencillas de implementar. Sin embargo, algunas veces, se requieren las implementaciones programáticas y las anotaciones de seguridad para cumplir con todos los requisitos de seguridad de una organización. Para permitir una seguridad basada en roles para las API REST, actualice web.xml para que contenga el siguiente <context-param>: <context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> Después de establecer resteasy.role.based.security, actualice el archivo web.xml con un conjunto de restricciones de seguridad a la ruta del servicio RESTEasy. Por ejemplo, la siguiente restricción de seguridad limita la ruta /todo/api/ únicamente a usuarios con el rol admin: <security-constraint> <web-resource-collection> <web-resource-name>RESTEasy</web-resource-name> <url-pattern>/todo/api/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint> La ruta a la API REST. El * es un comodín. En esta instancia, cualquier URL que comienza con /todo/api/ coincide con la restricción de seguridad. La etiqueta para determinar qué rol puede acceder al recurso. JB183-EAP7.0-es-2-20180124 385 Capítulo 9. Protección de aplicaciones Java EE Cada rol enumerado en la restricción de seguridad también se debe definir en el web.xml como <security-role>. Por ejemplo, para hacer referencia al rol admin, defina el rol mediante las siguientes etiquetas: <security-role> <role-name>admin</role-name> </security-role> Complete el archivo web.xml definiendo el login-config. Como se analizó en la sección anterior, esta etiqueta determina la forma en que el servidor autentica al usuario y, opcionalmente, define el dominio de seguridad para la aplicación: <login-config> <auth-method>BASIC</auth-method> <realm-name>ApplicationRealm</realm-name> </login-config> Anotaciones de RESTEasy Debido a que puede ser difícil asignar patrones URL complejos para los servicios RESTEasy en web.xml, RESTEasy proporciona varias anotaciones para proteger extremos. Después de habilitar la seguridad basada en roles en la web.xml de la aplicación REST, use las siguientes anotaciones para proteger extremos específicos por rol, basándose en los roles enumerados en web.xml: • @RolesAllowed: define el o los roles que pueden acceder al método. • @PermitAll: todos los roles definidos en web.xml pueden acceder al método. • @DenyAll: niega acceso a todos los roles al método. El siguiente es un ejemplo de una clase RESTEasy con anotaciones de seguridad: @Stateless @Path("hello") @Produces(MediaType.APPLICATION_JSON) public class HelloWorld { @PermitAll @GET @Produces("text/html") public String hello() { return "<b>Hello World!</b>"; } @RolesAllowed("admin, guest") @GET @Path("newest") public Person getNewestPerson() { ...implementation omitted... } @RolesAllowed("admin") @POST @Consumes(MediaType.APPLICATION_JSON) public String savePerson(Person person) { ...implementation omitted... 386 JB183-EAP7.0-es-2-20180124 Demostración: Protección de una API REST } @DenyAll @DELETE public String deleteAll() { ...implementation omitted... } } Demostración: Protección de una API REST 1. Ejecute el siguiente comando para preparar archivos usados por esta demostración. [student@workstation ~]$ demo secure-rest setup 2. Inicie JBDS e importe el proyecto secure-rest. Este proyecto es un servicio web RESTEasy simple que almacena y lee objetos Person de la base de datos. 3. Agregue los siguientes valores en el <context-param> a web.xml para activar la seguridad RESTEasy: <!-- TODO add RESTEasy context param --> <context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> 4. En la security-constraint aplicada al patrón URL de toda la aplicación, agregue los roles guest y admin en la etiqueta auth-constraint: <auth-constraint> <!-- TODO add roles --> <role-name>guest</role-name> <role-name>admin</role-name> </auth-constraint> 5. Actualice la login-config para definir el método de autenticación como BASIC y usar ApplicationRealm como dominio de seguridad: <login-config> <!-- TODO add login-config --> <auth-method>BASIC</auth-method> <realm-name>ApplicationRealm</realm-name> </login-config> JB183-EAP7.0-es-2-20180124 387 Capítulo 9. Protección de aplicaciones Java EE nota El ApplicationRealm es un dominio de seguridad predeterminado que utiliza el archivo de propiedades /opt/eap/standalone/configuration/ application-roles.properties para roles y el archivo de propiedades / opt/eap/standalone/configuration/application-users.properties para usuarios. Estos archivos se gestionan mediante el script /opt/eap/bin/ add-user.sh. Guarde los cambios en el archivo web.xml con Ctrl+S. 6. Abra la clase de servicio RESTEasy PersonService.java. Actualice cada método @GET con una anotación @PermitAll para permitir a los usuarios admin y guest acceder a estos métodos: ... @GET // TODO add a PermitAll annotation @PermitAll @Path("{id}") public Person getPerson(@PathParam("id") Long id) { ... @GET // TODO add a PermitAll annotation @PermitAll public List<Person> getAllPersons() { ... 7. Actualice los métodos @Delete y @POST anotados para que solo autoricen al usuario admin a ejecutar estos métodos: ... // TODO restrict access for admin @RolesAllowed("admin") @DELETE @Path("{id}") public void deletePerson(@PathParam("id") Long id) { ... // TODO restrict access for admin @RolesAllowed("admin") @POST public Response savePerson(Person person) { ... 8. En una nueva ventana de terminal, ejecute el script /opt/eap/bin/add-user.sh para crear los usuarios customer y owner: [student@workstation ~]$ cd /opt/eap/bin [student@workstation bin]$ ./add-user.sh 9. Use la siguiente información para crear el usuario customer (cliente) con el rol guest: • Application User (Usuario de aplicaciones): b 388 JB183-EAP7.0-es-2-20180124 Demostración: Protección de una API REST • Username (Nombre de usuario): customer • Password (Contraseña): redhat1! • Groups (Grupos): guest • About to add user 'customer' for realm 'ApplicationRealm' (Acerca de agregar el usuario 'customer' para el dominio 'ApplicationRealm'): yes (sí) • Is this new user going to be used for one AS process to connect to another AS process? (¿Este nuevo usuario se utilizará para que un proceso AS se conecte a otro proceso AS?): no 10. Vuelva a ejecutar el script y cree el usuario owner (propietario) con el rol admin: • Application User (Usuario de aplicaciones): b • Username (Nombre de usuario): owner • Password (Contraseña): redhat1! • Groups (Grupos): admin • About to add user 'customer' for realm 'ApplicationRealm' (Acerca de agregar el usuario 'customer' para el dominio 'ApplicationRealm'): yes (sí) • Is this new user going to be used for one AS process to connect to another AS process? (¿Este nuevo usuario se utilizará para que un proceso AS se conecte a otro proceso AS?): no 11. Inicie el servidor JBoss EAP local dentro de JBDS. 12. Ejecute el siguiente comando en una ventana de terminal para implementar la aplicación en el servidor JBoss EAP local: [student@workstation ~]$ cd /home/student/JB183/labs/secure-rest [student@workstation secure-rest]$ mvn wildfly:deploy 13. Acceda al complemento (plug-in) REST-Client en Firefox en la máquina virtual workstation. Ingrese http://localhost:8080/secure-rest/api/persons en la URL. Haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): customer • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 14. Haga clic en Send (Enviar) y observe que el código de devolución es 200. El servidor devuelve una lista vacía de usuarios en la pestaña Response Body (Cuerpo de respuesta). JB183-EAP7.0-es-2-20180124 389 Capítulo 9. Protección de aplicaciones Java EE 15. Cambie el Method Type (Tipo de método) a POST. Haga clic en Headers (Encabezados) en la barra de navegación y, luego, haga clic en Custom Header (Encabezado personalizado). Use la siguiente información para el encabezado personalizado y haga clic en Okay (Aceptar): • Name (Nombre): Content/Type • Value (Valor): application/json 16. Agregue el siguiente JSON a la sección Body de la solicitud para crear un nuevo objeto Person con el nombre Shadowman: {"name": "Shadowman"} Haga clic en Send (Enviar) y observe que el servidor responde con el código HTTP 403 Forbidden. 17. Haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): owner • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar) y, luego, vuelva a hacer clic en Send (Enviar). En este momento, el servidor responde con el código HTTP 200 OK. 18. Cambie el Method Type (Tipo de método) a GET y vuelva a hacer clic en Send (Enviar) para ver el nuevo usuario en la pestaña Response Body (Cuerpo de la respuesta): [{"id":1,"name":"Shadowman"}] 19. Anule la implementación de la aplicación y detenga el servidor. [student@workstation secure-rest]$ mvn wildfly:undeploy Referencias Para obtener más información, consulte la guía Desarrollo de aplicaciones de servicios web para Red Hat JBoss EAP 7 en https://access.redhat.com/documentation/en/red-hat-jboss-enterpriseapplication-platform/ 390 JB183-EAP7.0-es-2-20180124 Ejercicio guiado: Protección de una API REST Ejercicio guiado: Protección de una API REST En este ejercicio, protegerá un servicio REST para la autenticación y la autorización. Resultado Deberá ser capaz de usar las anotaciones de seguridad RESTEasy para proteger la autenticación y la autorización en un servicio web REST. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab rest-annotations setup Pasos 1. Abra JBDS e importe el proyecto de Maven. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta restannotations y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde para iniciar el servidor. Observe la Console (Consola) hasta que el servidor se inicie y muestre el siguiente mensaje: INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.0.0.GA (WildFly Core 2.1.2.Final-redhat-1) started 3. Actualice el archivo web.xml para permitir anotaciones de seguridad RESTEasy, agregue una restricción de seguridad para la ruta del servicio REST y use un método de autenticación BASIC. 3.1. Abra el archivo web.xml; para ello, expanda el ítem rest-annotations en la pestaña Project Explorer (Explorador de proyectos) del panel izquierdo JBDS y, luego, haga JB183-EAP7.0-es-2-20180124 391 Capítulo 9. Protección de aplicaciones Java EE clic en rest-annotations > src/main/webapp > WEB-INF y expándalo. Haga doble clic en el archivo web.xml. 3.2. Cree una nueva etiqueta <context-param> que contenga el parámetro resteasy.role.based.security con un valor de true: <!-- Add context param --> <context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> 3.3. Cree una nueva security-constraint que restrinja todos los recursos para la aplicación con un <url-pattern> establecido como /*: <!-- Add security constraint --> <security-constraint> <web-resource-collection> <web-resource-name>All resources</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> </security-constraint> 3.4. Actualice <security-constraint para restringir el acceso para usuarios con el rol guest o admin mediante la etiqueta auth-constraint: <security-constraint> <web-resource-collection> <web-resource-name>All resources</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>guest</role-name> <role-name>admin</role-name> </auth-constraint> </security-constraint> 3.5. Defina roles de seguridad para los roles guest y admin con una etiqueta <security-role>: <!-- Add security role --> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>guest</role-name> </security-role> 3.6. Cree un elemento <login-config>, establezca el <auth-method> en BASIC y use el dominio de seguridad predefinido, ApplicationRealm. <!-- Add login config --> 392 JB183-EAP7.0-es-2-20180124 <login-config> <auth-method>BASIC</auth-method> <realm-name>ApplicationRealm</realm-name> </login-config> 3.7. Presione Ctrl+S para guardar sus cambios. 4. Esta aplicación ahora usa el ApplicationRealm para autenticar usuarios. El ApplicationRealm es un dominio de seguridad predeterminado que utiliza el archivo de propiedades /opt/eap/standalone/configuration/applicationroles.properties para roles y el archivo de propiedades /opt/eap/standalone/ configuration/application-users.properties para usuarios. Use el script /opt/eap/bin/add-user.sh para crear los siguientes usuarios y sus respectivos roles: • customer (cliente): guest • owner (propietario): admin • superuser: guest, admin 4.1. Ejecute el script /opt/eap/bin/add-user.sh en una nueva ventana de terminal: [student@workstation ~]$ cd /opt/eap/bin [student@workstation bin]$ ./add-user.sh 4.2. Use la siguiente información para crear el usuario customer (cliente) con el rol guest cuando se le indique: • Application User (Usuario de aplicaciones): b • Username (Nombre de usuario): customer • Password (Contraseña): redhat1! • Groups (Grupos): guest • About to add user 'customer' for realm 'ApplicationRealm' (Acerca de agregar el usuario 'customer' para el dominio 'ApplicationRealm'): yes (sí) • Is this new user going to be used for one AS process to connect to another AS process? (¿Este nuevo usuario se utilizará para que un proceso AS se conecte a otro proceso AS?): no 4.3. Vuelva a ejecutar el script y use la siguiente información para crear el usuario owner (propietario) con el rol guest cuando se le indique: • Application User (Usuario de aplicaciones): b • Username (Nombre de usuario): owner • Password (Contraseña): redhat1! • Groups (Grupos): admin JB183-EAP7.0-es-2-20180124 393 Capítulo 9. Protección de aplicaciones Java EE • About to add user 'customer' for realm 'ApplicationRealm' (Acerca de agregar el usuario 'customer' para el dominio 'ApplicationRealm'): yes (sí) • Is this new user going to be used for one AS process to connect to another AS process? (¿Este nuevo usuario se utilizará para que un proceso AS se conecte a otro proceso AS?): no 4.4. Vuelva a ejecutar el script por tercera vez y use la siguiente información para crear el usuario superuser con admin y guest cuando se le indique: • Application User (Usuario de aplicaciones): b • Username (Nombre de usuario): superuser • Password (Contraseña): redhat1! • Groups (Grupos): admin, guest • About to add user 'customer' for realm 'ApplicationRealm' (Acerca de agregar el usuario 'customer' para el dominio 'ApplicationRealm'): yes (sí) • Is this new user going to be used for one AS process to connect to another AS process? (¿Este nuevo usuario se utilizará para que un proceso AS se conecte a otro proceso AS?): no 4.5. Use el siguiente comando para ver el contenido de /opt/eap/standalone/ configuration/application-users.properties con el siguiente comando: [student@workstation ~]$ less /opt/eap/standalone/configuration\ /application-users.properties ... customer=a2d94c2cc5f3a4706e669da24b9a9bf0 owner=2f408f4ad343d859807d7593128b3767 superuser=8e90480af06a793a083c16991b11dc99 ... Observe que cada línea contiene el usuario y su contraseña con hash. 4.6. Use el siguiente comando para ver el contenido de /opt/eap/standalone/ configuration/application-roles.properties con el siguiente comando: [student@workstation ~]$ less /opt/eap/standalone/configuration\ /application-roles.properties ... customer=guest owner=admin superuser=admin,guest ... Observe que cada línea contiene un usuario y el o los roles asignados a este. 394 JB183-EAP7.0-es-2-20180124 5. Actualice la clase de servicio RESTEasy PersonService.java y actualice cada método con una anotación de seguridad RESTEasy. 5.1. En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, seleccione src/main/java > com.redhat.training.rest y haga doble clic en PersonService.java para ver el archivo. 5.2. Actualice el método getPerson() con una anotación @PermitAll para permitir que todos los roles enumerados en web.xml puedan acceder al método: //TODO permit all users @PermitAll @GET @Path("{id}") public Person getPerson(@PathParam("id") Long id) { ... 5.3. Actualice el método assignAllPersons() con una anotación @RolesAllowed para permitir que solo los usuarios con el rol guest accedan al método: //TODO allow only guest @RolesAllowed("guest") @GET public List<Person> getAllPersons() { ... 5.4. Actualice el método deletePerson() DELETE con una anotación @DenyAll para evitar que todos los roles enumerados en web.xml accedan a este método: //TODO restrict access for all roles @DenyAll @DELETE @Path("{id}") public void deletePerson(@PathParam("id") Long id) { ... 5.5. Actualice el método savePerson() POST con una anotación @RolesAllowed para permitir que solo los usuarios con el rol admin accedan al método: //TODO allow only admin @RolesAllowed("admin") @POST public Response savePerson(Person person) { ... 5.6. Presione Ctrl+S para guardar sus cambios. 6. Navegue al directorio del proyecto e implemente la aplicación. [student@workstation ~]$ cd /home/student/JB183/labs/rest-annotations [student@workstation rest-annotations]$ mvn wildfly:deploy JB183-EAP7.0-es-2-20180124 395 Capítulo 9. Protección de aplicaciones Java EE 7. Use el complemento (plug-in) de cliente REST de Firefox para probar la API REST protegida. 7.1. Acceda al complemento (plug-in) REST-Client en Firefox en la máquina virtual workstation. Figura 9.2: Complemento (plug-in) de cliente REST de Firefox 7.2. Ingrese http://localhost:8080/rest-annotations/api/persons en la URL. 7.3. Haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): customer • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 7.4. Haga clic en Send (Enviar) y observe que el código de devolución es 200. Haga clic en la pestaña Response Body (Cuerpo de la respuesta) y observe que la carga es cualquier lista vacía, ya que aún no se han agregado usuarios a la base de datos. 7.5. Cambie el Method Type (Tipo de método) a POST. 7.6. Haga clic en Headers (Encabezados) en la barra de navegación y, luego, haga clic en Custom Header (Encabezado personalizado). Use la siguiente información para el encabezado personalizado: • Name (Nombre): Content/Type • Value (Valor): application/json Haga clic en Okay (Aceptar). 7.7. Agregue el siguiente JSON a la sección Body de la solicitud para crear un nuevo objeto Person con el nombre RedHat: {"name": "RedHat"} 7.8. Haga clic en Send (Enviar) y observe que el servidor responde con el código HTTP 403 Forbidden ya que el usuario customer (cliente) con el rol guest no está autorizado a usar ese método en la clase de servicio REST. 8. Vuelva a probar la API REST con los usuarios owner y superuser. 8.1. Haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): owner 396 JB183-EAP7.0-es-2-20180124 • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 8.2. Haga clic en Send (Enviar) usando los mismos datos de la solicitud JSON del paso anterior. En este momento, el servidor responde con el código HTTP 200 OK. 8.3. Verifique que los datos se hayan almacenado en la base de datos modificando el Method Type (Tipo de método) a GET y, luego, volviendo a hacer clic en Send (Enviar). El servidor responde con otro código HTTP 403 Forbidden porque el usuario owner con el rol admin no está autorizado a ver todos los usuarios. 8.4. Haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): superuser • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 8.5. El usuario superuser pertenece a los roles guest y admin y, por lo tanto, puede guardar los nuevos objetos Person y obtener una lista de todos los objetos en la base de datos. Con el Method Type (Tipo de método) aún establecido como GET, vuelva a hacer clic en Send (Enviar). En este momento, el servidor responde con el código HTTP 200 OK y los siguientes datos en la pestaña Response Body (Cuerpo de la respuesta): [{"id":1,"name":"RedHat"}] 8.6. Mientras aún está autenticado como superuser, cambie el Method Type (Tipo de método) a POST y agregue el siguiente JSON al Body (Cuerpo) de la solicitud: {"name": "Shadowman"} Haga clic en Send (Enviar). 8.7. Verifique que los datos se hayan almacenado en la base de datos modificando el Method Type (Tipo de método) a GET y, luego, volviendo a hacer clic en Send (Enviar). El servidor responde con el código HTTP 200 OK y los siguientes datos en la pestaña Response Body (Cuerpo de la respuesta): [{"id":1,"name":"RedHat"}, {"id":2, "name":"Shadowman"}] 8.8. Elimine el objeto Person en la base de datos con el ID 1 actualizando la solicitud URL a http://localhost:8080/rest-annotations/api/persons/1 y cambiando el Method Type (Tipo de método) a DELETE (ELIMINAR). JB183-EAP7.0-es-2-20180124 397 Capítulo 9. Protección de aplicaciones Java EE 8.9. Haga clic en Send (Enviar). El servidor devuelve otro código 403 Forbidden, ya que este método se anota con una anotación @DenyAll que evita que los usuarios lo ejecuten. 9. Anule la implementación de la aplicación y detenga JBoss EAP. 9.1. Ejecute el siguiente comando para anular la implementación de la aplicación: [student@workstation rest-annotations]$ mvn wildfly:undeploy 9.2. Para cerrar el proyecto, haga clic con el botón derecho en el proyecto restannotations del Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 9.3. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el ejercicio guiado. 398 JB183-EAP7.0-es-2-20180124 Trabajo de laboratorio: Protección de aplicaciones Java EE Trabajo de laboratorio: Protección de aplicaciones Java EE En este trabajo de laboratorio, protegerá un servicio REST de la aplicación To Do List con autenticación y autorización. Resultados Deberá ser capaz de proteger un servicio REST con autorización y autenticación. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab securing-lab setup Pasos 1. Abra JBDS e importe el proyecto securing-lab ubicado en el directorio /home/ student/JB183/labs/securing-lab. 2. Inicie JBoss EAP desde dentro de JBDS. 3. Use el script /home/student/JB183/labs/securing-lab/create-sd.sh para crear un dominio de seguridad UsersRoles nombrado userroles. Este dominio de seguridad usa el módulo de inicio de sesión UsersRoles para leer el archivo de propiedad del usuario /home/student/JB183/labs/securing-lab/todousers.properties proporcionado para la autenticación y el archivo /home/student/ JB183/labs/securing-lab/todo-roles.properties para la autorización. 3.1. En la ventana del terminal, diríjase al directorio del proyecto /home/student/ JB183/labs/securing-lab/ y ejecute el script create-sd.sh: [student@workstation ~]$ cd JB183/labs/securing-lab [student@workstation securing-lab]$ ./create-sd.sh 3.2. Confirme que el dominio de seguridad está disponible al visualizar el contenido de la configuración del servidor EAP /opt/jboss-eap-7.0/standalone/ configuration/standalone-full.xml. Mediante un editor de texto, abra el archivo de configuración /opt/jbosseap-7.0/standalone/configuration/standalone-full.xml y observe el nuevo dominio de seguridad que controla al servidor de aplicaciones para utilizar los archivos de propiedad de roles y usuarios proporcionados en el directorio de proyectos. <security-domain name="userroles" cache-type="default"> <authentication> <login-module code="UsersRoles" flag="required"> <module-option name=usersProperties" value="file:///home/student/JB183/labs/ securing-lab/todo-users.properties"/> JB183-EAP7.0-es-2-20180124 399 Capítulo 9. Protección de aplicaciones Java EE <module-option name="rolesProperties" value="file:///home/student/JB183/labs/ securing-lab/todo-users.properties"/> </login-module> </authentication> </security-domain> 4. Actualice el archivo jboss-web.xml para utilizar el dominio de seguridad userroles. 4.1. Abra la clase jboss-web.xml; para ello, expanda el ítem securing-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en securing-lab > src/main/webapp > WEB-INF y expándalo. Haga doble clic en el archivo jboss-web.xml. 4.2. Actualice el archivo jboss-web.xml para utilizar el nuevo dominio de seguridad nombrado userroles: <?xml version="1.0" encoding="ISO-8859-1"?> <jboss-web> <security-domain>userroles</security-domain> </jboss-web> 4.3. Presione Ctrl+S para guardar sus cambios. 5. Actualice el archivo web.xml para habilitar las anotaciones de seguridad RESTEasy, utilice la autenticación BASIC y restrinja el acceso a la API REST de la aplicación con la ruta / api/* a los roles admin, guest y observer. 5.1. Abra la clase web.xml; para ello, expanda el ítem securing-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en securing-lab > src/main/webapp > WEB-INF y expándalo. Haga doble clic en el archivo web.xml. 5.2. Cree una nueva etiqueta <context-param> que contenga el parámetro resteasy.role.based.security con un valor de true: <!-- Add context param --> <context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> 5.3. Cree una nueva security-constraint que restrinja todos los recursos para la aplicación con un <url-pattern> establecido como /api/*: <!-- Add security constraint --> <security-constraint> <web-resource-collection> <web-resource-name>REST Resources</web-resource-name> <url-pattern>/api/*</url-pattern> </web-resource-collection> </security-constraint> 400 JB183-EAP7.0-es-2-20180124 5.4. Actualice <security-constraint para restringir el acceso para usuarios con el rol guest, observer o admin mediante la etiqueta auth-constraint: <security-constraint> <web-resource-collection> <web-resource-name>All resources</web-resource-name> <url-pattern>/api/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>guest</role-name> <role-name>admin</role-name> <role-name>observer</role-name> </auth-constraint> </security-constraint> 5.5. Defina los roles de seguridad para los roles guest, observer y admin con una etiqueta <security-role>: <!-- Add security role --> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>guest</role-name> </security-role> <security-role> <role-name>observer</role-name> </security-role> 5.6. Cree un elemento <login-config> y defina <auth-method> como BASIC. <!-- Add login config --> <login-config> <auth-method>BASIC</auth-method> </login-config> 5.7. Presione Ctrl+S para guardar sus cambios. 6. Actualice la clase de servicio RESTEasy ItemResourceRESTService.java y actualice cada método con los siguientes requisitos: • Todos los métodos GET se permiten para todos los roles. • Todos los métodos POST se permiten para los roles admin y guest. • Todos los métodos DELETE se permiten solo para usuarios con el rol admin. Observe que el método @PostConstruct recupera el usuario Principal de la solicitud para determinar qué usuario ha iniciado sesión actualmente. De esta forma, el servicio REST solo recupera resultados para ese usuario específico. JB183-EAP7.0-es-2-20180124 401 Capítulo 9. Protección de aplicaciones Java EE 6.1. En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, seleccione src/main/java > com.redhat.training.rest. Haga doble clic en ItemResourceRESTService.java para ver el archivo. 6.2. Actualice el método listAllItems con una anotación @PermitAll para permitir que todos los roles enumerados en web.xml puedan acceder al método: @PermitAll @GET @Produces(MediaType.APPLICATION_JSON) public List<Item> listAllItems() { return repository.findAllItemsForUser(currentUser); } 6.3. Actualice el método lookupItemById() con una anotación @PermitAll para permitir que todos los roles enumerados en web.xml puedan acceder al método: @PermitAll @GET @Path("/{id:[0-9][0-9]*}") @Produces(MediaType.APPLICATION_JSON) public Item lookupItemById(@PathParam("id") long id) { ... 6.4. Actualice el método createItem() POST con una anotación @RolesAllowed para permitir que solo los usuarios con el rol admin o guest accedan al método: @RolesAllowed({"admin", "guest"}) @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response createItem(Item item) { ... 6.5. Actualice el método deleteItem() DELETE con una anotación @RolesAllowed para permitir que solo los usuarios con el rol admin accedan al método: @RolesAllowed("admin") @DELETE @Path("{id}") @Consumes(MediaType.APPLICATION_JSON) public void deleteItem(@PathParam("id") Long id) { itemService.remove(id); } 6.6. Presione Ctrl+S para guardar sus cambios. 7. Compile e implemente la aplicación en JBoss EAP mediante Maven ejecutando los siguientes comandos: 8. Use el complemento (plug-in) de cliente REST para probar la API REST protegida en la URL http://localhost:8080/securing-lab/api/items. Use el complemento (plug-in) 402 JB183-EAP7.0-es-2-20180124 para verificar que el usuario customer con la contraseña redhat1! tenga permisos para acceder solo a los métodos GET y POST. 8.1. Acceda al complemento (plug-in) REST-Client en Firefox en la máquina virtual workstation. Complemento (plug-in) de cliente REST de Firefox 8.2. Ingrese http://localhost:8080/securing-lab/api/items en la URL. 8.3. Haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): customer • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 8.4. Cambie el Method Type (Tipo de método) a POST. 8.5. Haga clic en Headers (Encabezados) en la barra de navegación y, luego, haga clic en Custom Header (Encabezado personalizado). Use la siguiente información para el encabezado personalizado: • Name (Nombre): Content/Type • Value (Valor): application/json Haga clic en Okay (Aceptar). 8.6. Agregue el siguiente JSON al Body (Cuerpo) de la solicitud para crear un nuevo Item con la descripción Walk the dog: {"description": "Walk the dog"} Haga clic en Send (Enviar). 8.7. Cambie el Method Type (Tipo de método) a GET y vuelva a hacer clic en Send (Enviar) para ver una lista de todos los objetos Item: [{"id:11, "description":"Walk the dog","done":false,"user":"id":2,"username":"customer"}}] 8.8. Mediante el ID de la respuesta anterior, ingrese http://localhost:8080/ securing-lab/api/items/11 en la URL del complemento (plug-in) y cambie el Method Type (Tipo de método) a DELETE (ELIMINAR). Haga clic en Send (Enviar) y el servidor devolverá una respuesta 403 Forbidden. JB183-EAP7.0-es-2-20180124 403 Capítulo 9. Protección de aplicaciones Java EE 9. Use el complemento (plug-in) para verificar que el usuario owner con la contraseña redhat1! tenga permisos para acceder a los métodos GET, POST y DELETE. 9.1. En el complemento (plug-in) de Firefox, haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): owner • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 9.2. Cambie el Method Type (Tipo de método) a POST y actualice la URL a http:// localhost:8080/securing-lab/api/items/. 9.3. Agregue el siguiente JSON al Body (Cuerpo) de la solicitud para crear un nuevo Item con la descripción Test owner: {"description": "Test owner"} Haga clic en Send (Enviar). 9.4. Cambie el Method Type (Tipo de método) a GET y vuelva a hacer clic en Send (Enviar) para ver una lista de todos los objetos Item: [{"id:12, "description":"Test owner","done":false,"user":"id":3,"username":"owner"}}] 9.5. Mediante el ID de la respuesta anterior, ingrese http://localhost:8080/ securing-lab/api/items/12 en la URL del complemento (plug-in) y cambie el Method Type (Tipo de método) a DELETE (ELIMINAR). Haga clic en Send (Enviar) y el servidor devolverá una respuesta 204. 9.6. Cambie el Method Type (Tipo de método) a GET, actualice la URL a http:// localhost:8080/securing-lab/api/items/ y vuelva a hacer clic en Send (Enviar) para ver que el objeto Item se haya eliminado. 10. Use el complemento (plug-in) para verificar que el usuario viewer con la contraseña redhat1! tenga permisos para acceder solo a los métodos GET. 10.1.En el complemento (plug-in) de Firefox, haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): viewer • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 10.2.Cambie el Method Type (Tipo de método) a POST. 404 JB183-EAP7.0-es-2-20180124 10.3.Agregue el siguiente JSON al Body (Cuerpo) de la solicitud para crear un nuevo Item con la descripción Test viewer: {"description": "Test viewer"} Haga clic en Send (Enviar). El servidor devuelve un código 403 Forbidden. 10.4.Cambie el Method Type (Tipo de método) a GET y haga clic en Send (Enviar) para ver una respuesta 200 Ok del servidor, aunque la lista esté vacía. 11. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab securing-lab grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 12. Realice la limpieza. 12.1.Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/securing-lab [student@workstation securing-lab]$ mvn wildfly:undeploy 12.2.Haga clic con el botón derecho en el proyecto securing-lab en el Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 12.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 405 Capítulo 9. Protección de aplicaciones Java EE Solución En este trabajo de laboratorio, protegerá un servicio REST de la aplicación To Do List con autenticación y autorización. Resultados Deberá ser capaz de proteger un servicio REST con autorización y autenticación. Antes de comenzar Abra una ventana de terminal en la máquina virtual workstation y ejecute el siguiente comando para descargar los archivos necesarios para este trabajo de laboratorio. [student@workstation ~]$ lab securing-lab setup Pasos 1. Abra JBDS e importe el proyecto securing-lab ubicado en el directorio /home/ student/JB183/labs/securing-lab. 1.1. Para abrir JBDS, haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo. Deje el espacio de trabajo predeterminado (/home/ student/JB183/workspace) y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta securing-lab y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Inicie JBoss EAP desde dentro de JBDS. Seleccione la pestaña Servers (Servidores) en JBDS. Haga clic con el botón derecho en la entrada del servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en la opción verde Start (Iniciar) para iniciar el servidor. 3. Use el script /home/student/JB183/labs/securing-lab/create-sd.sh para crear un dominio de seguridad UsersRoles nombrado userroles. Este dominio de seguridad usa el módulo de inicio de sesión UsersRoles para leer el archivo de propiedad del usuario /home/student/JB183/labs/securing-lab/todousers.properties proporcionado para la autenticación y el archivo /home/student/ JB183/labs/securing-lab/todo-roles.properties para la autorización. 406 JB183-EAP7.0-es-2-20180124 Solución 3.1. En la ventana del terminal, diríjase al directorio del proyecto /home/student/ JB183/labs/securing-lab/ y ejecute el script create-sd.sh: [student@workstation ~]$ cd JB183/labs/securing-lab [student@workstation securing-lab]$ ./create-sd.sh 3.2. Confirme que el dominio de seguridad está disponible al visualizar el contenido de la configuración del servidor EAP /opt/jboss-eap-7.0/standalone/ configuration/standalone-full.xml. Mediante un editor de texto, abra el archivo de configuración /opt/jbosseap-7.0/standalone/configuration/standalone-full.xml y observe el nuevo dominio de seguridad que controla al servidor de aplicaciones para utilizar los archivos de propiedad de roles y usuarios proporcionados en el directorio de proyectos. <security-domain name="userroles" cache-type="default"> <authentication> <login-module code="UsersRoles" flag="required"> <module-option name=usersProperties" value="file:///home/student/JB183/labs/ securing-lab/todo-users.properties"/> <module-option name="rolesProperties" value="file:///home/student/JB183/labs/ securing-lab/todo-users.properties"/> </login-module> </authentication> </security-domain> 4. Actualice el archivo jboss-web.xml para utilizar el dominio de seguridad userroles. 4.1. Abra la clase jboss-web.xml; para ello, expanda el ítem securing-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en securing-lab > src/main/webapp > WEB-INF y expándalo. Haga doble clic en el archivo jboss-web.xml. 4.2. Actualice el archivo jboss-web.xml para utilizar el nuevo dominio de seguridad nombrado userroles: <?xml version="1.0" encoding="ISO-8859-1"?> <jboss-web> <security-domain>userroles</security-domain> </jboss-web> 4.3. Presione Ctrl+S para guardar sus cambios. 5. Actualice el archivo web.xml para habilitar las anotaciones de seguridad RESTEasy, utilice la autenticación BASIC y restrinja el acceso a la API REST de la aplicación con la ruta / api/* a los roles admin, guest y observer. 5.1. Abra la clase web.xml; para ello, expanda el ítem securing-lab en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en securing-lab > src/main/webapp > WEB-INF y expándalo. Haga doble clic en el archivo web.xml. JB183-EAP7.0-es-2-20180124 407 Capítulo 9. Protección de aplicaciones Java EE 5.2. Cree una nueva etiqueta <context-param> que contenga el parámetro resteasy.role.based.security con un valor de true: <!-- Add context param --> <context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> 5.3. Cree una nueva security-constraint que restrinja todos los recursos para la aplicación con un <url-pattern> establecido como /api/*: <!-- Add security constraint --> <security-constraint> <web-resource-collection> <web-resource-name>REST Resources</web-resource-name> <url-pattern>/api/*</url-pattern> </web-resource-collection> </security-constraint> 5.4. Actualice <security-constraint para restringir el acceso para usuarios con el rol guest, observer o admin mediante la etiqueta auth-constraint: <security-constraint> <web-resource-collection> <web-resource-name>All resources</web-resource-name> <url-pattern>/api/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>guest</role-name> <role-name>admin</role-name> <role-name>observer</role-name> </auth-constraint> </security-constraint> 5.5. Defina los roles de seguridad para los roles guest, observer y admin con una etiqueta <security-role>: <!-- Add security role --> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>guest</role-name> </security-role> <security-role> <role-name>observer</role-name> </security-role> 5.6. Cree un elemento <login-config> y defina <auth-method> como BASIC. 408 JB183-EAP7.0-es-2-20180124 Solución <!-- Add login config --> <login-config> <auth-method>BASIC</auth-method> </login-config> 5.7. Presione Ctrl+S para guardar sus cambios. 6. Actualice la clase de servicio RESTEasy ItemResourceRESTService.java y actualice cada método con los siguientes requisitos: • Todos los métodos GET se permiten para todos los roles. • Todos los métodos POST se permiten para los roles admin y guest. • Todos los métodos DELETE se permiten solo para usuarios con el rol admin. Observe que el método @PostConstruct recupera el usuario Principal de la solicitud para determinar qué usuario ha iniciado sesión actualmente. De esta forma, el servicio REST solo recupera resultados para ese usuario específico. 6.1. En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, seleccione src/main/java > com.redhat.training.rest. Haga doble clic en ItemResourceRESTService.java para ver el archivo. 6.2. Actualice el método listAllItems con una anotación @PermitAll para permitir que todos los roles enumerados en web.xml puedan acceder al método: @PermitAll @GET @Produces(MediaType.APPLICATION_JSON) public List<Item> listAllItems() { return repository.findAllItemsForUser(currentUser); } 6.3. Actualice el método lookupItemById() con una anotación @PermitAll para permitir que todos los roles enumerados en web.xml puedan acceder al método: @PermitAll @GET @Path("/{id:[0-9][0-9]*}") @Produces(MediaType.APPLICATION_JSON) public Item lookupItemById(@PathParam("id") long id) { ... 6.4. Actualice el método createItem() POST con una anotación @RolesAllowed para permitir que solo los usuarios con el rol admin o guest accedan al método: @RolesAllowed({"admin", "guest"}) @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response createItem(Item item) { ... JB183-EAP7.0-es-2-20180124 409 Capítulo 9. Protección de aplicaciones Java EE 6.5. Actualice el método deleteItem() DELETE con una anotación @RolesAllowed para permitir que solo los usuarios con el rol admin accedan al método: @RolesAllowed("admin") @DELETE @Path("{id}") @Consumes(MediaType.APPLICATION_JSON) public void deleteItem(@PathParam("id") Long id) { itemService.remove(id); } 6.6. Presione Ctrl+S para guardar sus cambios. 7. Compile e implemente la aplicación en JBoss EAP mediante Maven ejecutando los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/securing-lab [student@workstation securing-lab]$ mvn clean wildfly:deploy 8. Use el complemento (plug-in) de cliente REST para probar la API REST protegida en la URL http://localhost:8080/securing-lab/api/items. Use el complemento (plug-in) para verificar que el usuario customer con la contraseña redhat1! tenga permisos para acceder solo a los métodos GET y POST. 8.1. Acceda al complemento (plug-in) REST-Client en Firefox en la máquina virtual workstation. Complemento (plug-in) de cliente REST de Firefox 8.2. Ingrese http://localhost:8080/securing-lab/api/items en la URL. 8.3. Haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): customer • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 8.4. Cambie el Method Type (Tipo de método) a POST. 8.5. Haga clic en Headers (Encabezados) en la barra de navegación y, luego, haga clic en Custom Header (Encabezado personalizado). Use la siguiente información para el encabezado personalizado: • Name (Nombre): Content/Type • Value (Valor): application/json 410 JB183-EAP7.0-es-2-20180124 Solución Haga clic en Okay (Aceptar). 8.6. Agregue el siguiente JSON al Body (Cuerpo) de la solicitud para crear un nuevo Item con la descripción Walk the dog: {"description": "Walk the dog"} Haga clic en Send (Enviar). 8.7. Cambie el Method Type (Tipo de método) a GET y vuelva a hacer clic en Send (Enviar) para ver una lista de todos los objetos Item: [{"id:11, "description":"Walk the dog","done":false,"user":"id":2,"username":"customer"}}] 8.8. Mediante el ID de la respuesta anterior, ingrese http://localhost:8080/ securing-lab/api/items/11 en la URL del complemento (plug-in) y cambie el Method Type (Tipo de método) a DELETE (ELIMINAR). Haga clic en Send (Enviar) y el servidor devolverá una respuesta 403 Forbidden. 9. Use el complemento (plug-in) para verificar que el usuario owner con la contraseña redhat1! tenga permisos para acceder a los métodos GET, POST y DELETE. 9.1. En el complemento (plug-in) de Firefox, haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): owner • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 9.2. Cambie el Method Type (Tipo de método) a POST y actualice la URL a http:// localhost:8080/securing-lab/api/items/. 9.3. Agregue el siguiente JSON al Body (Cuerpo) de la solicitud para crear un nuevo Item con la descripción Test owner: {"description": "Test owner"} Haga clic en Send (Enviar). 9.4. Cambie el Method Type (Tipo de método) a GET y vuelva a hacer clic en Send (Enviar) para ver una lista de todos los objetos Item: [{"id:12, "description":"Test owner","done":false,"user":"id":3,"username":"owner"}}] 9.5. Mediante el ID de la respuesta anterior, ingrese http://localhost:8080/ securing-lab/api/items/12 en la URL del complemento (plug-in) y cambie el JB183-EAP7.0-es-2-20180124 411 Capítulo 9. Protección de aplicaciones Java EE Method Type (Tipo de método) a DELETE (ELIMINAR). Haga clic en Send (Enviar) y el servidor devolverá una respuesta 204. 9.6. Cambie el Method Type (Tipo de método) a GET, actualice la URL a http:// localhost:8080/securing-lab/api/items/ y vuelva a hacer clic en Send (Enviar) para ver que el objeto Item se haya eliminado. 10. Use el complemento (plug-in) para verificar que el usuario viewer con la contraseña redhat1! tenga permisos para acceder solo a los métodos GET. 10.1.En el complemento (plug-in) de Firefox, haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): viewer • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 10.2.Cambie el Method Type (Tipo de método) a POST. 10.3.Agregue el siguiente JSON al Body (Cuerpo) de la solicitud para crear un nuevo Item con la descripción Test viewer: {"description": "Test viewer"} Haga clic en Send (Enviar). El servidor devuelve un código 403 Forbidden. 10.4.Cambie el Method Type (Tipo de método) a GET y haga clic en Send (Enviar) para ver una respuesta 200 Ok del servidor, aunque la lista esté vacía. 11. Abra una nueva ventana de terminal y ejecute el siguiente comando para calificar el trabajo de laboratorio: [student@workstation ~]$ lab securing-lab grade El script de calificación debe indicar SUCCESS (CORRECTO). Si se produce una falla, revise los errores y corríjalos hasta que vea un mensaje que indique SUCCESS (CORRECTO). 12. Realice la limpieza. 12.1.Anule la implementación de la aplicación en JBoss EAP con Maven con los siguientes comandos: [student@workstation ~]$ cd /home/student/JB183/labs/securing-lab [student@workstation securing-lab]$ mvn wildfly:undeploy 12.2.Haga clic con el botón derecho en el proyecto securing-lab en el Project Explorer (Explorador de proyectos) y seleccione Close Project (Cerrar proyecto). 12.3.Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 en la pestaña Servers (Servidores) de JBDS y haga clic en Stop (Detener). 412 JB183-EAP7.0-es-2-20180124 Solución Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 413 Capítulo 9. Protección de aplicaciones Java EE Resumen En este capítulo, aprendió lo siguiente: • El Servicio de Autenticación y Autorización de Java (JAAS) es una API de seguridad que se usa para implementar aplicaciones de autenticación y autorización en Java. • La seguridad declarativa aprovecha los descriptores y las anotaciones de implementación para definir el comportamiento de seguridad, mientras que la seguridad programática usa librerías para gestionar la autenticación y autorización de usuarios. • web.xml y jboss-web.xml son descriptores de implementación utilizados para definir el comportamiento de seguridad para una aplicación. • En EAP, un dominio de seguridad define un conjunto de configuraciones de seguridad declarativas de JAAS. • El dominio de seguridad predeterminado other en EAP usa ApplicationRealm, que almacena información del usuario en los archivos de propiedades applicationusers.properties y application-roles.properties. • Para usar las anotaciones de seguridad RESTEasy, agregue el parámetro de contexto resteasy.role.based.security al archivo web.xml de la aplicación. • RESTEasy proporciona las siguientes anotaciones para proteger un servicio REST con seguridad declarativa: ◦ @DenyAll ◦ @PermitAll ◦ @RolesAllowed 414 JB183-EAP7.0-es-2-20180124 TRAINING CAPÍTULO 10 REVISIÓN COMPLETA: RED HAT APPLICATION DEVELOPMENT I: PROGRAMMING IN JAVA EE Descripción general Meta Revisar tareas de Red Hat Application Development I: Programming in Java EE Objetivos • Revisar tareas de Red Hat Application Development I: Programming in Java EE Secciones • Revisión completa Trabajo de laboratorio • Trabajo de laboratorio: Creación de una API mediante JAX-RS • Trabajo de laboratorio: Persistencia de datos con JPA • Trabajo de laboratorio: Protección de la API REST con JAAS JB183-EAP7.0-es-2-20180124 415 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE Revisión completa Objetivos Tras finalizar esta sección, los estudiantes deberán ser capaces de revisar y actualizar las habilidades y los conocimientos aprendidos en Red Hat Application Development I: Programming in Java EE . Revisión Red Hat Application Development I: Programming in Java EE Antes de comenzar la revisión integral de este curso, los estudiantes deben sentirse cómodos con los temas que se explicaron en cada capítulo. Los estudiantes pueden consultar las secciones anteriores en el libro de textos para lecturas complementarias. Capítulo 1, Transición a aplicaciones con varios niveles Describir características de Java EE y distinguir entre las aplicaciones Java EE y Java SE. • Describir "aplicación empresarial" y nombrar algunos de los beneficios de las aplicaciones Java EE. • Comparar las características de Java EE y Java SE. • Describir las especificaciones y los números de versión de Java EE 7 y el proceso utilizado para introducir API nuevas y actualizadas en Java EE. • Describir las diferentes arquitecturas con varios niveles. • Instalar JBoss Developer Studio, Maven y JBoss Enterprise Application Platform. Capítulo 2, Empaquetado e implementación de una aplicación de Java EE Describir la arquitectura de un servidor de aplicaciones Java EE, empaquetar una aplicación e implementar la aplicación en un servidor EAP. • Identificar las características clave de los servidores de aplicaciones y describir el servidor de Java EE. • Enumerar los tipos de recursos JNDI más comunes y sus convenciones de nomenclatura típicas. • Empaquetar una aplicación de Java EE simple e implementarla en JBoss EAP mediante Maven. Capítulo 3, Creación de Enterprise Java Beans Crear Enterprise Java Beans. • Convertir un POJO en un EJB. • Acceder a un EJB de manera local y remota. • Describir el ciclo de vida de los EJB. • Describir las transacciones gestionadas por contenedor y gestionadas por bean y delimitar cada una en un EJB. 416 JB183-EAP7.0-es-2-20180124 Revisión Red Hat Application Development I: Programming in Java EE Capítulo 4, Gestión de la persistencia Crear entidades de persistencia con validaciones. • Describir la API de persistencia. • Conservar datos en un almacén de datos mediante entidades. • Anotar beans para validar datos. • Crear una consulta mediante Java Persistence Query Language. Capítulo 5, Administración de relaciones entre entidades Definir y administrar relaciones entre entidades JPA. • Configurar relaciones de una entidad con otra entidad y de una entidad con varias entidades. • Describir relaciones de varias entidades con varias entidades. Capítulo 6, Creación de servicios REST Crear API REST mediante la especificación de JAX-RS. • Describir conceptos de servicios web y enumerar tipos de servicios web. • Crear un servicio REST mediante la especificación de JAX-RS. • Crear una aplicación cliente que pueda invocar las API REST de manera remota. Capítulo 7, Implementación de Contextos e Inyección de dependencia (CDI) Describir casos de uso típicos para utilizar la CDI e implementarla correctamente en una aplicación. • Describir la inyección de recursos, la inyección de dependencias y las diferencias entre ellas. • Aplicar alcances en beans de manera adecuada. Capítulo 8, Creación de aplicaciones de mensajería con JMS Crear clientes de mensajería que envían y reciben mensajes mediante la API de JMS. • Describir la API de JMS y nombrar a los objetos que se utilizan para enviar y recibir mensajes. • Describir los componentes que constituyen la API de JMS. • Crear un cliente JMS que produce y consume mensajes mediante la API de JMS. • Crear, comprimir e implementar un bean controlado por mensaje. Capítulo 9, Protección de aplicaciones Java EE Proteger una aplicación de Java EE con JAAS. • Describir la especificación de JAAS. • Configurar un dominio de seguridad en el servidor de aplicaciones de JBoss EAP. • Proteger una API REST con autenticación y autorización basada en roles. JB183-EAP7.0-es-2-20180124 417 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE Trabajo de laboratorio: Creación de una API mediante JAX-RS En esta revisión, creará una API REST mediante JAX-RS que inyecta un EJB con código marcador de posición para las operaciones de creación, lectura, actualización y eliminación (CRUD). Resultados Usted deberá ser capaz de realizar lo siguiente: • Crear un EJB que esté disponible para inyección. • Exponer una API REST que consuma y produzca datos JSON mediante anotaciones JAX-RS. • Inyectar el EJB que se creó para proporcionar lógica de negocio requerida por la API REST. Antes de comenzar Si no restablece workstation y services al final del último capítulo, guarde el trabajo que desea mantener de ejercicios anteriores de esas máquinas y hágalo ahora. Configure sus computadoras para este ejercicio iniciando sesión en workstation como student y ejecutando el siguiente comando: [student@workstation ~]$ lab jaxrs-review setup Instrucciones 1. Abra JBDS e importe la estructura del proyecto jaxrs-review. Revise el código existente y observe lo siguiente: • El objeto modelo Employee en el paquete com.redhat.training.model representa un registro de empleado básico con atributos para un id y un nombre. • La clase de EJB EmployeeLogger en el paquete com.redhat.training.util brinda un método de utilidades logAction(Employee employee, Operation operation) para registrar acciones que se toman en el registro de un empleado en un archivo de registro especial. 2. Cree un EJB para proporcionar métodos con código auxiliar (stub) que implementen las operaciones CRUD para un objeto Employee. En este ejercicio, estos métodos no usan una persistencia real. El EJB debe cumplir con los siguientes requisitos: • El nombre de la clase de EJB es EmployeeBean • El EJB EmployeeBean no tiene estado y está disponible para inyección. • El EJB EmployeeBean inyecta una instancia del EJB EmployeeLogger. • La clase de EJB EmployeeBean implementa cuatro métodos: ◦ createEmployee(Employee e) ◦ readEmployeeById(Long id) ◦ updateEmployee(Employee e) 418 JB183-EAP7.0-es-2-20180124 ◦ deleteEmployee(Employee e) • Cada uno de los métodos del EJB EmployeeBean es solo un código auxiliar (stub) que aún no contiene funcionalidad de persistencia real. En cambio, cada método usa la clase de utilidades EmployeeLogger inyectada para imprimir un mensaje que simule su operación. • El código auxiliar (stub) del método createEmployee invoca el método logAction en la clase EmployeeLogger y envía el objeto del empleado que se crea y un valor de operación de CREATE. • El código auxiliar (stub) del método readEmployeeById crea un nuevo objeto de empleado con el nombre Example Employee y el ID que se envió al método. La creación de este objeto debe suceder antes de que se invoque a EmployeeLogger. El método debe invocar el método logAction y enviar este objeto de empleado y un valor de operación de READ. Por último, el método debe devolver el objeto Employee que se creó. • El código auxiliar (stub) del método updateEmployee invoca el método logAction en la clase EmployeeLogger y envía el objeto del empleado que se actualiza y un valor de operación de UPDATE. • El código auxiliar (stub) del método deleteEmployee invoca el método logAction en la clase EmployeeLogger; además, envía el objeto del empleado que se elimina y un valor de operación de DELETE. 3. Active JAX-RS para permitir que los servicios REST se implementen como parte de la aplicación jaxrs-review. Debe acceder a todos los servicios REST que se implementan en la ruta /api. Esto significa que la URL completa para acceder a los servicios REST implementados en esta aplicación coincide con la siguiente, donde service_path es específico para cada servicio REST: http://localhost:8080/jaxrs-review/api/service_path 4. Cree una clase que implemente un servicio REST que inyecte el EJB, y que utilice sus métodos CRUD para brindar una API REST para administrar los datos del empleado. El servicio REST debe cumplir con los siguientes requisitos: • El nombre de la clase de servicio REST es EmployeeRestService • El servicio REST es un EJB sin estado. • La ruta relativa para este servicio REST es employees • La API REST produce y consume datos JSON. • La clase EmployeeRestService inyecta una instancia del EmployeeBean nombrado employeeBean, que utiliza para implementar la lógica de negocio de estos tres métodos API REST. • La API REST que el servicio EmployeeRestService proporciona incluye tres métodos, cada uno asociado con un método HTTP diferente. Estos se resume en la siguiente tabla: JB183-EAP7.0-es-2-20180124 419 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE Resumen del método EmployeeRestService Firma del método Método HTTP public Employee getEmployee(Long id) GET public void deleteEmployee(Long id) DELETE public Response saveEmployee(Employee employee) POST • El método getEmployee devuelve datos JSON para el objeto Employee que tiene un valor de ID específico. Este método invoca directamente el método readEmployeeById en la instancia EmployeeBean, enviando el ID del empleado y devolviendo el resultado. El ID del empleado se asigna automáticamente mediante un parámetro de ruta. Por ejemplo, la URL para obtener al empleado con un valor de ID de 1: http://localhost:8080/jaxrs-review/api/employees/1 • El método deleteEmployee no devuelve datos y utiliza el método deleteEmployee en EmployeeBean para eliminar al empleado con un valor de ID determinado. El ID del empleado se asigna automáticamente mediante un parámetro de ruta. El método debe usar el método readEmployeeById en el EJB EmployeeBean para recuperar el registro de empleado por su ID y, luego, invocar el método deleteEmployee para eliminar el registro del empleado recuperado. • El método saveEmployee puede crear un nuevo empleado o actualizar uno existente. Este método consume una representación JSON de un objeto Employee, que se asigna automáticamente a uno de los parámetros del método. Debe devolver un objeto javax.ws.rs.core.Response. Después de definir el método y las anotaciones necesarias, use el fragmento de código ubicado en saveEmployee.txt para implementar la lógica del método. • Observe que el método saveEmployee invoca createEmployee o updateEmployee, según si un registro Employee tiene o no un valor de ID. Si se especifica un ID, el método readEmployeeByID se utiliza para verificar que un empleado con ese ID ya exista; de lo contrario, se produce un error en el servidor. 5. Inicie JBoss EAP y utilice Maven para implementar la aplicación jaxrs-review. 6. Pruebe el servicio REST y todos sus métodos mediante el complemento (plug-in) de cliente REST de Firefox. • Especifique un encabezado personalizado con el nombre Content-Type y el valor application/json. • Use HTTP GET para probar el método getEmployee y especificar cualquier valor de ID. Revise el archivo EmployeeLog.txt o los registros de servidor JBoss EAP para asegurarse de que el servicio REST y EJB funcionen de la manera esperada. • Use HTTP DELETE para probar el método deleteEmployee y especificar cualquier valor de ID. Revise el archivo EmployeeLog.txt o los registros de servidor JBoss EAP para asegurarse de que el servicio REST y EJB funcionen de la manera esperada. 420 JB183-EAP7.0-es-2-20180124 • Use HTTP POST e incluya el contenido del archivo Employee.json para probar el método saveEmployee. Estos datos incluyen un ID, por lo que desencadena una operación de lectura de empleado y, luego, una actualización. Revise el archivo EmployeeLog.txt o los registros de servidor JBoss EAP para asegurarse de que el servicio REST y EJB funcionen de la manera esperada. • Elimine el valor de ID de los datos JSON y, luego, envíe otro HTTP POST para probar el método saveEmployee. Esto desencadena una operación de creación de empleado. Revise el archivo EmployeeLog.txt o los registros de servidor JBoss EAP para asegurarse de que el servicio REST y EJB funcionen de la manera esperada. Evaluación Como el usuario student en workstation, ejecute el script lab jaxrs-review con el argumento grade para confirmar que ha realizado este ejercicio correctamente. Corrija las fallas informadas y vuelva a ejecutar el script hasta obtener un resultado satisfactorio. [student@workstation ~]$ lab jaxrs-review grade Después de que la calificación se realiza correctamente, anule la implementación del proyecto, detenga el servidor EAP y cierre el proyecto en JBDS. Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 421 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE Solución En esta revisión, creará una API REST mediante JAX-RS que inyecta un EJB con código marcador de posición para las operaciones de creación, lectura, actualización y eliminación (CRUD). Resultados Usted deberá ser capaz de realizar lo siguiente: • Crear un EJB que esté disponible para inyección. • Exponer una API REST que consuma y produzca datos JSON mediante anotaciones JAX-RS. • Inyectar el EJB que se creó para proporcionar lógica de negocio requerida por la API REST. Antes de comenzar Si no restablece workstation y services al final del último capítulo, guarde el trabajo que desea mantener de ejercicios anteriores de esas máquinas y hágalo ahora. Configure sus computadoras para este ejercicio iniciando sesión en workstation como student y ejecutando el siguiente comando: [student@workstation ~]$ lab jaxrs-review setup Instrucciones 1. Abra JBDS e importe la estructura del proyecto jaxrs-review. Revise el código existente y observe lo siguiente: • El objeto modelo Employee en el paquete com.redhat.training.model representa un registro de empleado básico con atributos para un id y un nombre. • La clase de EJB EmployeeLogger en el paquete com.redhat.training.util brinda un método de utilidades logAction(Employee employee, Operation operation) para registrar acciones que se toman en el registro de un empleado en un archivo de registro especial. 2. Cree un EJB para proporcionar métodos con código auxiliar (stub) que implementen las operaciones CRUD para un objeto Employee. En este ejercicio, estos métodos no usan una persistencia real. El EJB debe cumplir con los siguientes requisitos: • El nombre de la clase de EJB es EmployeeBean • El EJB EmployeeBean no tiene estado y está disponible para inyección. • El EJB EmployeeBean inyecta una instancia del EJB EmployeeLogger. • La clase de EJB EmployeeBean implementa cuatro métodos: ◦ createEmployee(Employee e) ◦ readEmployeeById(Long id) ◦ updateEmployee(Employee e) ◦ deleteEmployee(Employee e) • Cada uno de los métodos del EJB EmployeeBean es solo un código auxiliar (stub) que aún no contiene funcionalidad de persistencia real. En cambio, cada método usa la 422 JB183-EAP7.0-es-2-20180124 Solución clase de utilidades EmployeeLogger inyectada para imprimir un mensaje que simule su operación. • El código auxiliar (stub) del método createEmployee invoca el método logAction en la clase EmployeeLogger y envía el objeto del empleado que se crea y un valor de operación de CREATE. • El código auxiliar (stub) del método readEmployeeById crea un nuevo objeto de empleado con el nombre Example Employee y el ID que se envió al método. La creación de este objeto debe suceder antes de que se invoque a EmployeeLogger. El método debe invocar el método logAction y enviar este objeto de empleado y un valor de operación de READ. Por último, el método debe devolver el objeto Employee que se creó. • El código auxiliar (stub) del método updateEmployee invoca el método logAction en la clase EmployeeLogger y envía el objeto del empleado que se actualiza y un valor de operación de UPDATE. • El código auxiliar (stub) del método deleteEmployee invoca el método logAction en la clase EmployeeLogger; además, envía el objeto del empleado que se elimina y un valor de operación de DELETE. 3. Active JAX-RS para permitir que los servicios REST se implementen como parte de la aplicación jaxrs-review. Debe acceder a todos los servicios REST que se implementan en la ruta /api. Esto significa que la URL completa para acceder a los servicios REST implementados en esta aplicación coincide con la siguiente, donde service_path es específico para cada servicio REST: http://localhost:8080/jaxrs-review/api/service_path 4. Cree una clase que implemente un servicio REST que inyecte el EJB, y que utilice sus métodos CRUD para brindar una API REST para administrar los datos del empleado. El servicio REST debe cumplir con los siguientes requisitos: • El nombre de la clase de servicio REST es EmployeeRestService • El servicio REST es un EJB sin estado. • La ruta relativa para este servicio REST es employees • La API REST produce y consume datos JSON. • La clase EmployeeRestService inyecta una instancia del EmployeeBean nombrado employeeBean, que utiliza para implementar la lógica de negocio de estos tres métodos API REST. • La API REST que el servicio EmployeeRestService proporciona incluye tres métodos, cada uno asociado con un método HTTP diferente. Estos se resume en la siguiente tabla: Resumen del método EmployeeRestService Firma del método Método HTTP public Employee getEmployee(Long id) GET JB183-EAP7.0-es-2-20180124 423 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE Firma del método Método HTTP public void deleteEmployee(Long id) DELETE public Response saveEmployee(Employee employee) POST • El método getEmployee devuelve datos JSON para el objeto Employee que tiene un valor de ID específico. Este método invoca directamente el método readEmployeeById en la instancia EmployeeBean, enviando el ID del empleado y devolviendo el resultado. El ID del empleado se asigna automáticamente mediante un parámetro de ruta. Por ejemplo, la URL para obtener al empleado con un valor de ID de 1: http://localhost:8080/jaxrs-review/api/employees/1 • El método deleteEmployee no devuelve datos y utiliza el método deleteEmployee en EmployeeBean para eliminar al empleado con un valor de ID determinado. El ID del empleado se asigna automáticamente mediante un parámetro de ruta. El método debe usar el método readEmployeeById en el EJB EmployeeBean para recuperar el registro de empleado por su ID y, luego, invocar el método deleteEmployee para eliminar el registro del empleado recuperado. • El método saveEmployee puede crear un nuevo empleado o actualizar uno existente. Este método consume una representación JSON de un objeto Employee, que se asigna automáticamente a uno de los parámetros del método. Debe devolver un objeto javax.ws.rs.core.Response. Después de definir el método y las anotaciones necesarias, use el fragmento de código ubicado en saveEmployee.txt para implementar la lógica del método. • Observe que el método saveEmployee invoca createEmployee o updateEmployee, según si un registro Employee tiene o no un valor de ID. Si se especifica un ID, el método readEmployeeByID se utiliza para verificar que un empleado con ese ID ya exista; de lo contrario, se produce un error en el servidor. 5. Inicie JBoss EAP y utilice Maven para implementar la aplicación jaxrs-review. 6. Pruebe el servicio REST y todos sus métodos mediante el complemento (plug-in) de cliente REST de Firefox. • Especifique un encabezado personalizado con el nombre Content-Type y el valor application/json. • Use HTTP GET para probar el método getEmployee y especificar cualquier valor de ID. Revise el archivo EmployeeLog.txt o los registros de servidor JBoss EAP para asegurarse de que el servicio REST y EJB funcionen de la manera esperada. • Use HTTP DELETE para probar el método deleteEmployee y especificar cualquier valor de ID. Revise el archivo EmployeeLog.txt o los registros de servidor JBoss EAP para asegurarse de que el servicio REST y EJB funcionen de la manera esperada. • Use HTTP POST e incluya el contenido del archivo Employee.json para probar el método saveEmployee. Estos datos incluyen un ID, por lo que desencadena una operación de lectura de empleado y, luego, una actualización. Revise el archivo 424 JB183-EAP7.0-es-2-20180124 Solución EmployeeLog.txt o los registros de servidor JBoss EAP para asegurarse de que el servicio REST y EJB funcionen de la manera esperada. • Elimine el valor de ID de los datos JSON y, luego, envíe otro HTTP POST para probar el método saveEmployee. Esto desencadena una operación de creación de empleado. Revise el archivo EmployeeLog.txt o los registros de servidor JBoss EAP para asegurarse de que el servicio REST y EJB funcionen de la manera esperada. Pasos 1. Abra JBDS e importe el proyecto de Maven. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta jaxrs-review y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Cree un EJB para proporcionar métodos con código auxiliar (stub) para las operaciones CRUD de un objeto Employee. 2.1. Haga clic con el botón derecho en com.redhat.training.ejb y haga clic en New (Nueva) > Class (Clase). 2.2. En el campo Name (Nombre), ingrese EmployeeBean. Haga clic en Finish (Finalizar). 2.3. Convierta la clase EmployeeBean en un EJB sin estado disponible para inyección; para ello, agregue la anotación @Stateless en el nivel de la clase: import javax.ejb.Stateless; @Stateless public class EmployeeBean { 2.4. Inyecte una instancia del EJB EmployeeLogger para utilizar en los códigos auxiliares (stubs) del método: import javax.ejb.Stateless; import javax.inject.Inject; import com.redhat.training.util.EmployeeLogger; JB183-EAP7.0-es-2-20180124 425 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE @Stateless public class EmployeeBean { @Inject private EmployeeLogger logger; 2.5. Implemente el código auxiliar (stub) del método createEmployee(Employee e), que invoca el método logAction con un valor de operación de Create: import import import import import javax.ejb.Stateless; javax.inject.Inject; com.redhat.training.util.EmployeeLogger; com.redhat.training.model.Employee; com.redhat.training.util.EmployeeLogger.Operation; @Stateless public class EmployeeBean { @Inject private EmployeeLogger logger; public void createEmployee(Employee e) { logger.logAction(e, Operation.Create); } 2.6. Implemente el código auxiliar (stub) del método readEmployeeById(Long id), que invoca el método logAction con un valor de operación de Read: ... @Stateless public class EmployeeBean { @Inject private EmployeeLogger logger; public void createEmployee(Employee e) { logger.logAction(e, Operation.Create); } public Employee readEmployeeById(Long id) { Employee sample = new Employee(); sample.setId(id); sample.setName("Example Employee"); logger.logAction(sample, Operation.Read); return sample; } } 2.7. Implemente el código auxiliar (stub) del método updateEmployee(Employee e), que invoca el método logAction con un valor de operación de Update: ... @Stateless public class EmployeeBean { @Inject 426 JB183-EAP7.0-es-2-20180124 Solución private EmployeeLogger logger; public void createEmployee(Employee e) { logger.logAction(e, Operation.Create); } public Employee readEmployeeById(Long id) { Employee sample = new Employee(); sample.setId(id); sample.setName("Example Employee"); logger.logAction(sample, Operation.Read); return sample; } } public void updateEmployee(Employee e) { logger.logAction(e, Operation.Update); } 2.8. Implemente el código auxiliar (stub) del método deleteEmployee(Employee e), que invoca el método logAction con un valor de operación de Delete: ... @Stateless public class EmployeeBean { @Inject private EmployeeLogger logger; public void createEmployee(Employee e) { logger.logAction(e, Operation.Create); } public Employee readEmployeeById(Long id) { Employee sample = new Employee(); sample.setId(id); sample.setName("Example Employee"); logger.logAction(sample, Operation.Read); return sample; } } public void updateEmployee(Employee e) { logger.logAction(e, Operation.Update); } public void deleteEmployee(Employee e) { logger.logAction(e, Operation.Delete); } 2.9. Guarde los cambios en el archivo con Ctrl+S. 3. Active JAX-RS al crear una nueva clase de aplicación y definir la ruta de la aplicación como /api. 3.1. Haga clic con el botón derecho en com.redhat.training.rest y haga clic en New (Nueva) > Class (Clase). 3.2. En el campo Name (Nombre) ingrese Service. Haga clic en Finish (Finalizar). JB183-EAP7.0-es-2-20180124 427 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE 3.3. Haga que la nueva clase Service amplíe la superclase javax.ws.rs.core.Application: import javax.ws.rs.core.Application; public class Service extends Application { } 3.4. Defina la ruta de la aplicación como /api mediante la anotación en el nivel de la clase @ApplicationPath: import javax.ws.rs.core.Application; import javax.ws.rs.ApplicationPath; @ApplicationPath("/api") public class Service extends Application { } 3.5. Guarde los cambios en el archivo con Ctrl+S. 4. Cree el nuevo servicio REST que inyecta el EJB y proporciona tres métodos API. 4.1. Haga clic con el botón derecho en com.redhat.training.rest y haga clic en New (Nueva) > Class (Clase). 4.2. En el campo Name (Nombre) ingrese EmployeeRestService. Haga clic en Finish (Finalizar). 4.3. Convierta la clase EmployeeRestService en un EJB sin estado: import javax.ejb.Stateless; @Stateless public class EmployeeRestService { 4.4. Agregue las anotaciones necesarias en el nivel de la clase para definir la ruta relativa de este servicio en employees e indique que produce y consume JSON: import import import import import javax.ejb.Stateless; javax.ws.rs.Consumes; javax.ws.rs.Produces; javax.ws.rs.Path; javax.ws.rs.core.MediaType; @Stateless @Path("employees") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class EmployeeRestService { 4.5. Inyecte EmployeeBean para poder ser utilizado por los métodos REST: 428 JB183-EAP7.0-es-2-20180124 Solución import import import import import import import javax.ejb.Stateless; javax.ws.rs.Consumes; javax.ws.rs.Path; javax.ws.rs.Produces; javax.ws.rs.core.MediaType; javax.inject.Inject; com.redhat.training.ejb.EmployeeBean; @Stateless @Path("employees") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class EmployeeRestService { @Inject private EmployeeBean employeeBean; 4.6. Implemente el método getEmployee(Long id), que está asignado al método HTTP GET y utiliza un parámetro de ruta para asignar el valor del ID. El método invoca el método readEmployeeById en el EJB EmployeeBean directamente: import import import import import import import import import import javax.ejb.Stateless; javax.ws.rs.Consumes; javax.ws.rs.Path; javax.ws.rs.Produces; javax.ws.rs.core.MediaType; javax.inject.Inject; com.redhat.training.ejb.EmployeeBean; com.redhat.training.model.Employee; javax.ws.rs.GET; javax.ws.rs.PathParam; @Stateless @Path("employees") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class EmployeeRestService { @Inject private EmployeeBean employeeBean; @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Employee getEmployee(@PathParam("id") Long id) { return employeeBean.readEmployeeById(id); } 4.7. Implemente el método deleteEmployee(Long id), que está asignado al método HTTP DELETE y utiliza un parámetro de ruta para asignar el valor del ID. El método invoca el método deleteEmployee en el EJB EmployeeBean directamente: import import import import import import javax.ejb.Stateless; javax.ws.rs.Consumes; javax.ws.rs.Path; javax.ws.rs.Produces; javax.ws.rs.core.MediaType; javax.inject.Inject; JB183-EAP7.0-es-2-20180124 429 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE import import import import import com.redhat.training.ejb.EmployeeBean; com.redhat.training.model.Employee; javax.ws.rs.GET; javax.ws.rs.PathParam; javax.ws.rs.DELETE; @Stateless @Path("employees") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class EmployeeRestService { @Inject private EmployeeBean employeeBean; @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Employee getEmployee(@PathParam("id") Long id) { return employeeBean.readEmployeeById(id); } @DELETE @Path("{id}") public void deletePerson(@PathParam("id") Long id) { Employee toBeDeleted = employeeBean.readEmployeeById(id); employeeBean.deleteEmployee(toBeDeleted); } 4.8. Implemente la firma del método saveEmployee(Employee employee), que está asignada al método HTTP POST. Este método consume datos JSON del registro de un empleado y devuelve un objeto Response. import import import import import import import import import import import import import javax.ejb.Stateless; javax.ws.rs.Consumes; javax.ws.rs.Path; javax.ws.rs.Produces; javax.ws.rs.core.MediaType; javax.inject.Inject; com.redhat.training.ejb.EmployeeBean; com.redhat.training.model.Employee; javax.ws.rs.GET; javax.ws.rs.PathParam; javax.ws.rs.DELETE; javax.ws.rs.POST; javax.ws.rs.core.Response; @Stateless @Path("employees") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class EmployeeRestService { @Inject private EmployeeBean employeeBean; @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Employee getEmployee(@PathParam("id") Long id) { 430 JB183-EAP7.0-es-2-20180124 Solución return employeeBean.readEmployeeById(id); } @DELETE @Path("{id}") public void deletePerson(@PathParam("id") Long id) { Employee toBeDeleted = employeeBean.readEmployeeById(id); employeeBean.deleteEmployee(toBeDeleted); } @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response savePerson(Employee employee) { } 4.9. Copie el contenido de /home/student/JB183/labs/jaxrs-review/ saveEmployee.txt y péguelo en el contenido del método. Agregue una importación para ResponseBuilder: import javax.ws.rs.core.Response.ResponseBuilder; ...Imports omitted... @Stateless @Path("employees") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class EmployeeRestService { @Inject private EmployeeBean employeeBean; ...Methods omitted... @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response savePerson(Employee employee) { ResponseBuilder builder; if (employee.getId() == null) { Employee newEmployee = new Employee(); newEmployee.setName(employee.getName()); employeeBean.createEmployee(newEmployee); builder = Response.ok(); } else { Employee employeeToUpdate = employeeBean.readEmployeeById(employee.getId()); if (employeeToUpdate == null) { builder = Response.serverError(); } else { employeeBean.updateEmployee(employee); builder = Response.ok(); } } JB183-EAP7.0-es-2-20180124 431 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE return builder.build(); } 4.10.Guarde los cambios en el archivo con Ctrl+S. 5. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde de inicio para iniciar el servidor. 6. Implemente la aplicación jaxrs-review mediante los siguientes comandos en la ventana del terminal: [student@workstation ~]$ cd /home/student/JB183/labs/jaxrs-review [student@workstation jaxrs-review]$ mvn wildfly:deploy 7. Lea un registro de Employee mediante el complemento (plug-in) de cliente REST de Firefox al realizar una solicitud GET a http://localhost:8080/jaxrs-review/api/ employees 7.1. Inicie Firefox en la máquina virtual workstation y haga clic en el icono del complemento (plug-in) de cliente REST en la barra de herramientas del explorador. Figura Error.1: Complemento (plug-in) de cliente REST de Firefox 7.2. En la barra de herramientas superior, haga clic en Headers (Encabezados) y en Custom Header (Encabezado personalizado) para agregar un nuevo encabezado personalizado a la solicitud. 7.3. Introduzca la siguiente información en el cuadro de diálogo del encabezado personalizado: • Name (Nombre): Content-Type • Value (Valor): application/json 432 JB183-EAP7.0-es-2-20180124 Solución Figura Error.2: Creación de un encabezado de solicitud personalizado en el cliente REST Haga clic en Okay (Aceptar). 7.4. Seleccione GET como el Method (Método). En el formulario de URL, introduzca http://localhost:8080/jaxrs-review/api/employees/1. 7.5. Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 200 OK. 7.6. Revise /home/student/JB183/labs/jaxrs-review/EmployeeLog.txt y asegúrese de ver el siguiente mensaje: Read Employee: Employee [id=1, name=Example Employee] 8. Elimine un registro de Employee mediante el complemento (plug-in) de cliente REST de Firefox al realizar una solicitud DELETE a http://localhost:8080/jaxrs-review/ api/employees/1. 8.1. Seleccione DELETE como el Method (Método). En el formulario de URL, introduzca http://localhost:8080/jaxrs-review/api/employees/1. 8.2. Haga clic en Send (Enviar). 8.3. Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 204 No Content (204 Sin contenido). Esto es lo previsto ya que el método deleteEmployee tiene un tipo de devolución void (nulo), por lo que no se devuelve contenido al cliente REST. 8.4. Revise /home/student/JB183/labs/jaxrs-review/EmployeeLog.txt y asegúrese de ver el siguiente mensaje: Read Employee: Employee [id=1, name=Example Employee] Delete Employee: Employee [id=1, name=Example Employee] 9. Actualice un registro Employee mediante el complemento (plug-in) de cliente REST de Firefox al realizar una solicitud POST a http://localhost:8080/jaxrs-review/api/ JB183-EAP7.0-es-2-20180124 433 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE employees, enviando datos JSON del registro de un empleado con un ID en el cuerpo de la solicitud. 9.1. Seleccione POST como el Method (Método). En el formulario de URL, introduzca http://localhost:8080/jaxrs-review/api/employees/. 9.2. En la sección Body (Cuerpo) de la solicitud, agregue la siguiente representación JSON de una entidad Employee: {"id":1,"name":"Example Employee2"} Haga clic en Send (Enviar). 9.3. Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 200 OK. 9.4. Revise /home/student/JB183/labs/jaxrs-review/EmployeeLog.txt y asegúrese de ver el siguiente mensaje: Read Employee: Employee [id=1, name=Example Employee2] Update Employee: Employee [id=1, name=Example Employee2] 10. Cree un registro Employee mediante el complemento (plug-in) de cliente REST de Firefox al realizar una solicitud POST a http://localhost:8080/jaxrs-review/api/ employees, enviando datos JSON del registro de un empleado sin un ID en el cuerpo de la solicitud. 10.1.Seleccione POST como el Method (Método). En el formulario de URL, introduzca http://localhost:8080/jaxrs-review/api/employees/. 10.2.En la sección Body (Cuerpo) de la solicitud, agregue la siguiente representación JSON de una entidad Employee: {"name":"Example Employee3"} Haga clic en Send (Enviar). 10.3.Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 200 OK. 10.4.Revise /home/student/JB183/labs/jaxrs-review/EmployeeLog.txt y asegúrese de ver el siguiente mensaje: Create Employee: Employee [id=null, name=Example Employee3] Evaluación Como el usuario student en workstation, ejecute el script lab jaxrs-review con el argumento grade para confirmar que ha realizado este ejercicio correctamente. Corrija las fallas informadas y vuelva a ejecutar el script hasta obtener un resultado satisfactorio. [student@workstation ~]$ lab jaxrs-review grade 434 JB183-EAP7.0-es-2-20180124 Solución Después de que la calificación se realiza correctamente, anule la implementación del proyecto, detenga el servidor EAP y cierre el proyecto en JBDS. Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 435 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE Trabajo de laboratorio: Persistencia de datos con JPA En esta revisión, actualizará la API REST compilada en la revisión anterior para incluir persistencia. Resultados Usted deberá ser capaz de realizar lo siguiente: • Use JPA para asignar una entidad a una tabla de base de datos. • Use JPA para asignar relaciones entre entidades. • Use una consulta nombrada para recuperar registros de la base de datos. Antes de comenzar Prepare sus computadoras para este ejercicio iniciando sesión en workstation como student y ejecutando el siguiente comando: [student@workstation ~]$ lab jpa-review setup Instrucciones 1. Abra JBDS e importe la estructura del proyecto jpa-review. Revise el código existente y observe lo siguiente: • El archivo src/main/resources/META-INF/persistence.xml define un contexto de persistencia que se conecta a la fuente de datos MySQL definida en el servidor EAP. • Se agregaron dos nuevos objetos modelo en el paquete com.redhat.training.model: Manager (Gerente) y Department (Departamento). Cada Employee (Empleado) tiene un Department (Departamento) y cada Department (Departamento) tiene un Manager (Gerente). 2. Cree una clase de utilidades en el paquete com.redhat.training.util que produce instancias de EntityManager que utilizan el contexto de persistencia predeterminado. 3. Actualice la entidad Employee para que esté administrada por JPA, y asigne las relaciones entre las entidades Employee, Department y Manager mediante anotaciones de JPA. Asegúrese de cumplir con los siguientes requisitos: • El campo id es el identificador único de cada registro y se genera automáticamente por la base de datos cuando se persiste un nuevo registro. • La tabla Employee en la base de datos usa la columna departmentID para almacenar la clave externa en la tabla Department. • La tabla Manager en la base de datos usa la columna departmentID para almacenar la clave externa en la tabla Department. • La entidad Department asigna sus relaciones con Employee y Manager como el objeto secundario en la relación. 436 JB183-EAP7.0-es-2-20180124 • La entidad Department captura de manera diligente la lista de instancias de Employee a la que está relacionada. • La entidad Employee incluye una consulta nombrada JPQL, denominada findAllForManager, que toma el ID del registro de un gerente como su parámetro. Esta consulta extrae la lista de empleados que se reportan a ese gerente. Esto requiere unirse al registro department y, luego, unirse al registro manager. Sugerencia: La instrucción JPQL que verifica el ID de administrador de un empleado es employee.department.manager.id • La consulta findAllForManager devuelve objetos Employee y utiliza un parámetro nombrado denominado managerId para representar el ID de administrador. 4. Actualice EmployeeBean para admitir la persistencia mediante EntityManager. Asegúrese de cumplir con los siguientes requisitos: • Los métodos EJB CRUD asignan los siguientes métodos de administrador de entidades: ◦ createEmployee: persist(Employee e) ◦ readEmployeeById: find(Class, Object) ◦ updateEmployee: merge(Employee e) ◦ deleteEmployee: remove(Employee e) • El método findAllForManager utiliza la consulta nombrada findAllForManager correspondiente. Además, el parámetro managerID de la consulta está definido con el ID de manager que se envió al método. 5. Actualice EmployeeRestService para admitir tres nuevos métodos y asegúrese de que cumplan con los siguientes requisitos: • getEmployeesForManager(Manager m): ◦ Se asigna a las solicitudes HTTP GET. ◦ Usa la ruta relativa /getByManager/{managerId}. ◦ managerId es un parámetro de ruta asignado al parámetro de método. ◦ Produce datos XML. ◦ Usa el método findById en ManagerBean para buscar al gerente por el valor de su ID. ◦ Toma el objeto de gerente encontrado y lo envía al método findAllForManager en EmployeeBean y devuelve el resultado. • assignEmployee(Long employeeId, Long departmentId): ◦ Se asigna a las solicitudes HTTP POST. ◦ Usa la ruta relativa /assignEmployee/{employeeId}/{departmentId}. ◦ Tanto employeeId como departmentId son parámetros de ruta que se asignan a los parámetros de método. JB183-EAP7.0-es-2-20180124 437 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE ◦ Usa EmployeeBean para buscar al empleado por ID y, luego, usa DepartmentBean para buscar al departamento por ID. Por último, actualiza el registro del departamento en el objeto del empleado y, luego, invoca el método updateEmployee en EmployeeBean para persistir los cambios. • assignManager(Long managerId, Long departmentId) ◦ Se asigna a las solicitudes HTTP POST. ◦ Usa la ruta relativa /assignManager/{managerId}/{departmentId}. ◦ Tanto managerId como departmentId son parámetros de ruta que se asignan a los parámetros de método. ◦ Usa ManagerBean para buscar al gerente por ID y, luego, usa DepartmentBean para buscar al departamento por ID. Por último, actualiza el registro del departamento en el objeto del gerente y, luego, invoca el método updateManager en ManagerBean para persistir los cambios. 6. Inicie JBoss EAP y utilice Maven para implementar la aplicación jpa-review. 7. Pruebe el servicio REST y los tres nuevos métodos mediante el complemento (plug-in) de cliente REST de Firefox. Si desea restablecer la base de datos durante las pruebas, ejecute el script /home/student/ JB183/labs/jpa-review/reset-database.sh proporcionado. • Especifique un encabezado personalizado con el nombre Content-Type y el valor application/json. • Use HTTP GET para enviar una solicitud al extremo http://localhost:8080/ jpa-review/api/employees/getByManager/1 para probar el método getEmployeesForManager y recuperar los empleados para el gerente con un valor de id de 1. Revise ResponseBody para ver los datos XML para los empleados en el departamento administrado por Bob, que es el departamento de Sales (Ventas): <collection> <employee> <department> <id>1</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Sales</name> </department> <id>1</id> <name>William</name> </employee> <employee> <department> <id>1</id> <manager> <id>1</id> <name>Bob</name> </manager> 438 JB183-EAP7.0-es-2-20180124 <name>Sales</name> </department> <id>2</id> <name>Rose</name> </employee> <employee> <department> <id>1</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Sales</name> </department> <id>3</id> <name>Pat</name> </employee> </collection> • Use HTTP POST para enviar una solicitud al extremo http://localhost:8080/jpareview/api/employees/assignEmployee/1/2 para asignar al empleado con un valor de ID de 1 al departamento con un valor de ID de 2. • Use HTTP POST para enviar una solicitud a http://localhost:8080/jpareview/api/employees/assignManager/2/1 para asignar un nuevo Manager al departamento de ventas. • Use HTTP POST para enviar una solicitud a http://localhost:8080/jpa-review/ api/employees/assignManager/1/2 para asignar a Bob a un nuevo departamento con un valor de ID de 2. • Use HTTP GET para enviar una solicitud al extremo http://localhost:8080/ jpa-review/api/employees/getByManager/1 para probar el método getEmployeesForManager nuevamente. Revise ResponseBody para ver los datos XML para los empleados en el departamento administrado por Bob, que ahora es el departamento de Marketing. Esto debe incluir al empleado recientemente asignado y obtener un total de 4 empleados: <collection> <employee> <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Marketing</name> </department> <id>1</id> <name>William</name> </employee> <employee> <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> JB183-EAP7.0-es-2-20180124 439 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE <name>Marketing</name> </department> <id>4</id> <name>Rodney</name> </employee> <employee> <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Marketing</name> </department> <id>5</id> <name>Kim</name> </employee> <employee> <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Marketing</name> </department> <id>6</id> <name>Tom</name> </employee> </collection> Evaluación Como el usuario student en workstation, ejecute el script lab jpa-review con el argumento grade para confirmar que ha realizado este ejercicio correctamente. Corrija las fallas informadas y vuelva a ejecutar el script hasta obtener un resultado satisfactorio. [student@workstation ~]$ lab jpa-review grade Después de que la calificación se realiza correctamente, detenga el servidor EAP y cierre el proyecto en JBDS. Esto concluye el trabajo de laboratorio. 440 JB183-EAP7.0-es-2-20180124 Solución Solución En esta revisión, actualizará la API REST compilada en la revisión anterior para incluir persistencia. Resultados Usted deberá ser capaz de realizar lo siguiente: • Use JPA para asignar una entidad a una tabla de base de datos. • Use JPA para asignar relaciones entre entidades. • Use una consulta nombrada para recuperar registros de la base de datos. Antes de comenzar Prepare sus computadoras para este ejercicio iniciando sesión en workstation como student y ejecutando el siguiente comando: [student@workstation ~]$ lab jpa-review setup Instrucciones 1. Abra JBDS e importe la estructura del proyecto jpa-review. Revise el código existente y observe lo siguiente: • El archivo src/main/resources/META-INF/persistence.xml define un contexto de persistencia que se conecta a la fuente de datos MySQL definida en el servidor EAP. • Se agregaron dos nuevos objetos modelo en el paquete com.redhat.training.model: Manager (Gerente) y Department (Departamento). Cada Employee (Empleado) tiene un Department (Departamento) y cada Department (Departamento) tiene un Manager (Gerente). 2. Cree una clase de utilidades en el paquete com.redhat.training.util que produce instancias de EntityManager que utilizan el contexto de persistencia predeterminado. 3. Actualice la entidad Employee para que esté administrada por JPA, y asigne las relaciones entre las entidades Employee, Department y Manager mediante anotaciones de JPA. Asegúrese de cumplir con los siguientes requisitos: • El campo id es el identificador único de cada registro y se genera automáticamente por la base de datos cuando se persiste un nuevo registro. • La tabla Employee en la base de datos usa la columna departmentID para almacenar la clave externa en la tabla Department. • La tabla Manager en la base de datos usa la columna departmentID para almacenar la clave externa en la tabla Department. • La entidad Department asigna sus relaciones con Employee y Manager como el objeto secundario en la relación. • La entidad Department captura de manera diligente la lista de instancias de Employee a la que está relacionada. • La entidad Employee incluye una consulta nombrada JPQL, denominada findAllForManager, que toma el ID del registro de un gerente como su parámetro. JB183-EAP7.0-es-2-20180124 441 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE Esta consulta extrae la lista de empleados que se reportan a ese gerente. Esto requiere unirse al registro department y, luego, unirse al registro manager. Sugerencia: La instrucción JPQL que verifica el ID de administrador de un empleado es employee.department.manager.id • La consulta findAllForManager devuelve objetos Employee y utiliza un parámetro nombrado denominado managerId para representar el ID de administrador. 4. Actualice EmployeeBean para admitir la persistencia mediante EntityManager. Asegúrese de cumplir con los siguientes requisitos: • Los métodos EJB CRUD asignan los siguientes métodos de administrador de entidades: ◦ createEmployee: persist(Employee e) ◦ readEmployeeById: find(Class, Object) ◦ updateEmployee: merge(Employee e) ◦ deleteEmployee: remove(Employee e) • El método findAllForManager utiliza la consulta nombrada findAllForManager correspondiente. Además, el parámetro managerID de la consulta está definido con el ID de manager que se envió al método. 5. Actualice EmployeeRestService para admitir tres nuevos métodos y asegúrese de que cumplan con los siguientes requisitos: • getEmployeesForManager(Manager m): ◦ Se asigna a las solicitudes HTTP GET. ◦ Usa la ruta relativa /getByManager/{managerId}. ◦ managerId es un parámetro de ruta asignado al parámetro de método. ◦ Produce datos XML. ◦ Usa el método findById en ManagerBean para buscar al gerente por el valor de su ID. ◦ Toma el objeto de gerente encontrado y lo envía al método findAllForManager en EmployeeBean y devuelve el resultado. • assignEmployee(Long employeeId, Long departmentId): ◦ Se asigna a las solicitudes HTTP POST. ◦ Usa la ruta relativa /assignEmployee/{employeeId}/{departmentId}. ◦ Tanto employeeId como departmentId son parámetros de ruta que se asignan a los parámetros de método. ◦ Usa EmployeeBean para buscar al empleado por ID y, luego, usa DepartmentBean para buscar al departamento por ID. Por último, actualiza el registro del departamento en el objeto del empleado y, luego, invoca el método updateEmployee en EmployeeBean para persistir los cambios. 442 JB183-EAP7.0-es-2-20180124 Solución • assignManager(Long managerId, Long departmentId) ◦ Se asigna a las solicitudes HTTP POST. ◦ Usa la ruta relativa /assignManager/{managerId}/{departmentId}. ◦ Tanto managerId como departmentId son parámetros de ruta que se asignan a los parámetros de método. ◦ Usa ManagerBean para buscar al gerente por ID y, luego, usa DepartmentBean para buscar al departamento por ID. Por último, actualiza el registro del departamento en el objeto del gerente y, luego, invoca el método updateManager en ManagerBean para persistir los cambios. 6. Inicie JBoss EAP y utilice Maven para implementar la aplicación jpa-review. 7. Pruebe el servicio REST y los tres nuevos métodos mediante el complemento (plug-in) de cliente REST de Firefox. Si desea restablecer la base de datos durante las pruebas, ejecute el script /home/student/ JB183/labs/jpa-review/reset-database.sh proporcionado. • Especifique un encabezado personalizado con el nombre Content-Type y el valor application/json. • Use HTTP GET para enviar una solicitud al extremo http://localhost:8080/ jpa-review/api/employees/getByManager/1 para probar el método getEmployeesForManager y recuperar los empleados para el gerente con un valor de id de 1. Revise ResponseBody para ver los datos XML para los empleados en el departamento administrado por Bob, que es el departamento de Sales (Ventas): <collection> <employee> <department> <id>1</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Sales</name> </department> <id>1</id> <name>William</name> </employee> <employee> <department> <id>1</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Sales</name> </department> <id>2</id> <name>Rose</name> </employee> <employee> JB183-EAP7.0-es-2-20180124 443 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE <department> <id>1</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Sales</name> </department> <id>3</id> <name>Pat</name> </employee> </collection> • Use HTTP POST para enviar una solicitud al extremo http://localhost:8080/jpareview/api/employees/assignEmployee/1/2 para asignar al empleado con un valor de ID de 1 al departamento con un valor de ID de 2. • Use HTTP POST para enviar una solicitud a http://localhost:8080/jpareview/api/employees/assignManager/2/1 para asignar un nuevo Manager al departamento de ventas. • Use HTTP POST para enviar una solicitud a http://localhost:8080/jpa-review/ api/employees/assignManager/1/2 para asignar a Bob a un nuevo departamento con un valor de ID de 2. • Use HTTP GET para enviar una solicitud al extremo http://localhost:8080/ jpa-review/api/employees/getByManager/1 para probar el método getEmployeesForManager nuevamente. Revise ResponseBody para ver los datos XML para los empleados en el departamento administrado por Bob, que ahora es el departamento de Marketing. Esto debe incluir al empleado recientemente asignado y obtener un total de 4 empleados: <collection> <employee> <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Marketing</name> </department> <id>1</id> <name>William</name> </employee> <employee> <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Marketing</name> </department> <id>4</id> <name>Rodney</name> </employee> <employee> 444 JB183-EAP7.0-es-2-20180124 Solución <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Marketing</name> </department> <id>5</id> <name>Kim</name> </employee> <employee> <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Marketing</name> </department> <id>6</id> <name>Tom</name> </employee> </collection> Pasos 1. Abra JBDS e importe el proyecto de Maven. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta jpa-review y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Agregue una clase de utilidades que produzca instancias de EntityManager al inyectar el contexto de persistencia predeterminado. 2.1. Expanda el ítem jpa-review en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en jpa-review > Java Resources (Recursos de Java). Haga clic con el botón derecho en com.redhat.training.util y haga clic en New (Nueva) > Class (Clase). 2.2. En el campo Name (Nombre) ingrese Resources. Haga clic en Finish (Finalizar). JB183-EAP7.0-es-2-20180124 445 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE 2.3. Convierta a la clase Resources en un productor para las instancias EntityManager mediante la anotación @Produces, y vincule la entidad manager al contexto de persistencia predeterminado mediante la anotación @PersistenceContext: import javax.enterprise.inject.Produces; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; public class Resources { @Produces @PersistenceContext private EntityManager em; } 2.4. Guarde los cambios en el archivo con Ctrl+S. 3. Actualice la clase del modelo Employee para que sea una entidad administrada por JPA. 3.1. Abra la clase Employee; para ello, expanda el ítem jpa-review en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS y, luego, haga clic en jpa-review > Java Resources > src/main/java > com.redhat.training.model para expandirlo. Haga doble clic en el archivo Employee.java. Agregue la anotación @Entity para marcar esta clase como una entidad administrada por JPA: ... //TODO Make this class an Entity @Entity //TODO Add a named query to find all employees for a given manager public class Employee { ... 3.2. Configure el campo id para que sea el identificador único para la clase Employee mediante la anotación @Id. Márquelo como un valor generado mediante la anotación @GeneratedValue con un valor de IDENTITY: ... //TODO Make this class an Entity @Entity //TODO Add a named query to find all employees for a given manager public class Employee { //TODO mark this as the Id field @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; 3.3. Guarde los cambios en el archivo con Ctrl+S. 4. 446 Asigne las relaciones entre las entidades Employee, Manager y Department. JB183-EAP7.0-es-2-20180124 Solución 4.1. En la clase Employee, asigne la relación de muchos a uno con la entidad Department. Use la anotación @ManyToOne para indicarle a JPA que asigne la relación y la anotación @JoinColumn para especificar la columna departmentID como la clave externa a la tabla Department: ... //TODO Make this class an Entity @Entity //TODO Add a named query to find all employees for a given manager public class Employee { //TODO mark this as the Id field @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; //TODO map the relationship using JPA annotations @ManyToOne @JoinColumn(name="departmentID") private Department department; 4.2. Guarde los cambios en el archivo con Ctrl+S. 4.3. Abra la clase Department; para ello, expanda el ítem jpa-review en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en jpa-review > Java Resources (Recursos de Java) > src/main/java > com.redhat.training.model para expandirlo. Haga doble clic en el archivo Department.java. 4.4. Asigne una relación de muchos a uno con la entidad Employee, que está asignada por el campo department. Cargue las entidades Employee relacionadas. Use la anotación @OneToMany y el atributo mappedBy. Además, defina el tipo de captura como EAGER: @Entity public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; //TODO map the relationship using JPA annotations private Manager manager; //TODO map the relationship using JPA annotations, fetch the list eagerly @OneToMany(mappedBy="department", fetch=FetchType.EAGER) private Set<Employee> employees; JB183-EAP7.0-es-2-20180124 447 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE 4.5. Asigne una relación de uno a uno con la entidad Manager, que está asignada por el campo department. Use la anotación @OneToOne y el atributo mappedBy: @Entity public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; //TODO map the relationship using JPA annotations @OneToOne(mappedBy="department") private Manager manager; //TODO map the relationship using JPA annotations, fetch the list eagerly @OneToMany(mappedBy="department", fetch=FetchType.EAGER) private Set<Employee> employees; nota El error en la anotación OneToOne se resuelve en el próximo paso. 4.6. Guarde los cambios en el archivo con Ctrl+S. 4.7. Abra la clase Manager; para ello, expanda el ítem jpa-review en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en jpa-review > Java Resources (Recursos de Java) > src/main/java > com.redhat.training.model para expandirlo. Haga doble clic en el archivo Manager.java. 4.8. Asigne la relación de uno a uno con la entidad Department, que se relaciona mediante la columna departmentID como clave externa. Use la anotación @OneToOne para indicarle a JPA que asigne la relación. Use la anotación @JoinColumn para especificar departmentID como la clave externa a la tabla Department: @Entity public class Manager { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; //TODO map the relationship using JPA annotations @OneToOne @JoinColumn(name="departmentID") private Department department; 448 JB183-EAP7.0-es-2-20180124 Solución 4.9. Guarde los cambios en el archivo con Ctrl+S. 5. Agregue una consulta nombrada para buscar todos los empleados que se reportan a un determinado gerente y, luego, actualice EmployeeBean para usar la consulta nombrada. 5.1. Abra la clase Employee; para ello, expanda el ítem jpa-review en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en jpa-review > Java Resources (Recursos de Java) > src/main/java > com.redhat.training.model para expandirlo. Haga doble clic en el archivo Employee.java. Agregue la anotación @NamedQuery para registrar la consulta con JPA. Asígnele el nombre findAllForManager a la consulta y utilice un parámetro nombrado denominado :managerId: ... //TODO Make this class an Entity @Entity //TODO Add a named query to find all employees for a given manager @NamedQuery(name="findAllForManager", query="select e from Employee e where e.department.manager.id = :managerId") public class Employee { 5.2. Guarde los cambios en el archivo con Ctrl+S. 6. Implemente la funcionalidad de persistencia en EmployeeBean al inyectar EntityManager e invocar los métodos adecuados para cada método de EJB. 6.1. Abra la clase EmployeeBean; para ello, expanda el ítem jpa-review en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en jpa-review > Java Resources (Recursos de Java) > src/main/ java > com.redhat.training.ejb para expandirlo. Haga doble clic en el archivo EmployeeBean.java. 6.2. Inyecte una instancia de la clase EntityManager en EmployeeBean mediante la anotación @Inject: @Stateless public class EmployeeBean { //TODO inject EntityManager @Inject private EntityManager em; 6.3. Implemente la funcionalidad de persistencia createEmployee mediante el método persist en el administrador de entidades: public void createEmployee(Employee e) { //TODO persist employee em.persist(e); logger.logAction(e, Operation.Create); } JB183-EAP7.0-es-2-20180124 449 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE 6.4. Implemente la funcionalidad de persistencia readEmployeeById al actualizar el método para usar el método find en el administrador de entidades: public Employee readEmployeeById(Long id) { //TODO find the employee by its ID Employee employee = em.find(Employee.class,id) logger.logAction(employee, Operation.Read); return employee; } 6.5. Implemente la funcionalidad de persistencia updateEmployee al actualizar el método para usar el método merge en el administrador de entidades: public void updateEmployee(Employee e) { //TODO merge the employee record em.merge(e); logger.logAction(e, Operation.Update); } 6.6. Implemente la funcionalidad de persistencia deleteEmployee al actualizar el método para usar el método remove en el administrador de entidades: public void deleteEmployee(Employee e) { //TODO remove the employee record em.remove(e); logger.logAction(e, Operation.Delete); } 6.7. Implemente la funcionalidad de persistencia findAllForManager al actualizar el método para crear TypedQuery mediante la consulta nombrada creada en el paso anterior. Defina el ID del objeto administrador que se envió como el parámetro de consulta managerId. Actualice la instrucción de devolución para devolver el resultado: public List<Employee> findAllForManager(Manager manager) { //TODO use the named query to find all the employees for a manager TypedQuery<Employee> query = em.createNamedQuery("findAllForManager",Employee.class); query.setParameter("managerId", manager.getId()); return query.getResultList(); } 6.8. Guarde los cambios en el archivo con Ctrl+S. 7. Actualice EmployeeRestService para agregar un nuevo método REST nombrado getEmployeesForManager que utilice el método findAllForManager en EmployeeBean para recuperar todos los empleados de un departamento. 7.1. Abra la clase EmployeeRestService; para ello, expanda el ítem jpa-review en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en jpa-review > Java Resources (Recursos de Java) > src/main/ java > com.redhat.training.rest para expandirlo. Haga doble clic en el archivo EmployeeRestService.java. 450 JB183-EAP7.0-es-2-20180124 Solución 7.2. Agregue el nuevo método después del comentario //TODO add method to pull an XML list of employees for a given manager (//TODO agregar método para extraer una lista XML de los empleados para un gerente determinado): //TODO add method to pull an XML list of employees for a given manager id public List<Employee> getEmployeesForManager(Long managerId){ Manager manager = managerBean.findById(managerId); return employeeBean.findAllForManager(manager); } 7.3. Asocie el método con las solicitudes HTTP GET mediante la anotación @GET: @GET public List<Employee> getEmployeesForManager(Long managerId){ Manager manager = managerBean.findById(managerId); return employeeBean.findAllForManager(manager); } 7.4. Defina la ruta relativa del método como /getByManager/{managerId} mediante la anotación @Path: @GET @Path("getByManager/{managerId}") public List<Employee> getEmployeesForManager(Long managerId){ Manager manager = managerBean.findById(managerId); return employeeBean.findAllForManager(manager); } 7.5. Agregue la anotación @PathParam en el parámetro del método managerId para asignar el parámetro de ruta de la URL en el método automáticamente: @GET @Path("getByManager/{managerId}") public List<Employee> getEmployeesForManager(@PathParam("managerId") Long managerId){ Manager manager = managerBean.findById(managerId); return employeeBean.findAllForManager(manager); } 7.6. Especifique que el método produce datos XML mediante la anotación @Produces: @GET @Path("getByManager/{managerId}") @Produces(MediaType.APPLICATION_XML) public List<Employee> getEmployeesForManager(@PathParam("managerId") Long managerId){ Manager manager = managerBean.findById(managerId); return employeeBean.findAllForManager(manager); } 7.7. Guarde los cambios en el archivo con Ctrl+S. JB183-EAP7.0-es-2-20180124 451 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE 8. Actualice EmployeeRestService para agregar un nuevo método REST nombrado assignEmployee que asigne un empleado a un departamento mediante ID. 8.1. Agregue un nuevo método después del comentario //TODO add REST method to assign an employee to a department by ID (//TODO agregar método REST para asignar un empleado a un departamento por ID). Use employeeBean para buscar el empleado por su ID y departmentBean para buscar el departamento. A continuación, actualice el registro del departamento en el empleado y utilice el método updateEmployee para persistir los cambios: //TODO add REST method to assign an employee to a department by ID public void assignEmployee(Long employeeId, Long departmentId) { Employee e = employeeBean.readEmployeeById(employeeId); Department d = departmentBean.findById(departmentId); e.setDepartment(d); employeeBean.updateEmployee(e); } 8.2. Asocie el método con las solicitudes HTTP POST mediante la anotación @POST: //TODO add REST method to assign an employee to a department by ID @POST public void assignEmployee(Long employeeId, Long departmentId) { Employee e = employeeBean.readEmployeeById(employeeId); Department d = departmentBean.findById(departmentId); e.setDepartment(d); employeeBean.updateEmployee(e); } 8.3. Defina la ruta relativa del método como /assignEmployee/{employeeId}/ {departmentId} mediante la anotación @Path: @POST @Path("assignEmployee/{employeeId}/{departmentId}") public void assignEmployee(Long employeeId, Long departmentId) { Employee e = employeeBean.readEmployeeById(employeeId); Department d = departmentBean.findById(departmentId); e.setDepartment(d); employeeBean.updateEmployee(e); } 8.4. Agregue las anotaciones @PathParam en los parámetros del método para asignarlos desde la ruta de la URL: @POST @Path("assignEmployee/{employeeId}/{departmentId}") public void assignEmployee(@PathParam("employeeId") Long employeeId, @PathParam("departmentId") Long departmentId) { Employee e = employeeBean.readEmployeeById(employeeId); Department d = departmentBean.findById(departmentId); e.setDepartment(d); employeeBean.updateEmployee(e); } 8.5. Guarde los cambios en el archivo con Ctrl+S. 452 JB183-EAP7.0-es-2-20180124 Solución 9. Actualice EmployeeRestService para agregar un nuevo método REST nombrado assignManager que asigne un gerente a un departamento mediante ID. 9.1. Agregue un nuevo método después del comentario //TODO add REST method to assign an manager to a department by ID (//TODO agregar método REST para asignar un gerente a un departamento por ID). Use manager para buscar el gerente por su ID y departmentBean para buscar el departamento. A continuación, actualice el registro del departamento en el gerente y utilice el método updateManager para persistir los cambios: //TODO add REST method to assign a manager to a department by ID public void assignManager(Long managerId, Long departmentId) { Manager m = managerBean.findById(managerId); Department d = departmentBean.findById(departmentId); m.setDepartment(d); managerBean.updateManager(m); } 9.2. Asocie el método con las solicitudes HTTP POST mediante la anotación @POST: //TODO add REST method to assign an employee to a department by ID @POST public void assignManager(Long managerId, Long departmentId) { Manager m = managerBean.findById(managerId); Department d = departmentBean.findById(departmentId); m.setDepartment(d); managerBean.updateManager(m); } 9.3. Defina la ruta relativa del método como /assignEmployee/{managerId}/ {departmentId} mediante la anotación @Path: @POST @Path("assignManager/{managerId}/{departmentId}") public void assignEmployee(Long employeeId, Long departmentId) { Manager m = managerBean.findById(managerId); Department d = departmentBean.findById(departmentId); m.setDepartment(d); managerBean.updateManager(m); } 9.4. Agregue las anotaciones @PathParam en los parámetros del método para asignarlos desde la ruta de la URL: @POST @Path("assignManager/{managerId}/{departmentId}") public void assignManager(@PathParam("managerId") Long managerId, @PathParam("departmentId") Long departmentId) { Manager m = managerBean.findById(managerId); Department d = departmentBean.findById(departmentId); m.setDepartment(d); managerBean.updateManager(m); } 9.5. Guarde los cambios en el archivo con Ctrl+S. JB183-EAP7.0-es-2-20180124 453 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE 10. Seleccione la pestaña Servers (Servidores) en el panel inferior de JBDS para iniciar EAP. Haga clic con el botón derecho en el servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en el botón verde de inicio para iniciar el servidor. 11. Implemente la aplicación jpa-review mediante los siguientes comandos en la ventana del terminal: [student@workstation ~]$ cd /home/student/JB183/labs/jpa-review [student@workstation jpa-review]$ mvn wildfly:deploy 12. Pruebe el nuevo método REST para extraer empleados de un gerente mediante el complemento (plug-in) de cliente REST de Firefox. 12.1.Inicie Firefox en la máquina virtual workstation y haga clic en el icono del complemento (plug-in) de cliente REST en la barra de herramientas del explorador. 12.2.En la barra de herramientas superior, haga clic en Headers (Encabezados) y en Custom Header (Encabezado personalizado) para agregar un nuevo encabezado personalizado a la solicitud. 12.3.Introduzca la siguiente información en el cuadro de diálogo del encabezado personalizado: • Name (Nombre): Content-Type • Value (Valor): application/json Haga clic en Okay (Aceptar). 12.4.Seleccione GET como el Method (Método). En el formulario de URL, introduzca http://localhost:8080/jpa-review/api/employees/getByManager/1. Haga clic en Send (Enviar). 12.5.Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 200 OK. Revise la pestaña Response Body para ver los datos XML y verifique que coincidan con lo esperado: <collection> <employee> <department> <id>1</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Sales</name> </department> <id>1</id> <name>William</name> </employee> <employee> <department> <id>1</id> <manager> 454 JB183-EAP7.0-es-2-20180124 Solución <id>1</id> <name>Bob</name> </manager> <name>Sales</name> </department> <id>2</id> <name>Rose</name> </employee> <employee> <department> <id>1</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Sales</name> </department> <id>3</id> <name>Pat</name> </employee> </collection> 13. Pruebe los dos nuevos métodos REST para reasignar a los gerentes y empleados a nuevos departamentos. 13.1.Seleccione POST como el Method (Método). En el formulario de URL, introduzca http://localhost:8080/jpa-review/api/employees/assignEmployee/1/2 para reasignar un empleado con un valor de ID de 1 a un nuevo departamento con el ID de 2. 13.2.Haga clic en Send (Enviar). 13.3.Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 204 No Content (204 Sin contenido). Esto está previsto porque el tipo de devolución es void (nulo). 13.4.Seleccione POST como el Method (Método). En el formulario de URL, introduzca http://localhost:8080/jaxrs-review/api/employees/ assignManager/1/2 para reasignar un gerente con un valor de ID de 1 a un nuevo departamento. 13.5.Haga clic en Send (Enviar). 13.6.Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 204 No Content (204 Sin contenido). Esto está previsto porque el tipo de devolución es void (nulo). 13.7.Seleccione POST como el Method (Método). En el formulario de URL, introduzca http://localhost:8080/jpa-review/api/employees/assignManager/2/1 para reasignar al gerente con un valor de ID de 2 al departamento de ventas, reemplazando a Bob. 13.8.Haga clic en Send (Enviar). 13.9.Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 204 No Content (204 Sin contenido). Esto está previsto porque el tipo de devolución es void (nulo). JB183-EAP7.0-es-2-20180124 455 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE 13.10. Ejecute el método getByManager nuevamente para extraer una lista de los nuevos subalternos inmediatos de Bob. Seleccione GET como el Method (Método). En el formulario de URL, introduzca http://localhost:8080/jpa-review/api/employees/getByManager/1. Haga clic en Send (Enviar). 13.11. Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 200 OK. Revise la pestaña Response Body para ver los datos XML y verifique que coincidan con lo esperado: <collection> <employee> <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Marketing</name> </department> <id>1</id> <name>William</name> </employee> <employee> <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Marketing</name> </department> <id>4</id> <name>Rodney</name> </employee> <employee> <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Marketing</name> </department> <id>5</id> <name>Kim</name> </employee> <employee> <department> <id>2</id> <manager> <id>1</id> <name>Bob</name> </manager> <name>Marketing</name> </department> 456 JB183-EAP7.0-es-2-20180124 Solución <id>6</id> <name>Tom</name> </employee> </collection> Evaluación Como el usuario student en workstation, ejecute el script lab jpa-review con el argumento grade para confirmar que ha realizado este ejercicio correctamente. Corrija las fallas informadas y vuelva a ejecutar el script hasta obtener un resultado satisfactorio. [student@workstation ~]$ lab jpa-review grade Después de que la calificación se realiza correctamente, detenga el servidor EAP y cierre el proyecto en JBDS. Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 457 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE Trabajo de laboratorio: Protección de la API REST con JAAS En esta revisión, protegerá la API REST con autenticación y autorización mediante JAAS. Resultados Usted deberá ser capaz de realizar lo siguiente: • Conectar una aplicación web Java a un dominio de seguridad JBoss EAP mediante los archivos de configuración necesarios. • Proteger recursos específicos con autenticación básica. • Limitar la autorización de usuarios que pueden usar servicios RESTEasy específicos mediante anotaciones JAAS para configurar la autenticación basada en roles. Antes de comenzar Prepare sus computadoras para este ejercicio iniciando sesión en workstation como student y ejecutando el siguiente comando: [student@workstation ~]$ lab jaas-review setup Instrucciones 1. Abra JBDS e importe la estructura del proyecto jaas-review. 2. Agregue una nueva clase de servicio REST que cumpla con los siguientes requisitos: • Disponible en la siguiente ruta: http://localhost:8080/jaas-review/api/ healthCheck • Contiene el siguiente método e implementación: public Response getHealthCheck() { ResponseBuilder builder = Response.status(Status.OK); return builder.build(); } • Asociado con solicitudes HTTP GET. • No toma argumentos y devuelve siempre una respuesta de estado HTTP "200 OK". 3. Habilite la autenticación básica en el EmployeeRestService existente. Asegúrese de cumplir con los siguientes requisitos: • Inicie JBoss EAP y cree un nuevo dominio de seguridad para EAP mediante el script create-sd.sh proporcionado. Asegúrese de iniciar JBoss EAP antes de ejecutar este script . Este dominio de seguridad usa el módulo de inicio de sesión UsersRoles para leer el archivo de propiedad del usuario /home/student/JB183/labs/jaasreview/todo-users.properties proporcionado para la autenticación y el archivo /home/student/JB183/labs/jaas-review/todo-roles.properties para la autorización. Se incluyen los siguientes usuarios: 458 JB183-EAP7.0-es-2-20180124 Usuarios de trabajos de laboratorio de revisión integral Nombre de usuario Contraseña Rol employee redhat1! employee manager redhat1! manager superuser redhat1! superuser • Configure la aplicación para utilizar este dominio de seguridad. • Habilite la autenticación basada en rol para RESTEasy. • Use una restricción de seguridad para aplicar la nueva seguridad únicamente a EmployeeRestService, no al nuevo HealthCheckService, que debe estar disponible sin autenticación. • Defina los siguientes roles: ◦ employee ◦ manager ◦ superuser • Configure la aplicación para que use autenticación BÁSICA para requerir el nombre de usuario y la contraseña para todos los recursos protegidos. • Use la siguiente tabla para configurar cada uno de los siguientes métodos de la clase EmployeeRestService para que estén restringidos a los roles adecuados: Método para la asignación de roles 4. Método Roles permitidos getEmployee(Long id) All getEmployeesForManager(Long managerId) All assignEmployee(Long employeeId, Long departmentId) manager, superuser assignManager(Long managerId, Long departmentId) superuser Use Maven para implementar la aplicación jaas-review. nota Los datos de la base de datos se restablecieron antes de este trabajo de laboratorio de revisión integral. 5. Pruebe el método de servicio REST de comprobación de estado no protegido mediante el complemento (plug-in) de cliente REST de Firefox y sin utilizar autenticación. • Use HTTP GET para enviar una solicitud al extremo http://localhost:8080/jaasreview/api/healthCheck para probar el método getHealthCheck. JB183-EAP7.0-es-2-20180124 459 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE 6. Pruebe los métodos de servicio REST protegidos mediante el complemento (plug-in) de cliente REST de Firefox y autenticación básica. Asegúrese de que cada rol pueda acceder solo a los métodos corregidos. • Use las credenciales encontradas en la tabla anterior para definir la autenticación en las solicitudes HTTP enviadas por el cliente. • Especifique un encabezado personalizado con el nombre Content-Type y el valor application/json. Evaluación Como el usuario student en workstation, ejecute el script lab jaas-review con el argumento grade para confirmar que ha realizado este ejercicio correctamente. Corrija las fallas informadas y vuelva a ejecutar el script hasta obtener un resultado satisfactorio. [student@workstation ~]$ lab jaas-review grade Después de que la calificación se realiza correctamente, detenga el servidor EAP y cierre el proyecto en JBDS. Esto concluye el trabajo de laboratorio. 460 JB183-EAP7.0-es-2-20180124 Solución Solución En esta revisión, protegerá la API REST con autenticación y autorización mediante JAAS. Resultados Usted deberá ser capaz de realizar lo siguiente: • Conectar una aplicación web Java a un dominio de seguridad JBoss EAP mediante los archivos de configuración necesarios. • Proteger recursos específicos con autenticación básica. • Limitar la autorización de usuarios que pueden usar servicios RESTEasy específicos mediante anotaciones JAAS para configurar la autenticación basada en roles. Antes de comenzar Prepare sus computadoras para este ejercicio iniciando sesión en workstation como student y ejecutando el siguiente comando: [student@workstation ~]$ lab jaas-review setup Instrucciones 1. Abra JBDS e importe la estructura del proyecto jaas-review. 2. Agregue una nueva clase de servicio REST que cumpla con los siguientes requisitos: • Disponible en la siguiente ruta: http://localhost:8080/jaas-review/api/ healthCheck • Contiene el siguiente método e implementación: public Response getHealthCheck() { ResponseBuilder builder = Response.status(Status.OK); return builder.build(); } • Asociado con solicitudes HTTP GET. • No toma argumentos y devuelve siempre una respuesta de estado HTTP "200 OK". 3. Habilite la autenticación básica en el EmployeeRestService existente. Asegúrese de cumplir con los siguientes requisitos: • Inicie JBoss EAP y cree un nuevo dominio de seguridad para EAP mediante el script create-sd.sh proporcionado. Asegúrese de iniciar JBoss EAP antes de ejecutar este script . Este dominio de seguridad usa el módulo de inicio de sesión UsersRoles para leer el archivo de propiedad del usuario /home/student/JB183/labs/jaasreview/todo-users.properties proporcionado para la autenticación y el archivo /home/student/JB183/labs/jaas-review/todo-roles.properties para la autorización. Se incluyen los siguientes usuarios: Usuarios de trabajos de laboratorio de revisión integral Nombre de usuario Contraseña Rol employee redhat1! employee JB183-EAP7.0-es-2-20180124 461 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE Nombre de usuario Contraseña Rol manager redhat1! manager superuser redhat1! superuser • Configure la aplicación para utilizar este dominio de seguridad. • Habilite la autenticación basada en rol para RESTEasy. • Use una restricción de seguridad para aplicar la nueva seguridad únicamente a EmployeeRestService, no al nuevo HealthCheckService, que debe estar disponible sin autenticación. • Defina los siguientes roles: ◦ employee ◦ manager ◦ superuser • Configure la aplicación para que use autenticación BÁSICA para requerir el nombre de usuario y la contraseña para todos los recursos protegidos. • Use la siguiente tabla para configurar cada uno de los siguientes métodos de la clase EmployeeRestService para que estén restringidos a los roles adecuados: Método para la asignación de roles 4. Método Roles permitidos getEmployee(Long id) All getEmployeesForManager(Long managerId) All assignEmployee(Long employeeId, Long departmentId) manager, superuser assignManager(Long managerId, Long departmentId) superuser Use Maven para implementar la aplicación jaas-review. nota Los datos de la base de datos se restablecieron antes de este trabajo de laboratorio de revisión integral. 5. Pruebe el método de servicio REST de comprobación de estado no protegido mediante el complemento (plug-in) de cliente REST de Firefox y sin utilizar autenticación. • Use HTTP GET para enviar una solicitud al extremo http://localhost:8080/jaasreview/api/healthCheck para probar el método getHealthCheck. 6. Pruebe los métodos de servicio REST protegidos mediante el complemento (plug-in) de cliente REST de Firefox y autenticación básica. Asegúrese de que cada rol pueda acceder solo a los métodos corregidos. 462 JB183-EAP7.0-es-2-20180124 Solución • Use las credenciales encontradas en la tabla anterior para definir la autenticación en las solicitudes HTTP enviadas por el cliente. • Especifique un encabezado personalizado con el nombre Content-Type y el valor application/json. Pasos 1. Abra JBDS e importe el proyecto de Maven. 1.1. Haga doble clic en el icono de JBoss Developer Studio en el escritorio de la estación de trabajo para abrir JBDS. Seleccione el espacio de trabajo /home/student/ JB183/workspace y haga clic en OK (Aceptar). 1.2. En el menú de JBDS, haga clic en File (Archivo) > Import (Importar)para abrir el asistente para Import (Importar). 1.3. En la página Select (Seleccionar), haga clic en Maven > Existing Maven Projects (Proyectos de Maven existentes) y, luego, haga clic en Next (Siguiente). 1.4. En la página Maven projects (Proyectos de Maven), haga clic en Browse (Explorar) para abrir la ventana Select root folder (Seleccionar carpeta raíz). Diríjase al directorio /home/student/JB183/labs/. Seleccione la carpeta jaas-review y haga clic en OK (Aceptar). 1.5. En la página Maven projects (Proyectos de Maven), haga clic en Finish (Finalizar). 1.6. Observe la barra de estado de JBDS para monitorear el progreso de la operación de importación. Es probable que la descarga de todas las dependencias requeridas demore unos minutos. 2. Agregue el nuevo servicio de comprobación de estado. 2.1. Haga clic con el botón derecho en com.redhat.training.rest y haga clic en New (Nueva) > Class (Clase). 2.2. En el campo Name (Nombre) ingrese HealthCheckService. Haga clic en Finish (Finalizar). 2.3. Marque HealthCheckService como un EJB sin estado: import javax.ejb.Stateless; @Stateless public class HealthCheckService { 2.4. Configure la ruta relativa del servicio REST HealthCheckService para que sea / healthCheck mediante la anotación @Path: import javax.ejb.Stateless; import javax.ws.rs.Path; @Stateless @Path("/healthCheck") JB183-EAP7.0-es-2-20180124 463 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE public class HealthCheckService { 2.5. Implemente el método de comprobación de estado para que devuelva una respuesta de código de estado HTTP 200 "OK" para cada solicitud: import import import import import javax.ejb.Stateless; javax.ws.rs.Path; javax.ws.rs.core.Response; javax.ws.rs.core.Response.ResponseBuilder; javax.ws.rs.core.Response.Status; @Stateless @Path("/healthCheck") public class HealthCheckService { public Response getHealthCheck() { ResponseBuilder builder = Response.status(Status.OK); return builder.build(); } } nota El método getHealthCheck() está disponible en el archivo /home/ student/JB183/labs/jaas-review/healthCheck.txt. 2.6. Asocie el método getHealthCheck con las solicitudes HTTP GET mediante la anotación @GET de RESTEasy: import import import import import import javax.ejb.Stateless; javax.ws.rs.Path; javax.ws.rs.core.Response; javax.ws.rs.core.Response.ResponseBuilder; javax.ws.rs.core.Response.Status; javax.ws.rs.GET; @Stateless @Path("/healthCheck") public class HealthCheckService { @GET public Response getHealthCheck() { ResponseBuilder builder = Response.status(Status.OK); return builder.build(); } } 2.7. Presione Ctrl+S para guardar sus cambios. 3. 464 Inicie JBoss EAP desde dentro de JBDS. JB183-EAP7.0-es-2-20180124 Solución Seleccione la pestaña Servers (Servidores) en JBDS. Haga clic con el botón derecho en la entrada del servidor Red Hat JBoss EAP 7.0 [Stopped] y haga clic en la opción verde Start (Iniciar) para iniciar el servidor. 4. Use el script /home/student/JB183/labs/jaas-review/create-sd.sh para crear un dominio de seguridad UsersRoles nombrado userroles. Este dominio de seguridad usa el módulo de inicio de sesión UsersRoles para leer el archivo de propiedad del usuario /home/student/JB183/labs/jaas-review/todousers.properties proporcionado para la autenticación y el archivo /home/student/ JB183/labs/jaas-review/todo-roles.properties para la autorización. 4.1. En la ventana del terminal, diríjase al directorio del proyecto /home/student/ JB183/labs/jaas-review/ y ejecute el script create-sd.sh: [student@workstation ~]$ cd JB183/labs/jaas-review [student@workstation jaas-review]$ ./create-sd.sh 4.2. Confirme que el dominio de seguridad está disponible al visualizar el contenido de la configuración del servidor EAP /opt/jboss-eap-7.0/standalone/ configuration/standalone-full.xml. Mediante un editor de texto, abra el archivo de configuración /opt/jbosseap-7.0/standalone/configuration/standalone-full.xml y observe el nuevo dominio de seguridad que controla al servidor de aplicaciones para utilizar los archivos de propiedad de roles y usuarios proporcionados en el directorio de proyectos. <security-domain name="userroles" cache-type="default"> <authentication> <login-module code="UsersRoles" flag="required"> <module-option name=usersProperties" value="file:///home/student/JB183/labs/ jaas-review/todo-users.properties"/> <module-option name="rolesProperties" value="file:///home/student/JB183/labs/ jaas-review/todo-roles.properties"/> </login-module> </authentication> </security-domain> 5. Actualice el archivo jboss-web.xml para utilizar el dominio de seguridad userroles. 5.1. Abra la clase jboss-web.xml; para ello, expanda el ítem jaas-review en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en jaas-review > Java Resources (Recursos de Java) > src/main/webapp > WEB-INF y expándalo. Haga doble clic en el archivo jboss-web.xml. 5.2. Actualice el archivo jboss-web.xml para utilizar el nuevo dominio de seguridad nombrado userroles: <?xml version="1.0" encoding="ISO-8859-1"?> <jboss-web> <security-domain>userroles</security-domain> JB183-EAP7.0-es-2-20180124 465 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE </jboss-web> 5.3. Presione Ctrl+S para guardar sus cambios. 6. Actualice el archivo web.xml para habilitar las anotaciones de seguridad RESTEasy, utilice la autenticación BASIC y restrinja el acceso a la API REST de la aplicación con la ruta / api/* a los roles manager, employee y superuser. 6.1. Abra la clase web.xml; para ello, expanda el ítem jaas-review en la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS. Haga clic en jaas-review > Java Resources (Recursos de Java) > src/main/webapp > WEB-INF y expándalo. Haga doble clic en el archivo web.xml. 6.2. Cree una nueva etiqueta <context-param> que contenga el parámetro resteasy.role.based.security con un valor de true: <!-- Add context param --> <context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> 6.3. Cree una nueva etiqueta security-constraint que restrinja EmployeeRestService mediante <url-pattern> definido en /api/employees/ *: <!-- Add security constraint --> <security-constraint> <web-resource-collection> <web-resource-name>Secured REST Resources</web-resource-name> <url-pattern>/api/employees/*</url-pattern> </web-resource-collection> </security-constraint> 6.4. Actualice <security-constraint para restringir el acceso para usuarios con el rol employee, manager o superuser mediante la etiqueta auth-constraint: <security-constraint> <web-resource-collection> <web-resource-name>All resources</web-resource-name> <url-pattern>/api/employees/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>employee</role-name> <role-name>manager</role-name> <role-name>superuser</role-name> </auth-constraint> </security-constraint> 6.5. Defina los roles de seguridad para los roles employee, superuser y manager con una etiqueta <security-role>: 466 JB183-EAP7.0-es-2-20180124 Solución <!-- Add security role --> <security-role> <role-name>manager</role-name> </security-role> <security-role> <role-name>employee</role-name> </security-role> <security-role> <role-name>superuser</role-name> </security-role> 6.6. Cree un elemento <login-config> y defina <auth-method> como BASIC. <!-- Add login config --> <login-config> <auth-method>BASIC</auth-method> </login-config> 6.7. Presione Ctrl+S para guardar sus cambios. 7. Actualice la clase de servicio RESTEasy EmployeeRestService.java y actualice cada método con los siguientes requisitos: Método para la asignación de roles Método Roles permitidos getEmployee(Long id) All deleteEmployee(Long id) manager, superuser saveEmployee(Employee employee) manager, superuser getEmployeesForManager(Long managerId) All createDepartment(Department d) manager, superuser createManager(Manager m) superuser assignEmployee(Long employeeId, Long departmentId) manager, superuser assignManager(Long managerId, Long departmentId) superuser 7.1. En la pestaña Project Explorer (Explorador de proyectos) en el panel izquierdo de JBDS, seleccione src/main/java > com.redhat.training.rest. Haga doble clic en EmployeeRestService.java para ver el archivo. 7.2. Actualice el método getEmployee con una anotación @PermitAll para permitir que todos los roles enumerados en web.xml puedan acceder al método: @PermitAll @GET @Path("/byId/{id}") @Produces(MediaType.APPLICATION_JSON) public Employee getEmployee(@PathParam("id") Long id) { return employeeBean.readEmployeeById(id); } JB183-EAP7.0-es-2-20180124 467 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE 7.3. Actualice el método getEmployeesForManager con una anotación @PermitAll para permitir que todos los roles enumerados en web.xml puedan acceder al método: @PermitAll @GET @Path("getByManager/{managerId}") @Produces(MediaType.APPLICATION_XML) public List<Employee> getEmployeesForManager(@PathParam("managerId")Long managerId){ Manager manager = managerBean.findById(managerId); return employeeBean.findAllForManager(manager); } 7.4. Actualice el método assignEmployee con una anotación @RolesAllowed para permitir que solo los roles manager y superuser enumerados en web.xml puedan acceder al método: @RolesAllowed({"manager", "superuser"}) @POST @Path("assignEmployee/{employeeId}/{departmentId}") public void assignEmployee(@PathParam("employeeId")Long employeeId, @PathParam("departmentId") Long departmentId) { Employee e = employeeBean.readEmployeeById(employeeId); Department d = departmentBean.findById(departmentId); e.setDepartment(d); employeeBean.updateEmployee(e); } 7.5. Actualice el método assignEmployee con una anotación @RolesAllowed para permitir que solo el rol superuser enumerado en web.xml pueda acceder al método: @RolesAllowed({"superuser"}) @POST @Path("assignManager/{managerId}/{departmentId}") public void assignManager(@PathParam("managerId")Long managerId, @PathParam("departmentId") Long departmentId) { Manager m = managerBean.findById(managerId); Department d = departmentBean.findById(departmentId); m.setDepartment(d); managerBean.updateManager(m); } 7.6. Presione Ctrl+S para guardar sus cambios. 8. Implemente la aplicación jaas-review mediante los siguientes comandos en la ventana del terminal: [student@workstation ~]$ cd /home/student/JB183/labs/jaas-review [student@workstation jaas-review]$ mvn wildfly:deploy 9. 468 Pruebe el nuevo método REST de comprobación de estado mediante el complemento (plug-in) de cliente REST de Firefox. JB183-EAP7.0-es-2-20180124 Solución 9.1. Inicie Firefox en la máquina virtual workstation y haga clic en el icono del complemento (plug-in) de cliente REST en la barra de herramientas del explorador. 9.2. En la barra de herramientas superior, haga clic en Headers (Encabezados) y seleccione Custom Header (Encabezado personalizado) para agregar un nuevo encabezado personalizado a la solicitud. 9.3. Introduzca la siguiente información en el cuadro de diálogo del encabezado personalizado: • Name (Nombre): Content-Type • Value (Valor): application/json 9.4. Seleccione GET como el Method (Método). En el formulario de URL, introduzca http://localhost:8080/jaas-review/api/healthCheck. No defina una autenticación. Este servicio debe estar disponible públicamente. Haga clic en Send (Enviar). 9.5. Verifique en la pestaña Response Headers (Encabezados de respuesta) que Status Code (Código de estado) sea 200 OK. 10. Use el complemento (plug-in) para verificar que el usuario employee con la contraseña redhat1! tenga permisos para acceder solo a los métodos GET. 10.1.En el complemento (plug-in) de Firefox, haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): employee • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 10.2.Defina el Method Type (Tipo de método) en GET. Defina la URL como http:// localhost:8080/jaas-review/api/employees/getByManager/1 10.3.Haga clic en Send (Enviar). El servidor devuelve un código 200 OK. 10.4.Repita este paso con el extremo byId y asegúrese de que se devuelva un código de respuesta 200 OK. 10.5.Cambie el Method Type (Tipo de método) a POST. Defina la URL como http:// localhost:8080/jaas-review/api/employees/assignEmployee/1/2 10.6.Haga clic en Send (Enviar). El servidor devuelve un código 403 Forbidden. 10.7.Repita los dos pasos anteriores con los métodos assignManager y assignEmployee y asegúrese de que cada uno devuelva un código 403 Forbidden. 11. Use el complemento (plug-in) para verificar que el usuario manager con la contraseña redhat1! tenga permisos para acceder solo a los métodos correctos. JB183-EAP7.0-es-2-20180124 469 Capítulo 10. Revisión completa: Red Hat Application Development I: Programming in Java EE 11.1.En el complemento (plug-in) de Firefox, haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Nombre de usuario: manager • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 11.2.Defina el Method Type (Tipo de método) en GET. Defina la URL como http:// localhost:8080/jaas-review/api/employees/getByManager/1 11.3.Haga clic en Send (Enviar). El servidor devuelve un código 200 OK. 11.4.Repita este paso con el método byId y asegúrese de que se devuelva un código de respuesta 200 OK. 11.5.Cambie el Method Type (Tipo de método) a POST. Defina la URL como http:// localhost:8080/jaas-review/api/employees/assignEmployee/1/2 11.6.Haga clic en Send (Enviar). El servidor devuelve un código 204 No Content. 11.7.Repita los dos pasos anteriores con el método assignManager. Asegúrese de que cada uno devuelva un código 403 Forbidden. 12. Use el complemento (plug-in) para verificar que el usuario superuser con la contraseña redhat1! tenga permisos para acceder solo a los métodos correctos. 12.1.En el complemento (plug-in) de Firefox, haga clic en Authentication (Autenticación) y en Basic Authentication (Autenticación básica). Inicie sesión con las siguientes credenciales: • Username (Nombre de usuario): superuser • Password (Contraseña): redhat1! Haga clic en Okay (Aceptar). 12.2.Defina el Method Type (Tipo de método) en GET. Defina la URL como http:// localhost:8080/jaas-review/api/employees/getByManager/1 12.3.Haga clic en Send (Enviar). El servidor devuelve un código 200 OK. 12.4.Repita este paso con el método byId y asegúrese de que se devuelva un código de respuesta 200 OK. 12.5.Cambie el Method Type (Tipo de método) a POST. Defina la URL como http:// localhost:8080/jaas-review/api/employees/assignEmployee/1/2 12.6.Haga clic en Send (Enviar). El servidor devuelve un código 204 No Content. 12.7.Repita los dos pasos anteriores con el método assignManager y asegúrese de que devuelve un código 204 No Content. 470 JB183-EAP7.0-es-2-20180124 Solución Evaluación Como el usuario student en workstation, ejecute el script lab jaas-review con el argumento grade para confirmar que ha realizado este ejercicio correctamente. Corrija las fallas informadas y vuelva a ejecutar el script hasta obtener un resultado satisfactorio. [student@workstation ~]$ lab jaas-review grade Después de que la calificación se realiza correctamente, detenga el servidor EAP y cierre el proyecto en JBDS. Esto concluye el trabajo de laboratorio. JB183-EAP7.0-es-2-20180124 471 472