Your SlideShare is downloading. ×
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Fwpa doc-desarrollo
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Fwpa doc-desarrollo

1,117

Published on

Framework

Framework

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
1,117
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
14
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Manual de Desarrollode Aplicaciones J2EE
  • 2. Manual de Desarrollo de Aplicaciones J2EEVersión 1.5publicado 26-Junio-2006Copyright © Gobierno del Principado de Asturias 2005
  • 3. Tabla de contenidos 1. Presentación de openFWPA .............................................................................................. 1 Introducción .............................................................................................................. 1 Visión General de openFWPA ...................................................................................... 2 Empaquetamiento de openFWPA .......................................................................... 4 Requerimientos técnicos y de sistema ..................................................................... 5 Lista completa de funcionalidades ......................................................................... 5 Lista de componentes de terceras partes .................................................................. 6 Arquitectura de referencia ............................................................................................ 8 Desarrollo de aplicaciones .......................................................................................... 10 Estructura de directorios ..................................................................................... 10 Compilación y despliegue del sistema ................................................................... 11 Pruebas unitarias ............................................................................................... 11 Instalación de la aplicación de ejemplo (Sample App) .............................................. 11 2. Arquitectura Modelo -Vista- Controlador con openFWPA ..................................................... 13 MVC ...................................................................................................................... 13 Desarrollo de la Vista ................................................................................................ 14 Aspecto corporativo de las aplicaciones del Principado de Asturias ............................. 14 Cascading Style Sheets (CSS) ............................................................................. 14 Desarrollo del Controlador ......................................................................................... 16 Declaración de Actions ...................................................................................... 16 Jerarquía de Actions .......................................................................................... 17 Action Forms ............................................................................................. 29 Desarrollo de lógica de negocio .................................................................................. 31 Service Locator ................................................................................................ 31 Session EJBs .................................................................................................... 31 Value Objects .................................................................................................. 31 Excepciones ..................................................................................................... 32 Utilidades ........................................................................................................ 33 Otras clases de utilidad .............................................................................................. 33 PrincastMessageFmtter ............................................................................ 33 PrincastUtils ............................................................................................ 33 ParameterCaster ........................................................................................ 33 ServletPathUtils ...................................................................................... 33 DateDecorator ............................................................................................ 34 PrincastPathResolver .............................................................................. 34 PrincastOSCacheInterceptor .................................................................. 35 Providers ......................................................................................................... 35 3. Implementación de la Arquitectura de Referencia con openFWPA .......................................... 37 Inversión de Control en la Arquitectura de Referencia ..................................................... 37 Introducción al manejo de la Arquitectura con Spring ...................................................... 37 Estableciendo el Datasource ........................................................................................ 38 Enlazando con los DAOs ........................................................................................... 39 Enlazando con los Managers ....................................................................................... 40 Gestión de transacciones ............................................................................................ 41 Enlazado con los Delegates ........................................................................................ 42 Enlazado con las Actions ........................................................................................... 42 Acceso directo al ApplicationContext ................................................................. 43 BeanDoc para obtener la gráfica de arquitectura ............................................................. 43 Plugin SpringIDE para Eclipse .................................................................................... 43 4. Componentes para acceso a datos ..................................................................................... 45 Acceso a Bases de Datos Relacionales .......................................................................... 45 iv
  • 4. Manual de Desarrollo de Aplicaciones J2EE El patrón DAO ................................................................................................. 45 Loggeo de las Excepciones en los DAO ................................................................ 47 Listas clave/valor .............................................................................................. 47 LookupPropertyBean .................................................................................. 49 Providers ......................................................................................................... 49 Generadores de Secuencia .................................................................................. 49 Pools de conexiones .......................................................................................... 505. Construccion de informes con openFWPA ......................................................................... 51 Generación de Informes ............................................................................................. 51 Creación de los diseños XML. ............................................................................ 51 Clases en el openFWPA para la renderización de informes. ...................................... 576. Operaciones ................................................................................................................. 61 Sistema de Inicialización y Arranque ............................................................................ 61 Declaración de objetos inicializables .................................................................... 61 Desarrollo de objetos inicializables ...................................................................... 62 Arranque de aplicaciones web ............................................................................. 63 Arranque manual .............................................................................................. 63 Sistema de Configuración de Aplicaciones .................................................................... 63 Implementación de objetos configurables .............................................................. 64 Plugins de Configuración ................................................................................... 66 Logging .................................................................................................................. 72 Log4J. Componentes ......................................................................................... 72 Configuración ................................................................................................... 74 Componentes del openFWPA para Logging. .......................................................... 75 Pista de auditoría .............................................................................................. 76 Pista de Rendimiento ......................................................................................... 77 Ficheros de Configuración .......................................................................................... 77 web.xml ........................................................................................................ 77 struts-config.xml .................................................................................... 79 Filtros web .............................................................................................................. 83 Filtros del openFWPA. PrincastFilter. ......................................................... 83 Configuración del filtro GZIPFilter ................................................................. 85 Configuración del filtro SecurityFilter ......................................................... 85 Filtro de navegación .......................................................................................... 85 Filtro de activación ........................................................................................... 86 Filtros de Activación ......................................................................................... 87 Consola de gestión .................................................................................................... 887. Seguridad en aplicaciones con openFWPA ......................................................................... 90 Seguridad ................................................................................................................ 90 Autentificación básica ........................................................................................ 90 Autentificación basada en formulario .................................................................... 90 Autentificación basada en el Filtro de Seguridad del openFWPA ............................... 90 Single Sign On ............................................................................................... 1038. Integración de Sistemas ................................................................................................ 104 Tecnologías de Integración ....................................................................................... 104 XML Genérico: Configuración .......................................................................... 1049. Pruebas ...................................................................................................................... 106 Pruebas unitarias ..................................................................................................... 106 Jerarquía de clases para las pruebas unitarias ........................................................ 106 Convenciones a seguir ..................................................................................... 107 Ejecución de las pruebas unitarias ...................................................................... 107 Consultando los resultados de los tests ................................................................ 107 Pruebas unitarias de objetos que acceden a bases de datos. ...................................... 108 Pruebas unitarias de Spring Beans ...................................................................... 112 v
  • 5. Manual de Desarrollo de Aplicaciones J2EE Pruebas unitarias de objetos Configurables .................................................... 112 Pruebas unitarias en contenedor ......................................................................... 113 Pruebas unitarias de informes ............................................................................ 114 Más información sobre la implementación de pruebas unitarias ................................ 115Pruebas de Rendimiento ........................................................................................... 116 Modo de Prueba de Rendimiento ....................................................................... 116 vi
  • 6. Lista de figuras 1.1. Estructura de openFWPA ............................................................................................... 2 1.2. Arquitectura de Referencia ............................................................................................. 8 1.3. Estructura de directorios del proyecto Sample App. ........................................................... 10 2.1. Modelo Vista Controlador ............................................................................................ 13 2.2. Ciclo petición-acción-jsp de Struts ................................................................................. 13 2.3. Estructura de capas de las aplicaciones web con openFWPA ............................................... 14 2.4. Aspecto corporativo del portal princast.es ................................................................ 14 2.5. Aspecto de la aplicación de ejemplo (Sample App) ........................................................... 14 2.6. Jerarquía de Actions .................................................................................................... 17 2.7. Maquinaria de Estados de PrincastAction ................................................................ 18 2.8. Almacenamiento de la Action .................................................................................... 21 2.9. Esquema de la PrincastDispatchAction del ejemplo ............................................... 25 2.10. Esquema de las Actions para listados ............................................................................ 28 2.11. Estructura de la capa Modelo ...................................................................................... 31 2.12. Diagrama de Value Objects ........................................................................................ 32 2.13. Jerarquía de Excepciones ............................................................................................ 32 3.1. Ficheros de configuración de Beans ............................................................................... 37 3.2. Ejemplo de gráfica generada con BeanDoc ...................................................................... 43 3.3. Spring IDE para visualizar ficheros de Spring .................................................................. 44 4.1. Ejemplo de necesidad de lookup .................................................................................... 49 4.2. Navegación de una relación en BD con un LookupPropertyBean ......................................... 49 5.1. Proceso de generación de informes ................................................................................ 51 6.1. Estructura del sistema de configuración .......................................................................... 64 6.2. Jerarquía de Plugins .................................................................................................... 68 6.3. Estados del Sistema de Logging .................................................................................... 74 7.1. Esquema del sistema de autenticación ............................................................................ 91 9.1. Set de pruebas unitarias disponibles .............................................................................. 106 9.2. Autowiring de DAOs y DataSources ............................................................................ 110 vii
  • 7. Lista de tablas 1.1. Componentes necesarios para la ejecución del openFWPA ................................................... 7 1.2. Capas de la Arquitectura de Referencia de openFWPA ........................................................ 9 viii
  • 8. Capítulo 1. Presentación de openFWPAIntroducción openFWPA es el framework de desarrollo libre para sistemas de administración electrónica y gobierno electrónico desarrollado por el Gobierno del Principado de Asturias. Está basado en la tecnología J2EE y su objetivo es facilitar el diseño, implementación, implantación y mantenimiento de las aplicaciones. openFWPA es Software Libre / Open Source y está publicado bajo una doble licencia: LGPL 3.0 (o superior) y EUPL 1.0 (o superior). La Licencia Pública General Menor del proyecto GNU (LGPL) es una de las licencias desarrolladas y promovidas por la Free Software Foundation (FSF), y da permisos de reproducción, distribución, modificación y redistribución con copyleft, aunque no se impide la utilización de componentes privativos dentro del sistema. La Licencia Pública de la Unión Europea (EUPL) es una licencia de software libre con copyleft creada y apoyada por la Unión Europea para el impulso del Software Libre en las administraciones públicas. Los dos grandes objetivos del framework son: • Simplificación y homogeneización del proceso de desarrollo de aplicaciones. Para ello openFWPA proporciona una arquitectura reutilizable y un conjunto de herramientas y librerías que implementan algunos de los componentes más habituales, y de escritura más tediosa, en aplicaciones web. Todo ello debiera redundar en un menor coste total de propiedad (TCO) de las soluciones desarrolladas sobre openFWPA. • Definición de estándares de desarrollo, calidad y aceptación. Se trata de un conjunto de directrices, de obligado cumplimiento, para exigir y garantizar unos niveles mínimos de calidad en las aplicaciones J2EE [1]. Estos estándares son internos al Principado de Asturias, y son por tanto aplicables solamente dentro de aquellos proyectos desarrollados dentro del Principado de Asturias. El openFWPA posee las siguientes características: • Uso de software libre. Hay una serie de proyectos del mundo del software libre que poseen una excelente calidad, lo que los habilita para participar en aplicaciones de misión crítica. • Uso de patrones de diseño. El openFWPA promueve el uso de patrones de diseño, en dos sentidos importantes. En primer lugar, el framework está diseñado y construido sobre patrones. Por otro lado, las aplicaciones que se desarrollan sobre el framework hacen uso asimismo de patrones. Entre otros, las aplicaciones desarrolladas sobre openFWPA siguen una arquitectura Modelo2 , el estándar en aplicaciones web (se trata de una adaptación del famoso MVC). • Uso de estándares. En el diseño del openFWPA se ha promovido la utilización e incorporación de estándares (por ejemplo, XHTML [4] + CSS [14], etc.). El uso tanto de patrones de diseño como de estándares proporciona importantes ventajas en cuanto a la adaptabilidad y longevidad de las aplicaciones que los utilizan, al ser más fácilmente mantenidas, extendidas o reutilizadas. • Aspecto corporativo. Otra característica importante es que las aplicaciones deben integrarse con el resto de aplicaciones del Principado de Asturias, tanto a nivel funcional como de aspecto (look & feel). El openFWPA incluye un conjunto de plantillas y componentes para construir la capa de presentación de acuerdo a las guías de estilo corporativo del Principado de Asturias. En la versión publicada en portal, estas plantillas pueden modificarse de acuerdo a las necesidades de cada proyecto. Sin embargo, los proyectos internos han de seguir las directrices de estilo corporativo. • Integración de aplicaciones. La nueva funcionalidad, añadida al openFWPA en las últimas versiones, facilita la integración de las aplicaciones con otros sistemas del Principado de Asturias (sistema de seguridad, comunicaciones, bases de datos corporativas, sistemas de seguridad, sistemas de CRM, etc.). 1
  • 9. Presentación de openFWPA Esta funcionalidad solo está disponible para los proyectos internos, al carecer de interés en aplicaciones desarrolladas fuera de la organización. • Ciclo de vida. Las aplicaciones poseen un ciclo de vida más allá de su desarrollo y puesta en producción. Éstas han de ser configuradas, migradas y operadas en los diversos entornos. Por ejemplo, el framework proporciona piezas con una funcionalidad importante que facilita el soporte a la operación. Los componentes del framework están preparados para ser gestionados “en caliente” desde una consola de operaciones, y ofrece componentes para aspectos críticos de operación (como gestión adecuada de logging, pistas de auditoría, estadísticas de rendimiento y uso). En general, estos aspectos se incorporan al framework de manera transparente a las aplicaciones. Asimismo, se ofrece (opcionalmente) una serie de APIs avanzadas que permiten a las aplicaciones publicar funcionalidad en la consola de operaciones.Visión General de openFWPA El framework de desarrollo J2EE del Principado de Asturias posee la siguiente estructura. Dentro de cada elemento se muestran los artefactos más relevantes: Figura 1.1. Estructura de openFWPA A continuación, se muestran en más detalle los diversos elementos que lo componen. Aceptación Las aplicaciones desarrolladas con el framework para uso interno del Principado de Asturias deben pasar por una serie de controles de calidad. A tal efecto, se han desarrollado una serie de guías que deben seguirse para el desarrollo de estas aplicaciones. Dentro de esta guía de aceptación se define una arquitectura reutilizable que debieran seguir las aplicaciones. Entorno de desarrollo El framework de desarrollo es agnóstico en cuanto al entorno de desarrollo. Éste debiera poseer los siguientes elementos: • Entorno Integrado de desarrollo. Ofrece un entorno donde los desarrolladores pueden desarrollar, compilar, depurar y probar el software en construcción. • Herramientas de despliegue. Permite el despliegue de las aplicaciones en los distintos entornos (máquina local, máquinas del entorno de desarrollo, etc.). • Diseño de informes. Permite la construcción de informes en distintos formatos. • Gestión de la configuración. Permite la gestión del cambio de los distintos elementos del sistema (código fuente, scripts de construcción, pruebas, etc.). Se trata de un sistema de control de versiones. • Entorno de integración (semi) continua. El entorno de desarrollo debiera ofrecer funcionalidad avanzada de integración continua o semi-continua. Los proyectos arrancados dentro del Principado de Asturias deben poseer las herramientas definidas en el puesto estándar. Estas 2
  • 10. Presentación de openFWPA herramientas deben instalarse y configurarse de manera estándar (igual para todas las estaciones de trabajo).Software Libre Tras la definición de los requisitos del framework en términos de herramientas necesarias para el entorno de desarrollo, directrices de aceptación y diseño del runtime del framework, se realizó la selección de distintos componentes del mundo del código abierto o gratuito para su inclusión. Por ejemplo, se seleccionó Eclipse como Entorno Integrado de Desarrollo, o CVS para el Control de Versiones. Como elementos más relevantes, destaca el uso de Eclipse, Spring o Struts.Sistema de tiempo de ejecución El sistema de tiempo de ejecución es un conjunto de ficheros .jar que se despliegan con cada una de las aplicaciones del framework. Este sistema sigue las directrices de construcción de aplicaciones, y ofrece componentes reutilizables y una base extensible para el desarrollo basado fuertemente en patrones de diseño. De esta manera, las aplicaciones se reducen en tamaño y complejidad, teniendo todas la misma estructura interna (basada en una adaptación del patrón MVC llamada Modelo2). El sistema de tiempo de ejecución emplea diversos componentes del mundo de código abierto, usando lo mejor de cada uno de ellos e integrándolos. Esta aproximación ha facilitado enormemente el desarrollo, y emplea internamente dos frameworks – Struts y Spring.Módulos de integración Los sistemas a construir dentro del ámbito de la Administración del Principado de Asturias presentan una fuerte componente de integración con otros sistemas. Se han escrito adaptadores para los distintos sistemas corporativos existentes dentro de la organización, de manera que se simplifica y homogeneíza enormemente las tareas de integración siguiendo un patrón proxy. Estos módulos solo están disponibles para aquellos proyectos realizados para el Principado de Asturias.Seguridad Es crucial que las aplicaciones desarrolladas posean un nivel de seguridad suficiente, y que esta seguridad pueda gestionarse centralmente desde los sistemas corporativos de seguridad. A este fin, se ha desarrollado toda una infraestructura de seguridad sobre estándares (X509, Java Authentication and Authorization Service, Web Services, etc.). Desde el punto de vista de la aplicación, se trata de realizar una parametrización. Toda esta infraestructura es extensible. Dado que determinados proyectos se desarrollan por equipos externos sin acceso a la infraestructura del Principado de Asturias, se incluye un simulador de autenticación, de manera que determinados escenarios pueden ejecutarse empleando un documento XML en local como repositorio de credenciales. Asimismo, esta infraestructura es extensible, de manera que pueden desarrollarse adaptadores a otros repositorios (LDAP, etc) en proyectos ajenos al Principado de Asturias.Operación Las aplicaciones han de ser operadas en los distintos entornos, de manera que el personal de operaciones pueda mantener la 3
  • 11. Presentación de openFWPA aplicación en funcionamiento. El framework posee una serie de herramientas que facilitan esta operación, como pueden ser: • Filtro de compresión. El framework proporciona un filtro de compresión de las comunicaciones, de manera que se minimice la comunicación entre el servidor y el cliente. • Manual de Operaciones. En este documento se describen las operaciones que pueden realizarse sobre la aplicación desplegada. • Configuración. El framework posee un subsistema flexible de configuración, de manera que las aplicaciones se aislan de los repositorios de configuración. • Auditoría. Se proporciona funcionalidad para la generación de pistas de auditoría. • Gestión de logs. El framework proporciona un potente sistema de logs, de manera que (por configuración) puede enviarse los mensajes a una BD, a ficheros de texto, XML, HTML, etc. Esta configuración puede cambiarse en caliente. • Consola de Administración. Las aplicaciones desarrolladas con el framework poseen una consola de administración web para la modificación de los distintos componentes. • Métricas de uso. Pueden habilitarse diversas métricas de uso de la aplicación, de manera transparente para las aplicaciones. Documentación Con el framework se entrega toda la documentación necesaria para el desarrollo y operación de aplicaciones. Se entregan una aplicación de ejemplo (con sus pruebas de rendimiento correspondientes) y una aplicación en blanco, con la estructura de directorios creada. Soporte Existe un sitio de soporte para la resolución de dudas, incidencias, etc. Este sitio web de soporte permite comunicar al equipo de mantenimiento del framework bugs detectados, etc. de manera que se pueda liberar una nueva entrega (release) con los defectos corregidos. A tal efecto, se crea un usuario para cada equipo de desarrollo que demande soporte, para que puedan realizar un seguimiento consistente de las incidencias que puedan surgir.Empaquetamiento de openFWPA El conjunto completo de entregables que puede acompaña al openFWPA es el que sigue. En algunas distribuciones, pueden no estar disponibles determinados elementos: • Manual del desarrollador. (Este documento). • Manual de operaciones. • Manual de configuración de la seguridad. • Directrices de aceptación de aplicaciones J2EE del Principado de Asturias. 4
  • 12. Presentación de openFWPA • Herramientas del desarrollador. • Guía de estilo del lenguaje Java • Guía de estilo del lenguaje JSP. • openFWPA. (binarios) • Aplicación de ejemplo: SampleApp (binarios y fuentes) • Aplicación en blanco: App Blank (binarios y fuentes)Requerimientos técnicos y de sistema Para la correcta ejecución de las aplicaciones que utilizan el openFWPA es necesario disponer de los siguientes elementos: • Librerías de soporte (Ver “Lista de componentes de terceras partes”) • Servidor de aplicaciones Oracle10G OC4J (versión 10.1.2) con Java JRE 1.4.2 Determinadas partes de la aplicación requieren además, los siguientes componentes: a. Seguridad: • Certificado Raíz de la Fabrica Nacional de Moneda y Timbre (FNMT) • Fichero de certificados (cacerts) en la máquina virtualLista completa de funcionalidades Las funcionalidades soportadas por el openFWPA son las siguientes: • Extensión del framework Struts [8] con una colección propia de clases Action. • Acceso a datos a través de objetos DAO. • Automatización de la carga de consultas SQL desde ficheros de propiedades. • Plantillas (Tiles) para la creación rápida de páginas JSP. • Hojas de estilos con el look & feel del Principado de Asturias. • Facilidades para la generación de informes en formato PDF. • Etiquetas JSP para la inclusión de listas, barras de navegación, fechas y calendarios en las páginas web. • Integración de formularios (ActionForm) con entidades (ValueObject) de la aplicación. • Utilidades para la gestión de tablas de datos en formato {atributo, valor}. • Facilidades para la obtención de listas paginadas como resultado de consultas. • Herramienta para la generación automática de menús. 5
  • 13. Presentación de openFWPA • Infraestructura para pruebas unitarias. • Infraestructura para pruebas unitarias en contenedor. • Integración de una consola de monitorización y gestión basada en el estándar JMX [19]. • Jerarquía propia de excepciones. • Monitorización y control integrado de errores • Sistema de configuración centralizado. • Sistema de inicialización y arranque configurable. • Componentes para el acceso a pools de conexiones. • Sistema de logging con varios niveles. • Gestión de logging desde la consola de administración. • Monitor de rendimiento. • Sistema de monitorización para las clases Action. • Generación de estadísticas de acceso a las aplicaciones. • Generación de estadísticas de excepciones no controladas en las aplicaciones. • Componente para monitorizar el estado del sistema sobre el que corre la aplicación. • Infraestructura para filtros gestionados “en caliente”. • Filtro para compresión GZIP. • Inicialización de componentes configurables. • Filtro de seguridad para integración con el Módulo de Autenticación del SAC del Principado de Asturias. • Conexión con backends del Principado de Asturias (Claves, Terceros, Siebel, Módulo Común de SMS).Lista de componentes de terceras partes El framework de desarrollo del Principado de Asturias incorpora componentes de terceras partes. Las aplicaciones que se construyan sobre el framework han de utilizar las versiones enumeradas en 6
  • 14. Fundación Apache.Struts Menu sourceforge.net struts-menu.jar 2.2 Librería para struts-menu.tld facilitar el struts-menu-el.tld Presentación de openFWPA desarrollo de menús en aplicaciones web.Tabla 1.1. Componentes necesarios para la ejecución del openFWPAApache Ant ASF 1.6.1 Herramienta para la automatización de las operaciones de compilación, construcción y despliegue de proyectos.Java SDK Sun Microsystems 1.3.1_11 - 1.4.x Conjunto de herramientas y librerías Java.Oracle AS9i Oracle oc4.jar admin.jar 9.0.3.0.0 - 10.1.2 Servidor de aplicaciones de Oracle.JMX Reference Sun Microsystems jmxri.jar 1.0 Librería para laImplementation jmxgrinder.jar gestión dinámica de jmxtools.jar aplicaciones Java (sólo es necesaria con OC4J 9.0.3).Base de Datos Oracle 8.1.7.3 Sistema de GestiónOraclae de Bases de Datos.Jasper Reports sourceforge.net jasperreports.jar 0.6.0 Herramienta para la generación de informes en diferentes formatos: PDF, HTML, XLS, CSV y XML.JSSE Sun Microsystems jsse.jar 1.0.3_03 Proporciona soporte para la conexión bajo protocolo SSL.JAAS Sun Microsystems jaas.jar 1.0 Proporciona soporte para autentificación y autorización.JCE Sun Microsystems jce.jar 1.2.2 Proporciona local_policy.jar soporte para uso sunjce_provider.jar de protocolos de US_export_policy.jar encriptación.JDBC Sun Microsystems jdbc2_0-stdext.jar 2.0 Extensiones JDBC para la compilación con versión 1.3.1_11 de la JDK.Spring Spring Framework spring.jar 1.2.6 Framework IoC.Java Monitor API JAMon API JAMon.jar 1.1.2 Librería de monitorización y medición de tiemposDirect Web Get Ahead dwr.jar 1.0 Librería de AJAXRemoting (DWR) 7
  • 15. Presentación de openFWPAArquitectura de referencia El framework de desarrollo del Principado de Asturias hace un uso intensivo de Patrones de Diseño. A fin de lograr una homogeneidad efectiva en las aplicaciones realizadas en el marco del Principado de Asturias, se propone una Arquitectura de Referencia que describe la arquitectura de las aplicaciones desarrolladas con el openFWPA. El uso de esta arquitectura de referencia es obligatorio, al ser parte de las Directrices de Aceptación de aplicaciones. Una arquitectura de referencia es una descripción de los elementos de los que se compone una aplicación, y de las relaciones entre estos elementos. Manejar arquitecturas de referencia es tremendamente beneficioso, ya que permite: Homogeneizar las aplicaciones. Al usar la arquitectura de referencia, las aplicaciones son estructuralmente iguales, cambiando sólo los elementos en concreto, pero no la forma que tienen de relacionarse. Esto tiene un impacto directo en el esfuerzo en desarrollo y mantenimiento. Extender las mejores prácticas y tecnologías. La arquitectura de referencia ha de mantenerse, de manera que se vayan introduciendo cambios basados en cambios tecnológicos o en el establecimiento de mejores prácticas. La arquitectura de referencia J2EE propuesta se basa en el patrón Modelo2 sobre una disposición en capas, y puede verse en el siguiente diagrama: Figura 1.2. Arquitectura de Referencia El concepto de separación en capas está claramente definido en esta arquitectura de referencia: La comunicación entre capas sólo puede existir a través de a) interfaces, b) Objetos de Datos (Value Objects). Los elementos de la arquitectura de referencia pueden verse en la siguiente tabla: 8
  • 16. Presentación de openFWPATabla 1.2. Capas de la Arquitectura de Referencia de openFWPAElemento Descripción Patrones relevantesCapa de Acceso a Datos Encapsula toda la lógica de acceso Data Access Object Proxy Value a datos. Asimismo, encapsula los Object Absctract Factory accesos a sistemas remotos.Capa de Objetos de Datos Representa las entidades del Value Object modelo, como objetos JavaBean y sin lógica de negocio.Capa de Negocio Implementa toda la lógica de Business Delegate Façade negocio, implementada como procesos sobre la capa de Acceso a Datos. Oculta toda la comlejidad a la capa superior.Capa de Controlador Transforma eventos en la vista a MVC Command eventos en el modelo, y viceversa.Capa de Vista Presenta el modelo al usuario, MVC y comunica sus acciones al controladorFiltro web Permiten filtrar las peticiones de Chain Of Responsibility los clientes, a fin de propor-cionar autenticación, asertos a toda la aplicación, compresión de datos, etc.Datasource Gestiona pools de conexiones, a fin de no crear una conexión por cliente a Base de Datos u otros repositorios.Gestión de sesión Gestiona la sesión de los clientes, de manera que desconecta a los inactivos.Sistema externo Representa cualquier sistema a integrar a través de un interfaz bien definido.Dado el número de librerías que implementan el patrón MVC, tiene todo el sentido usar alguna de ellasen vez de implementarlo para un proyecto. El openFWPA da soporte para este patrón. Caso de ser unaaplicación J2EE no construida sobre el openFWPA, debiera hacer uso del framework Struts.Una vez fijada la arquitectura de referencia, se ha acudido al mundo del software libre buscandoimplementaciones de los elementos reseñados en ella. Por ejemplo, para la capa del controlador se haoptado por usar una implementación de un proyecto del software libre en vez de proceder a realizar unaimplementación propia. Asimismo, el openFWPA ofrece soporte en la implementación de todas las capas,desde acceso a datos hasta presentación.En general, prácticamente todas las librerías utilizadas por el openFWPA provienen de la Apache SoftwareFoundation (ASF) [5] y también pueden ser consideradas como estándares “de facto” en sus respectivasáreas. Las librerías proporcionadas por la ASF, son de código libre y abierto, están mantenidas por unnutrido grupo de desarrolladores de todo el mundo y son muy habituales en proyectos de desarrollo(principalmente Java) de cualquier índole. 9
  • 17. Presentación de openFWPADesarrollo de aplicaciones Antes de comenzar el desarrollo de una aplicación web con el openFWPA, es importante tener en cuenta las directrices y recomendaciones que se indican en este apartado.Estructura de directorios Las aplicaciones definirán una estructura de directorios siguiendo la plantilla: • build, target: Contendrá los .class generados para el proyecto. • db: Contendrá los scripts de creación de la base de datos o la propia base de datos. En caso de darse soporte a más de una base de datos o más de una versión, ha de crearse un directorio para cada una de las BD. • config: Contendrá los ficheros necesarios para la creación del fichero EAR necesario para desplegar la aplicación en el contenedor J2EE (como por ejemplo application.xml), así como los ficheros con la información necesaria para la configuración de recursos que necesitará la aplicación (por ejemplo DataSources. En este caso podría incluirse un fichero data-sources.xml con la información a añadir al fichero data-soruces.xml del contenedor J2EE para la definición de los mismos). • src: Este directorio contendrá dos subdirectorios: • java: Contendrá los ficheros de código fuente Java y de recursos de la aplicación, y el fichero build.xml. • webapp: • pages: Contendrá el resto de ficheros de la aplicación: páginas HTML, JSP, imágenes, hojas de estilo CSS, etc. • WEB-INF: Contendrá los ficheros de configuración XML (web.xml, struts-config.xml, validation.xml, etc.), DTDs y TLDs. • lib: Contendrá las librerías que será necesario distribuir con la aplicación, puesto que no estarán incluidas en el contenedor J2EE. • ejbApp: • META-INF: Contendrá el fichero de MANIFEST.MF, así como los ficheros necesarios para el despliegue de EJBs en caso de que sean utilizados en la aplicación. Estos ficheros sería ejb- jar.xml, orion-ejb-jar.xml, … y cualquier otro fichero que fuera necesario. • dist: Se trata de un directorio temporal empleado para la generación de los jars, ears,… necesarios para el proyecto. • javadoc: Contiene el javadoc generado con el target de Ant incluido al efecto. Como ejemplo se muestra la estructura de la aplicación de ejemplo (Sample App): Figura 1.3. Estructura de directorios del proyecto Sample App. 10
  • 18. Presentación de openFWPACompilación y despliegue del sistema Para la compilación y el despliegue de aplicaciones se utilizará la herramienta Ant [10] (http:// ant.apache.org). Ant es una herramienta de construcción basada en Java similar al clásico Make. Los ficheros de configuración de Ant están escritos en XML y tienen por nombre build.xml. Cada uno de ellos contiene un project y al menos un target (el default, que será el que se ejecutará si no se especifica ningún otro en la llamada a Ant). Cada uno de ellos será el encargado de la compilación, empaquetado, despliegue en el contenedor J2EE, etc. de la aplicación. Con las aplicaciones en blanco (Blank App) de ejemplo (Sample App) de distribuye un fichero build.xml. Los targets más relevantes son los siguientes: • all (default): Realiza lo mismo que make-ear. • compile: Compila los ficheros fuente Java de la aplicación. • javadoc: Genera la documentación Javadoc. • test.unit: Lanza las pruebas unitarias utilizando JUnit [11]. Busca en los paquetes de código fuente las clases cuyo nombre termine en Test (según el convenio de nombrado de JUnit), ejecuta las pruebas y genera informes con los resultados de las mismas en formato HTML. • make-war: Genera un fichero WAR (Web Application Archive) con la aplicación, necesario para la posterior generación del fichero EAR. • make-ear: Genera un fichero EAR (Enterprise Application Archive) con la aplicación, que podrá ser desplegado en un contenedor J2EE. • deploy.localhost: Despliega la aplicación en el contenedor J2EE instalado en la máquina local. • undeploy.localhost: Desinstala la aplicación del contenedor J2EE instalado en la máquina local. • deploy.desa: Despliega la aplicación en el contenedor J2EE instalado en la máquina cuya IP está contenida en la variable desa.test.host. • undeploy.desa: Desinstala la aplicación del contenedor J2EE instalado en la máquina cuya IP está contenida en la variable desa.test.host. • new: Crea un nuevo proyecto a partir del proyecto actual, para ello es necesario pasarle el nombre del proyecto nuevo mediante el parámetro -Dapp.name=proyectoNuevo. Esto copiará el proyecto actual, al mismo nivel de directorio y sustituye el nombre del proyecto en los ficheros de configuración que sea posible.Pruebas unitarias Es muy recomendable la implementación de pruebas unitarias, al menos para todos los componentes críticos de la aplicación. Es también recomendable, en aplicaciones web, implementar pruebas unitarias para todos los objetos de acceso a datos (DAO). Para facilitar esta tarea se puede utilizar la librería dbUnit y la clase PrincastDatabaseTestCase, suministrada en el openFWPA.Instalación de la aplicación de ejemplo (Sample App) Para instalar la aplicación de ejemplo (Carrito) se deben seguir los pasos descritos en los siguientes apartados. 11
  • 19. Presentación de openFWPAConfiguración de la seguridad Para habilitar la seguridad en la aplicación de ejemplo deben seguirse los pasos especificados en el documento [Manual de Operaciones].Configuración de la base de datos Esta aplicación utiliza una base de datos MySQL. Se ha de copiar el driver JDBC para MySQL (mysql- connector-java-3.0.12-production-bin.jar) en el directorio {OC4J_HOME}/j2ee/ home/applib. Para instalar la base de datos es necesario ejecutar la tarea ANT createdb incluida en build.xml (quizá sea necesario cambiar el usuario y contraseña para conectarse a MySQL). A continuación se edita el fichero data-sources.xml, que se encuentra en el directorio {OC4J_HOME}/j2ee/home/config, y se le define un nuevo origen de datos para la aplicación añadiéndole el siguiente código al fichero: <data-source class="com.evermind.sql.DriverManagerDataSource" name="MySQLDS" location="jdbc/CarritoDS" xa-location="jdbc/xa/CarritoXADS" ejb-location="jdbc/MySQLDS" connection-driver="org.gjt.mm.mysql.Driver" username="admin" password="" url="jdbc:mysql://localhost/carrito" inactivity-timeout="30"/> Si el servidor de base de datos no se encuentra en la misma máquina que OC4J, sustituir localhost por el nombre o la dirección a dicha máquina. Hacer que los campos username y password coincidan con los de algún usuario de MySQL con privilegios para acceder a la base de datos. Llegados a este punto es necesario re iniciar el OC4J. Una vez re iniciado ejecutar el target deploy.localhost del fichero build.xml, si se ejecuta desde la máquina donde está instalado OC4J, o deploy.desa si se trata de una máquina remota (en este caso cambiar la variable desa.test.host del fichero build.xml debe apuntar a la IP del servidor). Una vez completado el proceso de instalación, la aplicación estará disponible desde la dirección http:// localhost:8888/carrito. Para tener acceso al sistema puede utilizar como parámetros de autenticación los siguientes: • Identificador de usuario: cliente. • Contraseña: cliente. 12
  • 20. Capítulo 2. Arquitectura Modelo -Vista-Controlador con openFWPAMVC El patrón MVC – Model 2 puede ser visto como una implementación del lado del servidor del patrón de diseño Modelo-Vista-Controlador (MVC). Este patrón describe cómo debe implementarse una aplicación con tres elementos básicos: Modelo Se trata de las entidades del dominio del problema, implementadas con total independencia de su presentación. Vista (Presentación) Esta capa se encarga de mostrar las entidades del modelo al usuario. En el openFWPA, se implementa esta capa sobre la tecnología JSP. En esta capa, no hay lógica de negocio Controlador Traduce eventos/operaciones realizadas sobre la vista a invocaciones de métodos en el modelo. En el openFWPA se emplean servlet para esta capa. Básicamente, en esta capa se procesa la petición de entrada de un cliente, se accede a las entidades del modelo y se coloca cualquier elemento a pasar a la vista en algún ámbito de aplicación (request, session, etc.). Asimismo, dispara un evento que se mapeará a una página jsp que mostará los resultados. Esta estrategia da lugar a una separación entre presentación y contenido, produciéndose una clara definición de los roles y responsabilidades de los desarrolladores y diseñadores de páginas, en los equipos de programación. De hecho, cuanto más compleja sea la aplicación, mayores son los beneficios de utilizar la arquitectura de Modelo 2. Figura 2.1. Modelo Vista Controlador El proyecto Struts de la Apache Software Foundation es una implementación del MVC Modelo 2. El núcleo del framework Struts es una capa de control flexible basada en tecnologías estándar como servlets, JavaBeans, ResourceBundles y XML, así como varios paquetes del proyecto Jakarta Commons. (http://jakarta.apache.org/commons). Struts suministra su propio componente controlador (Controller) y se integra con otras tecnologías para proporcionar el Modelo y la Vista. Para el Modelo, Struts puede interactuar con tecnologías de acceso a datos estándar, como JDBC y EJB, así como con la mayoría de paquetes de terceras partes, como Hibernate, iBATIS, u Object Relational Bridge. Para la Vista, Struts trabaja bien con JSPs, incluyendo JSTL y JSF, así como con Velocity, XSLT y otros sistemas de presentación. Actualmente, el framework del Principado de Asturias sólo da soporte a JDBC y JSP. La figura siguiente muestra como es el ciclo petición-accion-jsp del framework Struts: Figura 2.2. Ciclo petición-acción-jsp de Struts Para obtener información más detallada sobre Struts consultar el tutorial que se adjunta en la documentación de openFWPA. 13
  • 21. Arquitectura Modelo -Vista- Controlador con openFWPA Figura 2.3. Estructura de capas de las aplicaciones web con openFWPADesarrollo de la VistaAspecto corporativo de las aplicaciones del Principadode Asturias Las aplicaciones construidas bajo los estándares del openFWPA de desarrollo J2EE se integrarán en el portal del Principado de Asturias ya existente (http://www.princast.es) tanto en internet como intranet. Por lo tanto debe respe-tarse el “look & feel” del portal en la medida de lo posible. Se establece como premisa la construcción de un “look & feel” ligeramente diferenciado, pero que a su vez respete la imagen corporativa del Principado de Asturias. Para lograr este objetivo, se ha partido de la hoja de estilos general.css propiedad del Principado de Asturias, y en base a ella se han desarrollado nuevas hojas de estilos que establezcan el aspecto de la vista de las aplicaciones construidas bajo el framework. Estas hojas de estilo permiten separar las instrucciones de formateo (posición, color, tamaño, etc) del código HTML generado por la aplicación. Esto ofrece una mayor sencillez al desarrollo y una mayor adaptabilidad al cambio - en caso de ocurrir cambio de imagen corporativa, se minimiza el ámbito del cambio unas pocas hojas de estilo CSS. Figura 2.4. Aspecto corporativo del portal princast.es Figura 2.5. Aspecto de la aplicación de ejemplo (Sample App)Cascading Style Sheets (CSS) La aplicación ejemplo (Sample App) maneja 5 hojas de estilos CSS. Debe tomarse esta implementación como referencia de posicionamiento y formateo de textos, bloques, párrafos, etc. En general, se prohíbe el uso de directrices de estilo dentro del código HTML. Cualquier estilo o posicionamiento de bloques deberá ir contenido en una hoja de estilos CSS.Hojas de estilo en la aplicación de ejemplo (Sample App) Las hojas de estilo son enlazadas a través de la página head.jsp. En caso de necesitar nuevas hojas de estilo, se utilizará este componente para hacerlo, de forma que esta tarea quede totalmente centralizada. El código actual de la página head.jsp es: <!-- Css basicas --> <link rel="stylesheet" type="text/css" href="../../css/general.css" /> <link rel="stylesheet" type="text/css" href="../../css/position.css" /> <link rel="stylesheet" type="text/css" href="../../css/princast-ui.css" /> <!-- Css para el menú Tabs --> <link rel="stylesheet" type="text/css" href="../../css/tabs.css" /> 14
  • 22. Arquitectura Modelo -Vista- Controlador con openFWPA<!-- Css para los listados --><link rel="stylesheet" type="text/css" href="../../css/displaytag.css" /><!-- Css especifica de la aplicacion --><link rel="stylesheet" type="text/css" href="../../css/carrito.css" />Las hojas de estilo manejadas por la aplicación de ejemplo SampleApp son:general.css proviene de la hoja de estilos de referencia con el mismo nombre, incluida en el portal princast.es. Ha sufrido ligeras modificaciones para adaptarse a las necesidades del framework PA. Establece los estilos para los elementos más comunes de una página HTML (enlaces, tablas, celdas, párrafos, listas, textos…)position.css define el posicionamiento de los bloques <div> dentro de la página. La estructura de una página se ha definido en base a bloques, de los cuales no todos tienen porque aparecer, según las necesidades de página. Para más información, véase los apartados correspondientes a los layouts tiles.princast-ui.css hoja de estilos para el estilo de los componentes de las etiquetas de princast para las páginastabs.css hoja de estilos para el tabbed menu.displaytag.css hoja de estilos exclusiva para el aspecto de las tablas generadas por el tag displaytag (Ver ???). El displaytag genera listas paginadas.carrito.css Hoja de estilo para la ubicación y formato de componentes específicos de la aplicación de ejemplo.Estos ficheros CSS definen los estilos para aplicaciones de tramitación. Además de estas hojas de estilo,se incluyen en el openFWPA ficheros CSS que definen estilos para aplicaciones de portal. Estas hojas deestilo son: componentsPortal.css, displaytagPortal.css y carritoPortal.css.Según lo expuesto, el código de las páginas JSP debe reducirse al mínimo imprescindible, obteniendo asíun código mucho más claro y mantenible.Ejemplo: código JSP del cuerpo de una página de la aplicación Sample App:<%@ page errorPage="/pages/errorEnJSP.jsp" %><%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean" %><%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html" %><%@ taglib uri="http://jakarta.apache.org/struts/tags-logic" prefix="logic" %><%@ taglib uri="http://displaytag.sf.net" prefix="display" %><%@ taglib uri="/WEB-INF/tld/princast-ui.tld" prefix="ui" %><html:xhtml /><div id="cuerpo"> <ui:errors/> <ui:box bodyId="productos_box"> <ui:box-caption headingLevel="1"> <bean:message key="productos.box.caption" /> </ui:box-caption> <display:table name="sessionScope.ListaProductoKey" id="listProd" 15
  • 23. Arquitectura Modelo -Vista- Controlador con openFWPA pagesize="3" export="false" sort="page" requestURI="../../../action/viewlistaproducto?paginate=true" summary="Listado de productos" > <display:column> <%=listProd_rowNum%> </display:column> <display:column titleKey="productos.column.name" sortable="true" > <bean:define id="nombreProducto" name="listProd" property="name" toScope="page" <html:link action="/viewdetalleproducto" paramId="id" paramName="listProd" para <bean:write name="listProd" property="name" /> </html:link> </display:column> <display:column titleKey="productos.column.description" property="description" / <display:column titleKey="productos.column.basePrice" property="basePrice" sorta <display:column> <bean:define id="url" name="listProd" property="smallImageURL" /> <bean:define id="nombre" name="listProd" property="name" toScope="page"/> <html:img styleClass="imagen_producto" src="<%=url%>" alt="<%="Imagen de " + </display:column> <display:column titleKey="productos.column.moreInfo" sortable="false" class="cen <html:link action="/viewdetalleproducto" paramId="id" paramName="listProd" para <html:img src="../../images/icon_info_sml.gif" altKey="label.masinformacion" </html:link> </display:column> </display:table> </ui:box> <html:img styleClass="carrito_image" src="../../images/productos.jpg" alt=""/> <span id="pdf_link"> <html:link styleClass="enlace_imagen enlace_pdf" action="/viewlistaproductopdf" t <bean:message key="productos.descargaPDF"/> </html:link> </span> </div> El código anterior responde a la forma en que se construye un cuerpo de página. No se ha utilizado en ningún caso directrices de estilo o posicionamiento dentro de este código, y en esta forma resulta más claro, donde se atiende únicamente a lo que debe mostrar la página y no a como y dónde debe mostrarlo.Desarrollo del ControladorDeclaración de Actions Desde la versión 1.5, las aplicaciones desarrolladas con el openFWPA, están basadas en el framework Spring. Para poder inyectar dependencias en las Actions de las aplicaciones, es necesario que éstas sean definidas como beans de Spring. En concreto, las definiciones de Actions se realizarán ahora en el fichero: beans/web/action-beans.xml, este fichero se debe ubicar en el CLASSPATH. <bean id="login" class="es.princast.sampleapp.web.action.LoginAction" singleton="fa 16
  • 24. Arquitectura Modelo -Vista- Controlador con openFWPA </bean> <bean id="logout" class="es.princast.sampleapp.web.action.LogoutAction" singleton= <property name="carritoDelegate"><ref bean="carritoDelegate" /></property> </bean> Mientras que las Actions (clases) se declaran ahora en el fichero action-beans.xml, todos los aspectos relativos a la navegación (forwards), mappings, formularios, etc. se sigue definiendo en el fichero struts- config.xml. En cada uno de los <action-mappings>, se debe inidcar el identificador (id) del bean que implementa la lógica de la Action, utilizando el atributo "type". Si el valor de este atributo es el identificador de un bean (de la clase Action), se tomará dicho bean para procesar las peticiones. Si el valor del atributo "type" es el nombre de una clase (una Action), se instanciará normalmente (como en versiones anteriores del openFWPA). <action path="/login" type="login" scope="request" validate="false" input="/pages/l <forward name="success" path="/action/viewperfil" redirect="true" /> <forward name="failure" path="/action/login" redirect="true" /> </action> <action path="/logout" type="logout" scope="request"> <forward name="success" path="/action/login" redirect="true" /> </action> En el código anterior se puede ver cómo se realiza el mapeo, en el fichero struts-config.xml, de las Actions definidas más arriba como beans de Spring. Atención Para poder realizar este tipo de mapeos es necesario utilizar como controlador (Controller) la clase PrincastRequestProcessor: <controller processorClass="es.princast.framework.web.action.PrincastRequestProcJerarquía de Actions En el openFWPA se proporciona un conjunto de Actions de Struts. Estas Actions definen un nuevo ciclo de ejecución diferente del existente en las Actions típicas de Struts (ver más adelante). Las aplicaciones que utilicen el openFWPA deben, obligatoriamente extender las Actions del Framework. Además de ser una imposición, las Actions del openFWPA ofrecen funcionalidad de uso habitual en las aplicaciones web. Figura 2.6. Jerarquía de ActionsPrincastAction La clase base de la jerarquía es PrincastAction. Es una clase abstracta que implementa una máquina de estados de la que podrán hacer uso el resto de Actions. Define métodos que deben ser sobrescritos por las actions de la aplicación. Estos métodos sobrescritos serán invocados por el framework para dar respuesta a una solicitud de un cliente, y en un orden preestablecido. Este orden se presenta como un esquema de la máquina de estados: 17
  • 25. Arquitectura Modelo -Vista- Controlador con openFWPAFigura 2.7. Maquinaria de Estados de PrincastActionCada uno de los métodos que aparecen en la figura anterior tiene un cometido en particular. Este cometidoes el siguiente:preProcess () Se emplea para comprobar las precondiciones que debe cumplir la PrincastAction. En caso de que no se cumpla alguna precondición se debe dejar un registro de ello mediante la creación de un error o un mensaje, dependiendo de la gravedad del mismo. Al dejar constancia de la incidencia se redireccionará el flujo de ejecución hacia una página de error o a una de alerta, invocándose findFailure() y findAlert(), respectivamente. La forma de crear una incidencia se detalla en la sección 4.5.1.1.1.executeActionLogic () Implementa la lógica de negocio de la PrincastAction. Éste será el método sobrescrito de forma obligatoria por todas las acciones que hereden de PrincastAction.catchException() Se encarga del tratamiento de cualquier excepción que se pueda lanzar durante la ejecución de la lógica de negocio de la PrincastAction. Si no se quiere que la excepción sea lanzada de nuevo debe notificarse su tratamiento mediante la llamada al método unsetException(). De esta forma se entenderá que todo el tratamiento necesario ya ha sido llevado a cabo y la excepción no será elevada. 18
  • 26. Arquitectura Modelo -Vista- Controlador con openFWPA postProcess() Se emplea para comprobar las poscondiciones que debe cumplir la PrincastAction. En caso de que no se cumpla alguna poscondición se debe dejar constancia de ello mediante la creación de un error o un mensaje. Al dejar constancia de la incidencia se redireccionará el flujo de ejecución hacia una página de error o a una de alerta, invocándose findFailure() y findAlert(), respectivamente. La forma de crear una incidencia se detalla en la sección 4.5.1.1.1. findFailure() Redirecciona a una página de error. Por defecto, la redirección se hace a lo que se indique en el atributo input de la Action. En caso de que este atributo no sea definido se intentará hacer la redirección a un forward llamado “failure”. findAlert() Redirecciona a una página de alerta en la que se muestra un mensaje informativo. Por defecto la redirección se hace a un forward llamado “warning”. findSuccess() Redirecciona a la página de éxito, es decir, a aquella a la que se debería ir si la ejecución de la acción no tiene ningún error. Por defecto se redirecciona a un forward llamado “success”.Creando un error en una PrincastAction El openFWPA posee soporte integrado a la gestión de errores para usuario. Por error se entiende cualquier situación anómala en la aplicación, sea por un fallo del sistema o por datos incorrectos suministrados por el usuario. Los errores que no son tratados por las aplicaciones se muestran al usuario final. Atención El método saveErrors(HttpServletRequest, List) está deprecado a partir de la versión 1.5 del openFWPA . Los errores ya no se deben almacenar directamente en la request, en su lugar, se utiliza el almacenamiento interno de las Action . Si se utiliza el método deprecado, las Actions pueden no funcionar correctamente. A continuación se muestra un ejemplo de creación de un error: protected void preProcess(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { java.util.List error = new java.util.ArrayList(); error.add(“error.general”); saveErrors(error); }; Para crear un error, se debe crear una instancia de java.util.List a la que se le añadirán hasta cinco elementos. El primero de estos elementos es la clave asociada al mensaje de error en el fichero de recursos. En caso de que el mensaje de error sea una simple cadena de caracteres (como ocurre en el ejemplo) bastará con un solo parámetro. En caso de que el mensaje lleve parámetros de la forma {0},{1},{2},{3}, un posible mensaje sería: error.general=Ha ocurrido un error de tipo {0} a las {1} horas en {2} con usuario { En este caso, la creación del error sería como sigue: java.util.List error = new java.util.ArrayList(); error.add(“error.general”); 19
  • 27. Arquitectura Modelo -Vista- Controlador con openFWPA error.add(“GRAVE”); // Parámetro {0} error.add(“15:30”); // Parámetro {1} error.add(“Gestión de usuarios”); // Parámetro {2} error.add(“Administrador”) // Parámetro {3} El usuario de la aplicación vería el siguiente mensaje: Ha ocurrido un error de tipo GRAVE a las 15:30 horas en Gestión de usuarios con usuCreando un mensaje de advertencia en una PrincastAction La forma de crear un mensaje de advertencia es similar al de la creación de un mensaje de error, con la salvedad de que en lugar de llamar al método saveErrors(HttpServletRequest, List) se ha de invocar el método saveMessages(List).Modificando una redirección En el curso de tratamiento de una petición, puede ser necesario redirigir la petición a otro servlet. PrincastAction proporciona una implementación por defecto para las redirecciones que pueden tener lugar durante la ejecución de una petición a una acción. Los métodos que se encargan de estas redirecciones son: findSuccess() Redirecciona a un forward etiquetado “success”. findFailure() Redirecciona a lo que se indique en el atributo input del elemento <action> correspondiente o a un forward etiquetado “failure” en caso de que no se defina el atributo input. findAlert() Redirecciona a un forward etiquetado “warning”. Todos ellos siguen la misma signatura: ActionForward find<redireccion> (ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response); La forma de modificar la redirección de estos métodos es devolviendo una instancia diferente del ActionForward. Por ejemplo, imaginemos que cuando la ejecución de la PrincastAction es éxitosa deseamos que se nos redireccione a un forward etiquetado como “ok”. En este caso, deberíamos sobrescribir el método findSuccess() como se muestra a continuación: ActionForward findSuccess(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { return mapping.findForward(“ok”); }; Como se puede apreciar en el ejemplo anterior, la cuestión es obtener del mapping (o crear indicando el path) un ActionForward a donde deseamos redireccionar la repuesta. ActionForward findSuccess(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { return new ActionForward("ok", "/action/test?method=ok", true); }; 20
  • 28. Arquitectura Modelo -Vista- Controlador con openFWPAAlmacenamiento interno de una Action Las Actions de Struts no son thread-safe. No es correcto utilizar atributos de instancia para compartir información entre los distintos métodos del ciclo de vida de una Action. En las ocasiones en que fuera indispensable utilizar un atributo de instancia, se recomienda utilizar el almacenamiento interno de la Action. Este almacén es un mapa de parámetros thread-local, cuyo ámbito se limita a los métodos del ciclo de vida de la Action (preProcess(), executeActionLogic(), catchException() y postProcess()). Figura 2.8. Almacenamiento de la Action Para acceder y manipular este almacenamiento, la PrincastAction dispone de los siguientes métodos: deleteActionParameter(nombre) del almacén el parámetro especificado. Borra getActionParameter(nombre)Obtiene del almacén el parámetro cuyo nombre se especifica. getActionParameters() Obtiene un iterador con los nombres de todos los parámetros del almacén. setActionParameter(nombre,Almacena un parámetro identificándolo con el nombre dado. valor) A continuación se muestra un ejemplo de uso de este almacén: public class MyAction extends PrincastAction { public MyAction(){ setActionParameter("oneParam", "oneValue"); } protected void preProcess(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { setActionParameter("myParam", "foo"); } protected void executeActionLogic(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { String param1 = (String) getActionParameter("myParam"); String param2 = (String) getActionParameter("oneParam"); … } } En la Action del ejemplo: MyAction, se han sobrescrito dos métodos del ciclo de vida de la Action: preProcess() y executeActionLogic(). En el método preProcess(), se establece el valor de un parámetro: “myParam”, asignándole la cadena “foo”. Por otro lado, en el constructor, se establece un valor para el parámetro “oneParam”. En el método executeActionLogic(), se recuperan los valores de ambos parámetros. Recuérdese que únicamente los métodos del ciclo de vida de la ejecución de la Action tienen visibilidad del almacén. Por este motivo, en el método executeActionLogic(), la variable param1 tomará el valor “foo”, mientras que la variable param2 tendrá como valor null. 21
  • 29. Arquitectura Modelo -Vista- Controlador con openFWPAInterrupción de la maquinaria de estados de la Action En algunas ocasiones es necesario interrumpir el proceso de la maquinaria de estados de la Action sin redirigir a un estado de error. Para interrumpir la ejecución de la Action, basta con disparar una excepción de tipo ActionProcessInterruption. Un uso práctico de esta excepción es el siguiente:Paginación sin reejecución de la lógica de negocio. El problema es el siguiente: utilizando la librería Display Tag, cada vez que se produzca un movimiento de página, se solicita una nueva ejecución de la Action que genera el listado, suponiendo esto la reejecución de la lógica de negocio completa (con acceso a datos incluido). La solución a este problema es la que sigue: • Almacenar siempre las listas de bean a mostrar por el Display Tag en el scope session. • En la etiqueta del Display Tag, en el atributo “requestUri”, añadir a la URL de la Action un parámetro GET (que la no entre en conflicto con alguno que ya utilice la Action). <display:table name="sessionScope.ListaProductoKey" align="center" id="listProd" pagesize="3" export="false" sort="page" requestURI="../../action/viewlistaproducto?paginate=true"> • Extender el método preProcess(). En este método se detectará la existencia del parámetro definido y, en tal caso, se disparará una ActionProcessInterruption. protected void preProcess(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { if (request.getParameter("paginate") != null) { throw new ActionProcessInterruption(); } } Otra opción es utilizar una de las Actions que ya vienen con esta funcionalidad implementada: PrincastCRUDAction y PrincastListAction.Diferentes tipos de PrincastAction Además de la clase base PrincastAction, en el openFWPA se proporcionan otros tipos de Actions. Por un lado están PrincastDispatchAction y PrincastCRUDAction, una generalización de la anterior, y por otro una serie de implementaciones concretas de la PrincastAction tratada en el punto anterior que facilitan el desarrollo de funcionalidades recurrentes en aplicaciones de gestión.Implementaciones concretas Existen varias clases que tienen una funcionalidad determinada y que pueden ser reutilizadas tal y como están. Estas son: • PrincastExistAttributeAction • PrincastRemoveAttributeAction 22
  • 30. Arquitectura Modelo -Vista- Controlador con openFWPA • PrincastForwardAction • PrincastParameterActionPrincastExistAttributeAction Esta clase se encarga de verificar la existencia de un atributo en alguno de los scopes o ámbitos (request, session o application) de la aplicación web. En la propiedad "parameter" del <action- mapping> se indicará el scope y el nombre del atributo a eliminar, separados por ";". Por ejemplo: parameter="application;HOURS". Si se quiere buscar el atributo en cualquier scope se utilizará un *. Por ejemplo: parameter="*;HOURS". Si no se especifica alguno de los dos parámetros, se produce un error.PrincastRemoveAttribute Esta clase trata de eliminar un atributo dentro de uno de los ámbitos posibles (application, request, session). Si el atributo existe devuelve el control a un ActionForward etiquetado con “success” y, sino, a uno etiquetado con “failure”. Tanto el ámbito como el atributo se pasan en la propiedad parameter de ActionMapping separados por ";" (parameter="application;HORAS"). Para indicar que la búsqueda se realice en todos los ámbitos, el primer parámetro debe ser un asterisco ("*") en lugar del nombre de un ámbito (parameter="*;HORAS"). El atributo sólo será eliminado del primer contexto en el que sea localizado.PrincastForwardAction Acción que redirecciona a la URI relativa al contexto especificada por la propiedad parameter del ActionMapping. Esta clase puede ser usada para integrar la aplicación con otra lógica de negocio de otros componentes implementados como Servlets o páginas JSP, pero manteniendo la funcionalidad del Servlet controlador de Struts (como el procesado de form beans). Para configurar una PrincastAction de este tipo en el fichero struts-config.xml es necesario crear una etiqueta como ésta : <action path="/guardaSuscripcion" type="es.princast.framework.web.action.PrincastForwardAction" name="suscripcionForm" scope="request" input="/suscripcion.jsp" parameter="/path/a/servlet"/> que redireccionará el control a la URI relativa al contexto / path/a/servlet .PrincastParameterAction Esta Action busca un parámetro en la request llamado dispatch y usa su valor para recuperar un forward local. Una vez conseguido este forward busca un segundo parámetro en la request cuyo nombre debe ser especificado en la propiedad parameter del ActionMapping. Este valor se concatena con el valor de la propiedad path del forward obtenido con anterioridad. La URI resultante es la que se usa para hacer la redirección. Un ejemplo de la declaración de una PrincastParameterAction de este tipo es: <action path="/menu/busca" type="es.princast.framework.web.action.PrincastParameterAction" name="menuForm" validate="false" 23
  • 31. Arquitectura Modelo -Vista- Controlador con openFWPA parameter="keyValue"> <forward name="titulo" path="/do/busca/Titulo?titulo=" /> <forward name="autor" path="/do/busca/Autor?autor=" /> <forward name="contenido" path="/do/busca/Contenido?contenido=" /> </action> Un fragmento de una página JSP que hiciera uso de esto podría ser: <html:form action="menu/busca"> Busca artículos por : <html:select property="dispatch"> <html:option value="titulo">Titulo</html:option> <html:option value="autor">Autor</html:option> <html:option value="contenido">Contenido</html:option> </html:select> <html:text property="keyValue" /> <html:submit>Enviar</html:submit> </html:form> Si el usuario elige Contenido y escribe Java en el campo de texto, el navegador enviará: dispatch=contenido keyValue=Java Con esta información la PrincastParameterAction busca el forward contenido y concatena el valor de keyValue al path del forward, quedando algo del estilo: /do/busca/Contenido?contenido=Java En los forwards definidos dentro del mapping de la PrincastParameterAction es posible incluir parámetros almacenados en la request utilizando la notación ${<nombre del parámetro>}. La PrincastParameterAction buscará, en el path (definido en el forward) la cadena "${<parámetro>}" y la sustituirá por “<parámetro>=<valor de parámetro>". Si, por ejemplo, el valor del parámetro "Titulo" es "Rambo" y se define la siguiente forward: <forward name=”titulo” path=”/do/busca?${Titulo}” /> La PrincastParameterAction dirigirá a la siguiente URL: /do/busca?Titulo=Rambo.Actions Compuestas (Dispatch) En muchas ocasiones interesa tener juntas aquellas acciones que se encargan de tareas relacionadas. Los métodos que se encargan de la ejecución de tales tareas son encapsulados en una misma clase. Para estos casos están pensadas las acciones que se presentan en este apartado: PrincastDispatchAction, PrincastCRUDAction.PrincastDispatchAction Esta clase es una especialización de la PrincastAction. Mantiene la misma estructura de máquina de estados que su clase padre pero se puede decir que cada una de las acciones que encapsula dispone de su propia máquina de estados. Si por ejemplo queremos encapsular juntas las acciones “update” e “insert” tendríamos: updatePreProcess, updateExecuteActionLogic, etc. - y también insertPreProcess, insertExecuteActionLogic, etc. A pesar de que cada acción pueda tener su propia máquina de estados, puede interesar que las acciones compartan determinada funcionalidad. Para estos casos están los métodos defaultPreProcess, defaultExecuteActionLogic, etc. 24
  • 32. Arquitectura Modelo -Vista- Controlador con openFWPA ¿Cómo identificar los métodos a ejecutar? A la hora de seleccionar los métodos a ejecutar la PrincastDispatchAction hace uso del valor que se le pasa en el parámetro parameter del ActionMapping asociado. Si lo que queremos es ejecutar los métodos de la máquina de estados asociada a la acción “update”, entonces este parámetro debe ser <action-mapping …… parameter="method" ….. />, donde el valor del parámetro method, será update. Si no se da implementación a alguno de los métodos update<estado_máquina>, por ejemplo updatePreProcess(), la PrincastDispatchAction ejecutará el método defaultPreProcess(). De igual modo ocurre con el resto de métodos. Es posible desacoplar el valor del parámetro del nombre del método. Se pueden establecer mapeos {valor_de_parameter, nombre_de_método} extendiendo el método getMethodKey() de la clase PrincastDispatchAction. La PrincastDispatchAction permite, por defecto, una salida de éxito (success), otra de error (error) y para cada método de la Action. Por convenio, en la PrincastDispatchAction, la salida de éxito de un método será un forward cuyo nombre será el mismo que la clave del método. El forward de error equivaldrá al nombre del método concatenado con la cadena “-failure”. Para el forward de advertencia se concatenará la cadena “-warning” al nombre del método. <action path="/customDispatchAction" name="aForm" parameter="method" type="customDispatchActionBean" validate="false" scope="session"> <forward name="method1" path="success.path" /> <forward name="method1-failure" path="failure.form" /> <forward name="method2" path="success.dos.path" /> <forward name="failure" path="failure.path" /> </action> En el ejemplo superior, se está mapeando una Action de tipo PrincastDispatch con dos métodos: method1 y method2. Cuando se ejecute con éxito el método method1, se redireccionará al path: “success.path”. Si hay algún error, la redirección se realizará al path: “failure.form”. Por el contrario, cuando se ejecute el método method2, en caso de éxito la redirección se hará al path: “success.dos.path” y cuando se produzca un error, el path de redirección será: “failure.path” (ya que, aunque no ha sido definido un forward de error específico, se ha definido el forward de error por defecto: “failure”). Figura 2.9. Esquema de la PrincastDispatchAction del ejemplo En ocasiones, es necesario que una DispatchAction tenga mayor control sobre las redirecciones (forwards) que debe realizar para cada uno de los métodos de dispatch. Al igual que ocurre con otros métodos de la Action (executeActionLogic(), catchException(), etc.) es posible redefinir los métodos de redirección (findSuccess(), findAlert() y findFailure()). El sistema es exactamente el mismo: prefijar cada método con la clave (MethodKey). Por ejemplo: method1FindSuccess(), method2FindFailure(), etc.PrincastLookupDispatchAction La clase PrincastLookupDispatchAction pemite implementar un tipo especial de Dispatch Actions para formularios con mas de un botón (submit). Es este escenario, el botón que se utilice para el envío del formulario (submit) será quien determine el método que se ejecutará en la Action. 25
  • 33. Arquitectura Modelo -Vista- Controlador con openFWPATodas las actions lookup deben manejar formularios que extiendan la clase LookupDispatchForm, yaque será esta clase quien se encargue de gestionar las correspondencias entre los botnoes del formularioy los claves para seleccionar los métodos de la Action.Para extender LookupDispatchForm se debe implementar el método getButtonKeys(),devolviendo un array que contendrá las posibles claves que se contemplan para seleccionar el método aejecutar. Por otro lado, el formulario maneja otro array (buttons), del mismo tamaño, con una posiciónreservada para cada botón.Al enviarse el formulario, el array buttons, tendrá todos sus campos nulos, salvo el correspondienteal botón utilizado para el envío (submit). Para seleccionar el método a ejecutar, se utilizará la clavealmacenada, en el array de claves, en la misma posición que el botón activo.En el ejemplo que se muestra a continuación, se presenta un formulario con tres botones "Aceptar","Volver" y "Cancelar". Cada uno de estos botones ejecuta un método distinto: "foo1" para "Aceptar","foo2" para "Volver" y "foo3" para "Cancelar".public class FooLookupForm extends LookupDispatchForm { public String[] getButtonKeys() { return new String[]{"foo1", "foo2", "foo3"}; }}En el ActionForm, basta con ordenar los botones y asignarle una clave a cada uno: foo1, foo2 y foo3.public class FooLookupAction extends PrincastLookupDispatchAction { protected void foo1ExecuteActionLogic(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { //Implementar logica de negocio } protected void foo2ExecuteActionLogic(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { //Implementar logica de negocio } protected void foo3ExecuteActionLogic(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { //Implementar logica de negocio }}Las PrincastLookupDispatchAction, desde el punto de vista de su implementación sonexactamente iguales que las PrincastDispatchAction habituales.<html:form action="test"> <html:submit property="buttons[0]" value="Aceptar"/> 26
  • 34. Arquitectura Modelo -Vista- Controlador con openFWPA <html:submit property="buttons[1]" value="Volver"/> <html:submit property="buttons[2]" value="Cancelar"/> </html:form> En la JSP cada botón submit se debe asignar, por orden, a una entrada del array "buttons".PrincastCRUDAction Esta Action está pensada para la gestión del ciclo de vida de entidades del modelo de la aplicación. Esta dos Action define los métodos del ciclo de vida de una entidad: new Este método debe precargar los campos necesarios para mostrar el formulario de creación de una nueva entidad. retrieve Este método permitirá recuperar una entidad. list Este método debe obtener listados de entidades. delete Permite borrar una entidad. update Permite actualizar los datos de una entidad. create Este método servirá para inserter nuevas entidades. Atención Para poder utilizar correctamente este tipo de Actions es necesario mapearlas dos veces en el fichero struts-config.xml. Uno de los mapeos tendrá la validación de formularios desactivada (validate = true) y se utilziará para solicitar los métodos que no requieren un formulario: new, retrieve, list y delete. El otro mapeo tendrá la validación activada y se utilizará para los métodos que sí requieren formulario: update y create. <action path="/productosAction" type="productosActionBean" input="facturas.listaProductos" validate="false" scope="request" name="productoForm"> <forward name="list" path="facturas.listaProductos"/> <forward name="retrieve" path="facturas.listaProductos"/> <forward name="new" path="facturas.listaProductos"/> <forward name="delete" path="facturas.listaProductos"/> </action> <action path="/productosFormAction" type="productosActionBean" input="facturas.listaProductos" validate="true" scope="request" name="productoForm"> <forward name="create" path="facturas.listaProductos"/> <forward name="update" path="facturas.listaProductos"/> </action> Estas Action también soportan, además, paginación sin necesidad de reejecutar la lógica de negocio (Ver “Paginación sin reejecución de la lógica de negocio.”). Basta con incluir en la request el parámetro 27
  • 35. Arquitectura Modelo -Vista- Controlador con openFWPA “paginate”. En el caso de esta Action, al contrario que la PrincastListAction, hay que registrar el objeto que se devuelve para listar, explícitamente en la session.Validación de Formularios en acciones compuestas La validación de formularios, en el framework Struts, redirecciona de forma automática, en caso de error, a una página de “input” definida en el mapeo de la action (en el fichero struts-config.xml). Este sistema tiene una limitación y esta es que las Actions compuestas (DispatchAction) solamente pueden definir una única página de “input” para todos sus métodos. El openFWPA permite solucionar esta limitación del framework Struts. Para ello, basta con seguir los siguientes pasos: 1. Utilizar el controlador PrincastTilesRequestProcessor (deprecado) o PrincastRequestProcessor. Para ello, es necesario incluir la siguiente definición de controlador en el fichero struts-config.xml: <!-- Para poner multiples input --> <controller processorClass="es.princast.framework.web.action.PrincastRequestProcessor" / > 2. En el mapeo de la action compuesta (DispatchAction) que tiene más de una entrada, dejar la definición de input vacía. 3. Para cada método de la Action, definir un forward utilizando el siguiente convenio de nombrado: “<nombre del metodo>Input”. <action path="/productosFormAction" type="productosFormAction" parameter="method" validate="true" scope="request" name="productoForm"> <forward name="create" path="/action/productosAction?method=list"/> <forward name="update" path="/action/productosAction?method=list"/> <forward name="createInput" path="facturas.addProducto"/> <forward name="updateInput" path="facturas.detalleProducto"/>Actions para Listados Un subconjunto especial de Actions son aquellas que no tienen ninguna lógica de negocio especial. Su único objetivo es obtener un conjunto de objetos para ser mostrados. En función de si el listado se mostrará en una página HTML o en un PDF, se utilizará la PrincastListAction o la PrincastPDFReportAction. Figura 2.10. Esquema de las Actions para listadosPrincastListAction Si una Action tiene únicamente como propósito obtener un listado, se puede utilizar la PrincastListAction. No hace falta sobrescribir ningún método del ciclo de vida de esta Action, basta con implementar el método getContentList() y devolver el objeto (o colección de objetos) que serán mostrados. El objeto devuelto quedará registrado en sesión, bajo la clave que se especifique en el atributo parameter, en el mapeo de ese action, en el fichero struts-config.xml. 28
  • 36. Arquitectura Modelo -Vista- Controlador con openFWPA En caso de que no se especifique ningún valor para el atributo parameter se disparará una excepción de tipo PrincastActionProcessException Esta Action permite realizar paginación sin necesidad de reejecutar la lógica de negocio (Ver “Paginación sin reejecución de la lógica de negocio.”).PrincastPDFReportAction Esta Action permite obtener un listado en formato PDF utilizando las utilidades para generación de informes de openFWPA (Ver “Generación de Informes”). Para implementar una “Report Action”, basta con redefinir el método getReport(), devolviendo un objeto proveedor de contenido PDF (PDFProvider), por ejemplo, un objeto PrincastReport o PrincastMultiReport. Habitualmente, los informes compilados (en formato .jasper) se almacenan juntos en una misma carpeta. Para facilitar la carga de los ficheros “.jasper”, la clase PrincastPDFReportAction implementa el método loadReport() que devuelve el InputStream correspondiente al fichero del informe. Este método, supone que todos los informes se encuentran en la misma carpeta (por defecto: / WEB-INF/reports). Para buscar los informes en una carpeta distinta, se debe sobrescribir el método getRelativePathToReportFolder().PrincastDispatchPDFReportAction Este Action es la versión dispatch de la PrincastPDFReportAction, permite definir varios métodos para obtener el PDFProvider, por ejemplo, si el parámetro pasado al Action es myMethod se ejecutaría el método myMethodGetReport. Para más información, consultar el Javadoc de la clase y la “Actions Compuestas (Dispatch)”.PrincastXMLAction Este Action permite servir contenido XML. Para servir una respuesta XML, basta con implementar el método getXMLProvider, que retorna un proveedor de contenido XML. El proveedor de contenido XML, será una clase que implemente el interfaz XMLProvider, el cual, obliga implementar el método writeXML(Writer writer). Donde simplemente se escribirá el XML, a servir.PrincastDispatchXMLAction Este Action es la versión dispatch de la PrincastXMLAction, permite definir varios métodos para obtener el XMLProvider, por ejemplo, si el parámetro pasado al Action es myMethod se ejecutaría el método myMethodGetXMLProvider. Para más información, consultar el Javadoc de la clase y la “Actions Compuestas (Dispatch)”.Action Forms El openFWPA dispone de una clase base para el desarrollo de los beans de formulario. Se trata de la clase PrincastActionForm. Entre las propiedades destacables de esta clase se encuentran: mutable Para evitar que una PrincastActionForm sea rellenada de forma automática al hacer un forward entre diferentes acciones, establezca el valor de mutable a true y asegúrese de que todos los setters comprueban el valor de dicha propiedad (if (isMutable()) this.field = field;). locale propiedad de la clase Locale. Si la instancia de la form es mutable, se le asigna la locale de sesión de Struts siempre que se llame a reset(). Para actualizar el locale de la sesión debe usarse putSessionLocale(). 29
  • 37. Arquitectura Modelo -Vista- Controlador con openFWPAEn cuanto a los métodos:setSessionLocale(Locale) Establece el atributo locale.getSessionLocale() Devuelve el atributo locale.setMutable(boolean) Establece el valor del atributo mutable.isMutable() Devuelve el valor del atributo mutable.reset(ActionMapping, Las subclases que deseen resetear el valor de sus atributos debenHttpServletRequest) comprobar el valor de éste atributo (if (isMutable()) ...)resetSessionLocale(HttpServletRequest) locale al valor que tenga el objeto locale Cambia el atributo almacenado en la sesión e la petición en curso bajo la clave Globals.LOCALE_KEY. Cambia el atributo Globals.LOCALE_KEY de la sesión por elputSessionLocale(HttpServletRequest) atributo locale o por el Locale por defecto si el atributo locale es null.getLocaleDisplay() Devuelve el Locale del usuario o el Locale por defecto.setLocaleDisplay(String) Cambia el atributo locale a un código de lenguaje ISO dado. Recibe como atributo un String con el código del país.isBlank(String) Comprueba si el String que se le pasa es null o la cadena vacía.describe() Devuelve un Map con las propiedades de esta PrincastActionForm. Se usa el método PropertyUtils.describe(). Sobrescriba el método si considera que alguna propiedad no debería ser mostrada de este modo, o si un nombre de una propiedad debería ser cambiado. Este método devuelve las propiedades públicas.set(PrincastValueObject) Rellena las propiedades de la clase con las del PrincastValueObject que se le pasa como parámetro. Se proporciona una implementación vacía de este método para que sea sobrescrito.populate(PrincastValueObject) cargar los datos del formulario sobre un Value Object. Este Permite método recibe como parámetro el Value Object sobre el que se van a cargar los datos. Devuelve una referencia al objeto que contiene todos los datos del formulario.Para la definición de ActionForms dinámicos, se incluye en el openFWPA una clase base:PrincastDynaActionForm. Se incluye además una clase base para los formularios que van aser utilizados por dispatch actions: PrincastDispatchActionForm. Este tipo de formulariosincluyen un campo (method) para seleccionar el método de dispatch que se ejecutará paraprocesarlo. Las clases para la implementación de formularios se encuentran en el paquete:es.princast.framework.web.form.La clase LookupDispatchForm permite disponer de formularios con más de un botón desubmit. Para obtener más información acerca de este tipo de forms, véase el apartadoPrincastLookupDispatchAction en la sección dedicada a las Actions. 30
  • 38. Arquitectura Modelo -Vista- Controlador con openFWPADesarrollo de lógica de negocio Es importante disponer de un buen diseño técnico antes de programar la lógica de negocio. En este área intervienen dos tipos de objetos: Business Delegates y Business Managers. Los objetos “Delegate” se encargarán de crear y gestionar los objetos de lógica de negocio y proporcionarán un interfaz, para la aplicación web, de los métodos de negocio. Los objetos "Manager", se encargarán de implementar la propia lógica de negocio. Figura 2.11. Estructura de la capa Modelo Utilizando esta estructura, se puede modificar la implementación del servicio sin que sea necesario modificar el resto de la aplicación. Un ejemplo de implementación puede verse en la aplicación de ejemplo (Sample App). En ningún caso la lógica de negocio ha de tener dependencias con el protocolo http (como por ejemplo hacer uso de la sesión), ya que sus servicios han de poder reutilizarse desde cualquier otro entorno (como Web Services, JMS, etc.). Las únicas dependencias al protocolo concreto de acceso han de estar en las acciones (View Adapters y Actions).Service Locator El patrón de diseño “Service Locator” permite encapsular, en una clase, la localización y acceso a objetos de servidor. El openFWPA incluye un componente que implementa este patrón: la clase ServiceLocator. El ServiceLocator proporciona los siguientes métodos para la búsqueda de objetos: getDataSource() Permite instanciar un DataSource definido en el servidor (En OC4J, en el fichero data-sources.xml) getLocalHome() Obtiene un interfaz ejbHome (local) para la creación de un EJB. getRemotelHome() Obtiene un interfaz ejbHome (remoto) para la creación de un EJB. getQueue() Obtiene una cola de mensajes JMS. Obtiene una factory de conexiones a colas de mensajes JMS. getQueueConnectionFactory() getTopic() Obtiene un Topic JMS. Obtiene una factory de conexiones a Topics JMS getTopicConnectionFactory()Session EJBs Habitualmente, es necesario, cuando se trabaja con Session EJBs, gestionar, para cada uno de ellos, de forma específica el mantenimiento del contexto de la sesión (SessionContext). Para evitar la obligación de implementar los métodos de mantenimiento de la sesión, se ha incluido en el openFWPA, una clase base para los Session EJBs: PrincastSessionEJBTemplate. Esta clase implementa los métodos setSessionContext() y unsetSessionContext(), dejando la instancia del contexto en la variable protegida “context”.Value Objects Los objetos de datos (Patrón Value Object), en las aplicaciones desarrolladas sobre el openFWPA, deben implementar el interfaz PrincastValueObject. 31
  • 39. Arquitectura Modelo -Vista- Controlador con openFWPA Figura 2.12. Diagrama de Value Objects Esta interfaz define el método toXML() que permite ver una descripción del objeto en formato XML. Para facilitar la implementación de Value Objects, se han incluído dos clases base: BasePrincastVO, que realiza una implementación por defecto para el método toXML() basada en reflectividad, y BasePrincastLazyLoadingVO, que debe ser utilizada si los Value Objects de la aplicación se van a usar en conjunción con Lazy loading de los Value Objects. Ambas clases extienden la clase AbstractBasePrincastVO, que define otro método de utilidad: toPropertyBeans(). En la clase BasePrincastVO, este método permite "desmenuzar" un VO, mapeándolo a una lista de objetos de tipo PropertyBean. El nombre de la propiedad se asignará al campo "value" del PropertyBean. El valor se asignará al campo "label". Si alguna de las propiedades del VO es un objeto compuesto (una lista, una tabla, un array u otro VO) estos serán, a su vez, descompuestos. Se seguirá el siguiente convenio de nombrado para las propiedades: Propiedades de tipo VO Si una propiedad es de tipo PrincastValueObject, el nombre (PrincastValueObject) de cada una de sus propiedades se mapeará siguiendo el patrón: <nombre de la propiedad de tipo PrincastValueObject>.<nombre de cada propiedad del VO> Propiedades de tipo List o arrays El nombre de este tipo de propiedades se compone como sigue: <nombre de la propiedad>[<posición de cada una de las propiedades de la lista>] Propiedades de tipo Map El nombre de este tipo de propiedades se compone como sigue: <nombre de la propiedad>(<clave en el Map>). También se ha empaquetado en el Framework un tipo de Value Object muy habitual: PropertyBean. Este objeto es un Value Object que almacena pares {valor-etiqueta}. La clase PropertyBean también dispone de un método estático: pupulateList() que recibe como parámetro un Map y lo transforma en una lista de PropertyBeans. Debe tenerse en cuenta que la clase BasePrincastLazyLoadingVO tiene implementaciones vacías para los métodos toPropertyBeans() y toXML(), por lo que deberán ser sobreescritos por los Value Objects de la aplicación en caso de necesitar un comportamiento diferente.Excepciones El openFWPA dispone de su propia jerarquía de excepciones. La política general de manejo de excepciones en el openFWPA es que se utilicen excepciones Runtime (no manejadas estáticamente). Figura 2.13. Jerarquía de Excepciones La clase base para la creación de excepciones es PrincastException. Existen dos ramas en esta jerarquía de excepciones runtime: excepciones de sistema (PrincastSystemException), reservadas para el openFWPA y sus componentes y excepciones de modelo (PrincastModelException), que son disparadas por las excepciones. 32
  • 40. Arquitectura Modelo -Vista- Controlador con openFWPA Como norma general, las aplicaciones no beráin nunca extender las excepciones del sistema. Siempre deben extender PrincastModelException. Además, el openFWPA también tiene una clase base para la creación de excepciones gestionadas: PrincastRequiredHandlingException. La excepción DeprecatedAPIException se reserva para ser disparada desde métodos deprecados en los que no sea posible implementar una lógica alternativa.Utilidades Junto con las excepciones, se incluye una clase (ToXMLExceptionHelper) auxiliar para facilitar el fromateo de las mismas y su traducción a XML.Otras clases de utilidad Junto a las Actions, en el paquete web se incluyen algunas clases de utilidad para los componentes de la capa de aplicación.PrincastMessageFmtter Clase para facilitar el formateo de cadenas de caracteres (mensajes, etc). Esta clase permite: • Reemplazar tokens en un String. Método replace(). • Formatear un mensaje, siendo éste una cadena con parámetros del tipo {0}, {1}, … {n}. Este método (format()) recibirá como parámetros una cadena de texto y un array de objetos. El objeto en la posición 0 se introducirá en lugar de la subcadena “{0}” y así sucesivamente.PrincastUtils Contiene métodos de utilidad general. Actualmente únicamente implementa el método normalizePath() que tiene como objetivo normalizar los paths en los distintos sistemas operativos.ParameterCaster Clase de utilidad para la capa web. Permite traducir el tipo (casting) de los parámetros que se reciben de una request http.ServletPathUtils Clase de utilidad del paquete web que permite gestionar paths de peticiones http. Los métodos que define son: match() Valida si un path se ajusta a un patrón URL (url-pattern) determinado. extractRelativePath() Obtiene el path relativo a partir de un path absoluto. getCompleteURL() A partir de una request, obtiene la URL solicitada completa, incluyendo los parámetros GET. getURLParametersSeparator() partir de una URL, determina si los parámetros que se vayan A a añadir a continuación se preceden de un carácter ‘?’ ó ‘&’, 33
  • 41. Arquitectura Modelo -Vista- Controlador con openFWPA en funciónd e si dicha URL ya tenía, o no, parámetros GET anteriormente.DateDecorator Clase que facilita la escritura de fechas y horas con un formato determinado. Esta clase implementa el patrón “Decorator” sobre la clase java.util.Date, sobrescribiendo su método toString(). La clase es.princast.framework.core.util.DateDecorator permite definir el patrón de formato que se aplicará al obtener la representación textual de la fecha utilizando el método Date.toString(). Además, también define los patrones para los formatos de fecha más comunes: /** * Formato corto para las fechas tomando como separador el caracter /. */ public static final String SHORT_DATE = "dd/MM/yyyy"; /** * Formato corto para las fechas tomando como separador el caracter -. */ public static final String SHORT_DATE_DASH = "dd-MM-yyyy"; /** * Formato para mostrar sólo horas, minutos y segundos. Las horas varían en * el rango 0..24. */ public static final String ONLY_TIME = "HH:mm:ss"; /** * Formato largo para la fecha, tomando como caracteres de separación el * caracter / para día, mes, año y el caracter : para horas, minutos y * segundos. */ public static final String LONG_DATE = "dd/MM/yyyy HH:mm:ss"; /** * Formato largo para la fecha, tomando como caracteres de separación el * caracter - para día, mes, año y el caracter : para horas, minutos y * segundos. */ public static final String LONG_DATE_DASH = "dd-MM-yyyy HH:mm:ss";PrincastPathResolver El objetivo del PrincastPathResolver es ofrecer, al programador de aplicaciones, un punto centralizado para resolver paths (a recursos) uniformemente. Este objeto (que implementa el patrón Singleton) define los siguientes métodos: resolvePath(path) Resuelve un path, que se especifica por parámetro, devolviendo el path absoluto. resolveToFile(path) Resuelve un path, devolviendo el objeto File correspondiente. 34
  • 42. Arquitectura Modelo -Vista- Controlador con openFWPA resolveToStream(path) Resuelve un path, devolviendo un stream de lectura sobre el recurso que se halle en dicho path. Si no encuentra ninguno, dispara una FileNotFoundException. Existen varios tipos de "path resolvers" en el openFWPA, en función del tipo de aplicación. En general, se puede asignar cualquier tipo de "path resolver" definido por el usuario. Para ello, basta con extender la clase PrincastPathResolver y utilizar el método PrincastPathresolver.registerResolver(). Los resolvers implementados en el openFWPA son: DefaultPathResolver Implementación por defecto. Resuelve paths absolutos y relativos al classpath y al "working dir" de la aplicación. WebAppPathResolver Implementación por defecto en aplicaciones web (siempre que utilicen el PrincastStartupListener). Resuelve paths absolutos y relativos al classpath y al directorio de despliegue de la aplicación.PrincastOSCacheInterceptor Esta clase permite a través de Spring y OSCache, realizar cacheos transparentes de las llamadas a cualquier método de cualquier bean de Spring. Esto es útil, por ejemplo para cachear las llamadas al sistema de Genéricos del Gobierno del Principado de Asturias. El uso de esta clase está documentado en la Javadoc. La funcionalidad por defecto establece una caché por método cacheado, donde la clave para buscar en la cache es la concatenación del toString, de los argumentos. Si dos llamadas al mismo método tienen el mismo toString concatenado de los argumentos se devuelve el resultado cacheado. Este comportamiento se puede sobreescribir heredando de la clase. El tiempo de refresco se establece en la definición de bean, por defecto son 600 segundos se recomienda ver la Javadoc, para ver la sintáxis de los tiempos de refresco en función del método.Providers Para aislar la capa de acceso a datos de otras capas de la aplicación, habitualmente es buena idea definir interfaces “providers”. Estos interfaces proporcionan datos a la capa del controlador, o directamente a la vista, sin indicar donde ni cómo se obtienen esos datos. El controlador (o la vista) pueden manipular los “providers” directamente sin preocuparse de cómo éstos se han obtenido. Desde la versión 1.5 del openFWPA, los providers se encuentran en el paquete: es.princast.framework.facilities.providers. El openFWPA define un conjunto de providers habituales: EntityContentProvider Se trata de un proveedor de entidades. Este interfaz devuelve una sola entidad que puede ser utilizada directamente. Por ejemplo, para mostrar sus datos en un formulario. ListContentProvider Provee conjuntos de entidades. Este interfaz devuelve listas de entidades. Se pueden utilizar para listados. PaginatedContentProvider Provee listas paginadas de entidades. Este interfaz proporciona listas que pueden recorrerse de forma paginada. Se pueden utilizar en listados en los cuales toda la lista no cabe en una sola página HTML 35
  • 43. Arquitectura Modelo -Vista- Controlador con openFWPAPDFProvider Provee documentos en formato PDF. Este interfaz proporciona un array de bytes que contienen un documento PDF. Se puede utilizar para la realización de informes o documentos.XMLProvider Provee contenido en formato XML. Este interfaz proporciona un método writeXML(Writer writer), donde se escribirá directamente el XML. Se puede utilizar para servir contenido XML, junto con la PrincastXMLAction . Un ejemplo de este tipo de Provider, incluido en el openFWPA, es el PrincastVelocityXMLProvider que provee contenido, a través del motor de plantillas Velocity. Su principal objetivo es la generación de contenido XML basado en plantillas, aunque se puede usar para generar cualquier tipo de contenido. Para mayor información acerca de su uso, se recomienda leer la JavadocPropertyBeansProvider Es una implementación del ListContentProvider que provee a la aplicación de beans de propiedades (PropertyBean). El provider puede cargar estos beans de objetos Map o de ficheros de properties (.properties).Para conocer con mayor detalle el interfaz de cada uno de los providers, consúltese la documentaciónJavadoc del openFWPA. 36
  • 44. Capítulo 3. Implementación de laArquitectura de Referencia conopenFWPAInversión de Control en la Arquitectura deReferencia A partir de la versión 1.5 de openFWPA, se hace un uso intensivo de la inversión de control (IoC), para implementar la arquitectura de referencia, en las aplicaciones realizadas con el openFWPA. Para ello, se hace uso de Spring Framework, que ofrece la implementación del patrón AbstractFactory basado en ficheros XML. Esto permite, eliminar los elementos de unión en las aplicaciones, como las factorías, y singletons. Además, permite tener la arquitectura modularizada en "piezas", que por estar definidas en un fichero XML, son intercambiables. Lo que deriva, en un sistema débilmente acoplado, más tolerable a cambios y modificaciones.Introducción al manejo de la Arquitectura conSpring Para manejar la Arquitectura de referencia con Spring, se hace uso de una serie de ficheros XML, donde se definen los beans que forman parte de la arquitectura del sistema. Estos ficheros están ubicados en src/ java/beans y sigue la estructura de directorios, propuesta para la arquitectura. Figura 3.1. Ficheros de configuración de Beans Los ficheros siguen la sintaxis de definición de beans de Spring, al igual que el fichero de inicialización de openFWPA (princast-init-script.xml). Para hacer uso de la inversión de control, es necesario seguir una serie de pasos. Como ejemplo, se va a ver cómo se construye una clase Action dependiente de una clase Delegate desde cero. El proceso de inyectar la dependencia se ha denominado "enlazado", tomándolo como traducción libre del término "wiring", utilizado en el manual de referencia de Spring. La primera tarea que hay que hacer, es implementar el Action. Como se tiene una dependencia, con un Manager, se introduce un campo o propiedad (privado o protegido), en la clase Action. Además, se define un setter para ese campo, de esta forma se puede inyectar esa dependencia. A la hora de usar el objeto Delegate se utiliza normalmente, aunque parezca que al usarlo apunta a un valor nulo, el motor de inversión de control se encarga de inicializarlo. public class GetListaProductoAction extends PrincastListAction { // inyeccion de dependencia (/beans/web/action-beans.xml) protected CarritoDelegate carritoDelegate; 37
  • 45. Implementación de la Arquitectura de Referencia con openFWPA public void setCarritoDelegate(CarritoDelegate carritoDelegate) { this.carritoDelegate = carritoDelegate; } protected Object getContentList(ActionMapping mapping, ActionForm form, HttpServle //Llamamos al delegate para obtener la lista de productos. return carritoDelegate.getListaProducto(); } } Una vez programada la clase, se debe registrar en el fichero de beans correspondiente, en este caso, como se trata de un action, se registra en el fichero actions-beans.xml. Para ello se le da un identificador mediante el atributo id. Un nombre de clase con el atributo class, y mediante el atributo singleton, se especifica si se quiere que la clase sea un singleton o no (si no se especifica ese atributo, por defecto, será un singleton). <bean id="viewlistaproducto" class="es.princast.sampleapp.web.action.GetListaProdu <property name="carritoDelegate"><ref bean="carritoDelegate" /></property> </bean> Para inyectar la dependencia se realiza mediante el elemento property, donde se establece un atributo name, que coincide con el nombre asociado al setter, que se ha definido en la clase que se implementó anteriormente. En el contenido del elemento property, se hace referencia mediante ref al identificador (id) de otro bean. Este bean puede estar definido, en ese mismo fichero o en cualquiera de la estructura comentada anteriormente. En este caso, la definición es de un Delegate, por lo que estará definido en el fichero delegate-beans.xml. <bean id="carritoDelegate" class="es.princast.sampleapp.web.delegate.CarritoDelega <property name="carritoManager"><ref bean="carritoManager"/></property> <property name="formasPagoManager"><ref bean="formasPagoManager"/></property> <property name="agenciasManager"><ref bean="agenciasManager"/></property> </bean>Estableciendo el Datasource La inversión de control, comienza desde la primera dependencia que se tiene. En este caso el datasource, para ello se define en el fichero datasource-beans.xml, un bean que representa un datasource JNDI, a continuación se muestra un ejemplo de definición del mismo: <!-- JNDI Datasource --> 38
  • 46. Implementación de la Arquitectura de Referencia con openFWPA <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"> <value>jdbc/MySQLDS</value> </property> </bean> En el caso de la aplicación en blanco, para que una aplicación pueda desplegar sin necesidad de datasource, se ha definido un datasource nulo. Este datasource, sirve para poder enlazar las dependencias sin la necesidad de un datasource real. En el momento que se disponga de uno real, es recomendable cambiarlo por el datasource JNDI. <!-- Datasouce Nulo, BORRAR cuando se use un datasource real --> <bean id="dataSource" class="es.princast.framework.facilities.dao.PrincastNullD Para realizar las pruebas unitarias, es necesario disponer de un datasource para realizar los test. En la aplicación de ejemplo, se suministra un test sobre una clase DAO, que utiliza un datasource para test. Un ejemplo de definición de datasource de test, es el siguiente: <!-- DataSource para Test Unitarios --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerD <property name="driverClassName"><value>org.gjt.mm.mysql.Driver</value></pr <property name="url"><value>jdbc:mysql://localhost/carrito</value></propert <property name="username"><value>admin</value></property> <property name="password"><value></value></property> </bean> Para más información sobre la realización de pruebas sobre los DAOs, ver “Pruebas unitarias de objetos que acceden a bases de datos.”Enlazando con los DAOs Una vez definido el datasource, se definen los DAOs, para ello se utiliza el fichero dao-beans.xml. A continuación se muestra un ejemplo de definición: <bean id="carritoDAO" class="es.princast.sampleapp.business.dao.MySQLCarritoDAO"> <property name="dataSource"><ref bean="dataSource"/></property> </bean> <bean id="formaPagoDAO" class="es.princast.sampleapp.business.dao.MySQLFormaPagoDA <property name="dataSource"><ref bean="dataSource"/></property> </bean> 39
  • 47. Implementación de la Arquitectura de Referencia con openFWPA Se recuerda que para que se produzca la inyección de la dependencia, la clase que implementa el DAO, debe disponer de un setter para el campo dependiente, como se observa en el siguiente ejemplo: public class MySQLCarritoDAO implements CarritoDAO, PrincastDAO { // La conexion se injecta en /beans/business/dao-beans.xml protected DataSource dataSource; ... // Este setter es necesario para la injeccion de la conexion en // la configuracion de los beans public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } ...Enlazando con los Managers Para enlazar los DAOs con los Managers, se realiza en el fichero manager-beans.xml. Hay que tener en cuenta, si el manager que se está definiendo, está sujeto a transacciones. A continuación, se muestra la declaración de un Manager que no está sujeto a transacciones y que no tiene ninguna dependencia con DAOs: <!-- Este no esta sujeto a transacciones luego declaracion normal --> <bean id="agenciasManager" class="es.princast.sampleapp.business.manager.Agenci </bean> En el caso de estar sujeto a transacciones, la definición del bean es un poco más complicada, ya debe heredar de una plantilla para transacciones. Esto se realiza mediante el atributo parent, donde se le especifica el nombre de la plantilla, que estará definida en el fichero transaction-beans.xml. La definición propiamente dicha de la clase Manager, se realiza dentro de la propiedad target, y se le inyecta las dependencias normalmente. A continuación, se muestra la definición de un Manager sujeto a manejo de transacciones. <bean id="carritoManager" parent="transactionTemplate"> <property name="target"> <bean class="es.princast.sampleapp.business.manager.CarritoManager"> <property name="carritoDAO"><ref bean="carritoDAO"/></property> </bean> </property> </bean> 40
  • 48. Implementación de la Arquitectura de Referencia con openFWPA Como en los casos anteriores, la dependencia se establece por medio de la definición de un setter, para cada campo dependiente.Gestión de transacciones Desde el fichero transaction-bean.xml se controla la gestión de transacciones. Esta gestión se realiza de forma declarativa, por lo que los Managers que hereden de estas plantillas de transacciones, no tendrán que implementar código para la gestión de transacciones, ni conexiones. Antes de la definición de la plantilla se establece un transactionManager sobre el datasource a utilizar. Un ejemplo de definición es el siguiente: <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSou <property name="dataSource"><ref bean="dataSource"/></property> </bean> Una vez declarado el transactionManager, se define una plantilla para el manejo de transacciones. De esta plantilla se debe de heredar, en la definición de beans, los Manager que esten sujetos a transacciones (mediante el atributo parent, visto en el apartado anterior). Se podrían definir varias plantillas, en función de las necesidades. Un ejemplo de plantilla para la gestión de transacciones es el siguiente: <bean id="transactionTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryB <property name="transactionManager"><ref bean="transactionManager"/></prope <property name="transactionAttributes"> <props> <!-- Los metodos que comiencen por get en los Manager seran readOnl <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> Como se observa en el ejemplo de plantilla, se dispone de una property de nombre transactionAttributes, que define las propiedades de la transacción. Las transacciones se definen a nivel de método de un Manager, por lo que, cada ejecución de un método de un Manager establecería una transacción. Además se pueden establecer patrones, de forma que definiendo el patrón get*, las propiedades establecidas para ese patrón, afectará a todos los métodos del Manager que comiencen por get. De esta manera definiendo las propiedades, PROPAGATION_REQUIRED,readOnly se tendrá una transacción por método, y además estará optimizada para sólo lectura. 41
  • 49. Implementación de la Arquitectura de Referencia con openFWPA El comportamiento por defecto, para la política de rollback, define que si se produce una excepción de tipo Runtime durante la ejecución del método, se hará un rollback. Algunas de las propiedades que se pueden establecer son: • PROPAGATION_LEVEL: Nivel de propagación. Por defecto se establece el nivel de propagación a PROPAGATION_REQUIRED, esto significa que creará una nueva transacción, sólo si se necesita. • ISOLATION_LEVEL: Nivel de aislamiento. Por defecto trendrá el valor ISOLATION_DEFAULT. • readOnly: Optimización de sólo lectura. Se optimiza la transacción para las operaciones de sólo lectura. • Lista de Excepciones: Establece la política de rollback o commit, al margen del comportamiento por defecto. Por ejemplo, si se le añade la cadena -MiExcepcion, se hara rollback en caso de que dispare la excepción de nombre MiExcepcion, aunque el tipo de excepción no sea Runtime. Si se añade la cadena +OtraExcepcion, se hará commit aunque la excepción sea Runtime. Para más información sobre las propiedades disponibles consultar el manual de referencia de Spring.Enlazado con los Delegates Para enlazar las definiciones de Managers, con las clases Delegate, se hace de manera similar a los enlazados visto anteriormente, en este caso se realiza en el fichero delagate-beans.xml Un ejemplo de enlazado es el siguiente: <bean id="carritoDelegate" class="es.princast.sampleapp.web.delegate.CarritoDelega <property name="carritoManager"><ref bean="carritoManager"/></property> <property name="formasPagoManager"><ref bean="formasPagoManager"/></property> <property name="agenciasManager"><ref bean="agenciasManager"/></property> </bean> Se recuerda que la clase CarritoDelegate debe disponer de un setter para cada dependencia.Enlazado con las Actions Una vez enlazados los Delegates, se deben enlazar las Actions. Para ello se definen los beans en el fichero action-beans.xml. Un ejemplo, de definición de beans para las Actions es el siguiente: <bean id="viewlistaproducto" class="es.princast.sampleapp.web.action.GetListaProdu <property name="carritoDelegate"><ref bean="carritoDelegate" /></property> </bean> En el caso de las Actions es importante establecer el atributo singleton="false", así se creará una nueva instancia del action, por cada petición de struts. 42
  • 50. Implementación de la Arquitectura de Referencia con openFWPA Una vez definidos los beans para las Actions deben ser referenciados desde el fichero struts- config.xml, la referencia se hará mediante el atributo type. Hasta ahora, ese atributo referenciaba el nombre cualificado de una clase, a partir de la versión 1.5, se buscará primero por id de bean definido en el fichero action-beans.xml, si no encuentra ningún bean definido, tomará el nombre como el de una clase, tal y como se hacía en versiones anteriores. Un ejemplo de un Action referenciando a una definición de bean en struts-config.xml, es el siguiente: <action path="/viewdetalleproducto" name="detalleProductoForm" input="/index.jsp" t <forward name="success" path="carrito.detalleprod" /> </action> Se observa que el atributo type tiene el valor type="viewdetalleproducto" que coincide con el id del bean definido en action-beans.xml.Acceso directo al ApplicationContext Otra forma de acceder a los beans (menos recomendable que las anteriores) declarados en cualquier fichero de definición de beans, cargado por la aplicación, es utilizar el GlobalApplicationContext (GlobalApplicationContext.getInstance().getApplicationContext()). Este contexto de aplicación global es una clase singleton que da acceso directo al ApplicationContext de Spring cargado por la aplicación. Atención En aplicaciones Struts, únicamente se puede acceder, por este método, a los beans cargados por el módulo por defecto (módulo sin prefijo).BeanDoc para obtener la gráfica dearquitectura Como la arquitectura esta reflejada en ficheros XML, se dispone de una herramienta para generar una gráfica de arquitectura, Spring BeanDoc, que es un subproyecto de Spring Framework. Se dispone de una tarea Ant en la aplicación de ejemplo para ejecutar esta herramienta, aunque deberá estar previamente instalada. Para instalarla, se debe ir a la página principal del proyecto Spring Framework, y seleccionar el subproyecto BeanDoc, donde se indica cómo instalarla y configurarla. Figura 3.2. Ejemplo de gráfica generada con BeanDocPlugin SpringIDE para Eclipse Para facilitar el desarrollo, y el tratamiento de ficheros de Spring, existe el plugin para Eclipse, SpringIDE [http://springide.org/project]. 43
  • 51. Implementación de la Arquitectura de Referencia con openFWPAEste plugin, permite ver de una forma visual la definición de beans, además incluye caraterísticas como elautocompletado de clases y el acceso directo desde el gráfico de visualización, a la definición del bean ode la clase. Para su instalación y primeros pasos, se debe acudir a la página del proyecto SpringIDE.Figura 3.3. Spring IDE para visualizar ficheros de Spring 44
  • 52. Capítulo 4. Componentes para accesoa datosAcceso a Bases de Datos Relacionales Un componente básico de la capa de modelo Modelo es el acceso a datos (generalmente, a bases de datos relacionales). Tal y como se especifica en la Guía de Aceptación de Aplicaciones, no está permitido el acceso a datos desde cualquier componente de la aplicación que no pertenezca a esta capa (páginas JSP, Actions, etc.). La herramienta clave para la implementación de esta capa es el patrón de diseño DAO (Data Access Object).El patrón DAO Para realizar el acceso a datos se utilizará el patrón DAO (Data Access Object). El openFWPA dispone de una interface base PrincastDAO que deben de implementar todos los DAOs, o extender de una clase que la implemente. Esta interfaz posee dos métodos, getDataSource y setDataSource que permiten obtener y establecer, el DataSource que utliza el DAO, para acceder a Base de Datos. El framework también dispone de una clase de utilidad para facilitar la implementación de objetos DAO: la clase PrincastDAOHelper. Un objeto PrincastDAOHelper siempre debe estar asociado con un objeto DAO (que se llamará OwnerDAO). Solamente los OwnerDAOs de un HelperDAO podrán acceder a sus métodos. Es altamente recomendable, que para mejorar el rendimiento general de la aplicación, todos los DAO de una misma clase puedan acceder al mismo DAOHelper a través de una variable static. La característica más interesante que proporcionan los DAOHelpers es la capacidad de cargar consultas SQL desde un fichero .properties. Este fichero debe cumplir las normas de ficheros properties Java. Cada propiedad del fichero se corresponderá con una consulta SQL identificada por una etiqueta. Las consultas SQL del fichero de properties pueden tener parámetros, utilizándose para ello el carácter “?” de forma análoga a los PreparedStatement. Los métodos públicos de esta clase DAOHelper son: PrincastDAOHelper(Class) Constructor del DAOHelper que recibe como parámetro el OwnerDAO getQueries() Devuelve un Properties con las consultas manejadas por el DAOHelper getQuery(String) Devuelve un String con la consulta cuya clave en el fichero de properties se le pasa como parámetro. getStament(String, Devuelve un String con la consulta construida, cuya clave en el Object []) fichero de properties se le pasa como parámetro, haciendo uso de los parámetros estructurales, que también se pasan como parámetro. executeQuery(String, Devuelve una List de Map con el resultado de ejecutar la Object[], Object[], consulta cuya clave en el fichero de properties se le pasa DataSource) como parámetro. Utiliza como parámetros los objetos que se le pasan en dos arrays: a) El primero de los arrays contiene 45
  • 53. Componentes para acceso a datos parámetros estructurales. Se utilizan para componer una consulta y se identifican por números entre “{“ y ”}” (por ejemplo: “SELECT * FROM {0};” ). Este tipo de parámetros pueden considerarse como “comodines” para todas aquellas situaciones en las que no se puedan utilizar los parámetros estándar de la clase java.sql.PreparedStatement. b) El segundo array de parámetros contendrá los valores de los parámetros estándar (identificados por caracteres ?).executeQueryForList(String, equivalente a executeQuery. EsObject[], Object[],DataSource)executeQueryForList(String, equivalente a executeQueryForList. Salvo por el EsObject[], Object[], parámetro PrincastRowMapper, que permite mapear unPrincastRowMapper, ResultSet, a una clase de negocio (VO), esto permite que elDataSource) método devuelva una lista de VOs.executeUpdate(String, Ejecuta la consulta de actualización cuya clave en el fichero deObject[], Object[], properties se le pasa como parámetro.DataSource)reload() Refresca la tabla de consultas releyéndolas del fichero properties correspondiente.Desde la versión 1.5 del framework, se ha introducido la posibilidad de mapear los ResultSet a clases denegocio, generalmente VOs. Para ello se necesita crear una clase que herede de PrincastRowMapper,e implemente el método mapRow. Desde una clase DAOHelper se podrá, usar y reutilizar estosmapas, mediante llamadas al método executeQueryForList que acepta como parámetro unPrincastRowMapper. Un ejemplo de mapeo se puede observar en la clase ProductosMapper, enla aplicación Sample App.public class ProductosMapper extends PrincastRowMapper{ public Object mapRow(ResultSet rs, int rowNum) throws SQLException { ProductoVO producto = new ProductoVO(); producto.setId(rs.getInt("id")); producto.setName(rs.getString("nombre")); producto.setDescription(rs.getString("descripcion")); producto.setSmallImageURL(rs.getString("smallImageURL")); producto.setBasePrice(rs.getDouble("basePrice")); return producto; }}La aplicación en blanco (App Blank) dispone de plantillas para la realización de DAOs (concretamente enla clase DAOTemplate), y la aplicación de ejemplo ( Sample App) dispone de un ejemplo de utilizaciónen la clase MySQLCarritoDAO.public class MySQLCarritoDAO implements CarritoDAO, PrincastDAO {...protected static PrincastDAOHelper helper = new PrincastDAOHelper( MySQLCarritoDAO.class);... 46
  • 54. Componentes para acceso a datos /** * Devuelve una List con todos los productos que maneja la aplicación. */ public List getListaProducto() { List listaProducto; listaProducto = helper.executeQueryForList("listaProducto", null, new Object[] {} new ProductosMapper(),dataSource); return listaProducto; } ... }Loggeo de las Excepciones en los DAO Como se ha visto en capítulos anteriores, a partir de la versión 1.5 no hace falta capturar las excepciones producidas en el acceso a datos, sin embargo si que es recomendable imprimir en el log de la aplicación, la excepción que se produce. Para ello, basta con definir dos spring beans en el fichero dao-beans.xml, y las excepciones que se produzca quedan loggeadas automáticamente. <!-- Con estas dos definiciones se auditan todas las expcepciones que se produzcan en los DAOs a nivel de log de ERROR--> <bean id="loggerThrowsAdvice" class="es.princast.framework.facilities.interceptor. <property name="level"><value>ERROR</value></property> </bean> <bean id="daoBeanAutoProxy" class="org.springframework.aop.framework.autoproxy.Bea <property name="beanNames"><value>*DAO</value></property> <property name="interceptorNames"> <list> <value>loggerThrowsAdvice</value> </list> </property> </bean> La primera definición de bean loggerThrowsAdvice, indica la clase de log que se va a usar. La propiedad más importante es el nivel de log, en el ejemplo se indica que las excepciones se van a loggear a un nivel de ERROR (son válidos los mismos niveles de log que para log4j). La segunda definición indica los beans a los que se va a aplicar el log, se aplican por nombre de bean. En este caso se indica con un patrón "*DAO", esto quiere decir que el log se aplica a todos los métodos de todos los beans que su identificador acabe en la cadena DAO. Aunque aquí se auditen los DAO, se puede auditar cualquier otra clase por su definición de bean de spring, por ejemplo todos los "Delegate" con el patrón "*Delegate".Listas clave/valor Existe un tipo especial de DAO que permite acceder y manipular a información estructurada como listas de pares clave/valor sobre una tabla de una base de datos. Se trata de la clase PropertiesTableDAO. El principal uso de este tipo de objetos DAO es facilitar el acceso a listas de valores que puedan ser utilizadas para cargar componentes de interfaz de usuario como inputs HTML de tipo select. 47
  • 55. Componentes para acceso a datosEsta clase devuelve objetos (o listas de objetos) del tipo PropertyBean, que tiene dos campos valuey label correspondientes a la clave y su valor asociado respectivamente.Los métodos públicos de esta clase son: Constructor. Recibe un DataSource, el nombre de la tabla,PropertiesTableDAO(DataSource,String, String, String) nombre de la columna donde se almacenan las claves y nombre de la columna que tiene los valores.getAllProperties() Devuelve una List de PropertyBeans con todos los pares de la tabla.getProperties(String) Devuelve una List de PropertyBeans con los pares de la tabla que se cumplen una determinada condición especificada por parámetro.findProperty(String) Devuelve el PropertyBean cuya clave se pasa como parámetro.insertProperty(String, Inserta en la tabla de la base de datos un nuevo registro con la claveString) y el valor que se pasan como parámetro.updateProperty(String, Actualiza en la base de datos el valor de la clave especificada.String)deleteAllProperties() Elimina todos los pares clave/valor de la tabla.deleteProperty(String) Elimina el par cuya clave se pasa como parámetro. Devuelve un Iterator con todos los pares de la tabla. Si el array quegetReportIterator(Object[]) se le pasa esta vacío o es nulo devolverá todos los valores de la tabla. Sin embargo, si contiene valores se buscarán todos los pares cuya clave esté en el arrayPuede verse un ejemplo de utilización en la clase MySQLFormaPagoDAO, de la aplicación de ejemplo(Sample App).public class MySQLFormaPagoDAO implements FormaPagoDAO, PrincastDAO {... protected static List formasPago = null; public MySQLFormaPagoDAO() {} /** * Devuelve una List con las formas de pago existentes */ public List getFormaPago() { // se mantiene en memoria las formas de pago en una variable static if (formasPago==null){ PropertiesTableDAO propsTable = new PropertiesTableDAO(dataSource, "formapago", "id","descripcion"); formasPago = propsTable.getAllProperties(); } return formasPago; }...} 48
  • 56. Componentes para acceso a datos Otra forma de crear listas de beans con pares atributo/valor (PropertyBean) es a partir de un Map. La propia clase PropertyBean dispone de un factory method estático que, recibiendo como parámetro una referencia a un objeto Map, crea una lista de pares atributo / valor (PropertyBeans). public static List populateList(Map props)LookupPropertyBean Una situación habitual es aquella en la que se necesita “navegar” una relación entre entidades. En esta situación, se dispone de una clave y se necesita obtener el valor de dicha clave. Figura 4.1. Ejemplo de necesidad de lookup Si en el modelo de la figura, se necesita hacer un listado con: {ARTICULO, DESCRIPCION} , es necesario realizar una acceso a base de datos para cada entrada de la lista, o bien modificar el diseño de la aplicación, cambiando los campos de la clase ArticuloVO. Una forma más sencilla es utilizar el componente LookupPropertyBean, que proporciona el openFWPA. Este bean permite, recibiendo una clave, obtener el valor asociado a dicha clave. Figura 4.2. Navegación de una relación en BD con un LookupPropertyBean En ocasiones, los datos sobre los que hay que hacer " lookup ", no están almacenados en una base de datos. Estos datos pueden estar en memoria, en un Map o en una lista de PropertyBeans . Para poder manejar todas estas situaciones, el componente LookupPropertyBean , dispone de los siguientes constructores: Constructor estándar para este tipo de objetos. Asocia el lookup LookupPropertyBean(DataSource dataSource, String bean con una base de datos. Como parámetros debe recibir: el tableName, String dataSource de la base de datos, la tabla en la que se almacenan los keyColumn, String datos de la relación, el nombre de la columna de las claves y el valueColumn) nombre de la columna de los valores. LookupPropertyBean(Map Utilizando este constructor, la relación lookup se realiza sobre los props) datos almacenados en un Map. LookupPropertyBean(List Este constructor debe recibir como parámetro una lista de objetos de beans) tipo PropertyBean. Las claves de la asociación se corresponden con la propiedad " value" de los beans y los valores, con al propiedad " label". Cualquier objeto en la lista que no pertenezca a la clase PropertyBean será ignorado.Providers A partir de la versión 1.5 del openFWPA, los providers están ubicados en el paquete: es.princast.framework.facilities.providers. Los providers del paquete es.princast.framework.dao.providersestán deprecados. Ver “Providers”.Generadores de Secuencia Otra utilidad incluida en el openFWPA son los generadores de secuencia. Este tipo de objetos permiten generar secuencias de números consecutivos. Su utilidad más habitual es la generación de claves primarias. 49
  • 57. Componentes para acceso a datos La clase base para generadores de secuencia, en el openFWPA, es SequenceGenerator, cuyo método más relevante es: generateId(). Se proporcionan dos implementaciones: MySQLSequenceGenerator y OracleSequenceGenerator que utilizan bases de datos MySQL y Oracle respectivamente.Pools de conexiones La configuración de Pools de conexiones en el contenedor oc4jse realiza en el fichero data- sources.xml. El propio fichero trae un ejemplo de configuración para base de datos Oracle, y en la aplicación de ejemplo (Sample App) se puede encontrar la definición de un DataSource para el servidor MySQL.[17] <data-source class="com.evermind.sql.DriverManagerDataSource" name="MySQLDS" location="jdbc/CarritoDS" xa-location="jdbc/xa/CarritoXADS" ejb-location="jdbc/MySQLDS" connection-driver="org.gjt.mm.mysql.Driver" username="admin" password="" url="jdbc:mysql://localhost/carrito" inactivity-timeout="30"/> </data-sources> Para opciones avanzadas de configuración (número mínimo y máximo de conexiones del Pool, número máximo de intentos de conexión, etc.) consultar el DTD de dicho fichero, disponible en la dirección http://xmlns.oracle.com/ias/dtds/data-sources.dtd. 50
  • 58. Capítulo 5. Construccion de informescon openFWPAGeneración de Informes La generación de informes se basa en el proyecto JasperReports [20]. JasperReports es una librería de clases que puede verse como un motor de reporting para desarrollos java. El principal objetivo de este proyecto es facilitar la construcción de documentos con contenido dinámico y su visualización en diferentes formatos. JasperReports organiza la información que le servirá para generar los documentos en forma de fichero XML. El fichero XML, de acuerdo al DTD http://jasperreports.sourceforge.net/ dtds/jasperreport.dtd, es visto como el diseño del informe. Una vez en disposición de un diseño XML válido, es necesario realizar un proceso de compilación, que generará un objeto que es serializado, y podría ser almacenado en disco con extensión .jasper. La compilación validará todas las expresiones java que pudieran estar embebidas en el XML. Una vez compilado, y para proveer al informe con datos dinámicos, se realiza el proceso de completado (fill). Este proceso puede recibir diferentes fuentes de datos, entre ellas conexiones a bases de datos relacionales (instancias de clase Connection), colecciones o arrays de Beans (instancias de clase JRDataSource de la librería de JasperReports) o fuentes de datos personalizadas, extendiendo el interface JRDataSource. Otra forma de pasar información dinámica a los documentos es a través de parámetros de informe, que forman parte del diseño XML y pueden ser establecidos por programación, e incluso tener valores por defecto en diseño. Este proceso puede verse en la siguiente figura (obtenida del libro “[The JasperReports Ultimate Guide]”) Figura 5.1. Proceso de generación de informesCreación de los diseños XML. El proyecto JasperReports no provee de ninguna herramienta adicional que facilite la labor de la creación de los ficheros XML. En este punto, un desarrollador que pretenda crear el diseño XML de un documento necesario para su aplicación, debería crearse el fichero XML desde un editor de texto, o editor XML. Evidentemente, esto es una tarea tediosa y poco operativa. Más bien se necesitaría otra herramienta que facilitara la labor de visualizar, compilar y generar el XML del documento que se está diseñando. Una herramienta visual del tipo WYSIWYG. Existen varias herramientas de este tipo desarrolladas como proyectos abiertos. Algunas de ellas pueden verse en http://jasperreports.sourceforge.net/gui.tools.html. El desarrollador es libre de elegir su herramienta preferida; el openFWPA no provee de ninguna de ellas, aunque se han realizado pruebas con el proyecto iReport-Designer for JasperReports [21]La herramienta iReports La herramienta iReports es un editor visual de ficheros XML listos para ser usados por el motor de reporting JasperReports. Trabaja en modo standalone (aplicación de ventana de java) y tiene un interfaz claro. En la página del proyecto http://ireport.sourceforge.net puede obtenerse una amplia documentación sobre como manejar esta herramienta, e incluso un video tutorial que muestra algunos de los aspectos más interesantes sobre ella. 51
  • 59. Construccion de informes con openFWPA Al estar estrechamente relacionada con JasperReports, es necesario conocer los aspectos en que este último organiza su información (bandas, grupos, subreports, parameters, fields, variables…) para optimizar el manejo de la herramienta visual. Una vez descargado el proyecto y ejecutado, se abrirá una ventana como la de la siguiente figura, donde se ha abierto un fichero XML llamado solicitudQuemaEnBlanco.xml. No es objeto de este manual mostrar el manejo detallado de iReports. No obstante, se introducen algunos conceptos sobre la herramienta. Una vez se tiene un diseño cargado, o bien se ha optado por un diseño nuevo a partir de cero al cual se le van añadiendo elementos, se puede compilar el diseño, tal y como la haría JasperReports a través de su motor. Con esto se asegura que el diseño que se está generando es válido y no producirá errores de compilación en el momento de que la aplicación a desarrollar tome el fichero XML para compilarlo y mostrarlo en pantalla. Para esto se dispone del icono Compliar (1) a la derecha en la barra del menú. También es posible observar como se vería el documento que se está diseñando con alguno de los visores predeterminados, previa configuración de los programas externos que maneja la herramienta, de dos modos distintos: con una fuente de datos vacía o con la fuente de datos actual (previa configuración de las fuentes de datos).Establecer los programas externos. Menú # Tools # Options # Pestaña External Programs Desde esta ventana se pueden establecer los ejecutables para los programas externos que puede usar la herramienta para visualizar los informes.Crear fuentes de datos. Menú # Datasource # Connections / Datasources Desde esta ventana se pueden gestionar las fuentes de datos (crear, borrar y modificar) Admite cuatro tipos diferentes de fuentes de datos: • DataBase JDBC Connection • XML file DataSource • JavaBeans set DataSource (Collection o Array de Beans) • Custom JRDataSource Las más interesentas son las conexiones JDBC y las colecciones de Beans. El framework de programación de JasperReports provee de una serie de implementaciones de referencia para conjuntos de Beans. Son clases que extienden el interface base JRDataSource. También existe la prosibilidad de que el desarrollador extienda su propia implementación del interface, con lo que conseguiría una fuente de datos CustomJRDataSource, la cual podría probar utilizando la herramienta iReports (creando una fuente de datos de este tipo y configurando la clase asociada)Establecer la fuente de datos actual Menú # Build # Set active connection Desde esta opción se establecerá la fuente de datos que la herramienta utilizará cuando se visualice el diseño usando una fuente de datos (icono en la barra de navegación u opción de menú Menú # Build # Execute report (using active conn.) 52
  • 60. Construccion de informes con openFWPAEstablecer la vista del informe Menú # Build Desde este grupo de opciones se tiene la posibilidad de establecer como obtener la salida visual del documento que se está diseñando, seleccionando alguno de los 7 radio-buttons disponibles. Se deberían tener correctamente establecidos los programas externos para poder utilizar correctamente las vistas. La opción JRViewer preview funciona en cualquier caso, al ser el visor por defecto de JasperReports, que ya incluye la herramienta iReports.Report Wizard Menú # Report Wizard Esta opción permite la creación de nuevos diseños a partir del wizard de iReports. El proceso consta de una serie de pasos que se detallan a continuación: 1. Introducir la consulta que obtendrá los datos de la fuente de datos. 2. : Seleccionar los campos que se desean visualizar en el documento a generar. 3. : Agrupar por algún campo si se desea. No es obligatorio agrupar. 4. Elegir una plantilla para el layout. iReports viene con una serie de plantillas que se pueden utilizar, o bien el desarrollador podría construir sus propias plantillas. Con esto finaliza el proceso, y se genera un informe en vista diseño como el siguiente: Que puede ser compilado y visto en PDF, pulsando el icono correspondiente: En este ejemplo se ha utilizado una fuente de datos JDBC Connection. No se han realizado pruebas para obtener datos de collecciones de Beans (fuente de datos JRDataSource) aunque el proceso no debería diferir sustancialmente de lo aquí expuesto.Report Parameters Menú # View # Report Parameters Los parámetros son una buena forma de añadir datos dinámicos al informe y parametrizar éste, de forma que se puedan visualizar elementos según el valor o rango de algún parámetro, e incluso modificar la consulta SQL según parámetros. Los parámetros añaden más flexibilidad a la generación de los informes, hasta el punto de que es posible crear informes sin fuente de datos, y que toda su información llegue por parámetros. Un parámetro tiene un nombre, un tipo (clase java seleccionable de una lista) y opcionalmente un valor por defecto (expresión java que será compilada en el proceso de compilación y evaluada en el proceso de completado del informe) Los parámetros son referencias a objetos que son pasadas al proceso de completado (fill) del informe. Su uso, como ya se ha comentado, servirá para enviar información al motor de reporting que no puede ser encontrada en la fuente de datos (DataSource) Los parámetros se identifican en el fichero XML por construcciones similares a la siguiente: <parameter name="ejemplar_para" isForPrompting="false" class="java.lang.String"> 53
  • 61. Construccion de informes con openFWPA <defaultValueExpression > <![CDATA[new String("Ejemplar para ... por defecto")]]> </defaultValueExpression> </parameter> En el entorno de iReports, se puede hacer referencia a este parámetro mediante la construcción $P{ejemplar_para}Consultas parametrizadas Menú # View # Report quey Es posible parametrizar la consulta que lanzará el informe según el valor de uno o varios parámetros en tiempo de ejecu-ción. Para hacer referencia a un parámetro definido se utiliza la construcción $P{nombre_de_parametro} y estas construcciones se pueden insertar en las propia consulta, de forma que sean evaluadas durante el proceso de completado. Un ejemplo puede verse en la siguiente imagen, donde se observa la intrusión de los parámetros “fecha_inicio” y “fecha_fin” como parte de la sentencia SQL. El objetivo de esta consulta es filtrar la búsqueda entre dos fechas, que posi-blemente el usuario introduzca en algún formulario de la vista de su aplicación. Una forma de hacer llegar dinámicamente estos valores a la consulta es utilizando parámetros de informe, cuyos valores serán asignados por programación en tiempo de ejecución y pasados como información adicional al proceso de completado del informe.Fields Los fields representan la forma de mapear campos en el diseño del report con datos de la fuente de datos. Si el DataSource es una base de datos, entonces el mapeo es directo con los nombres de campos en una o varias tablas. Si el DataSource es una collección de Beans, el mapeo se realizará con las property ´s de éstos. Un Field se puede idetificar en el XML por una construcción como: <field name="DL_FINALIDAD" class="java.lang.String"/> En el entorno de iReports se le puede hace referencia como $F{DL_FINALIDAD}Variables Se declaran como expresiones en java, y más tarde pueden ser usadas de forma masiva. Pueden servir como forma de no repetir código. Tambíen se utilizan para realizar cálculos. Exiten funciones built- in que facilitian algunas tareas de cálculo, como Count, Sum, Average, Lowest, Highest y StandardDeviation. Las variables serán reinicializadas según la jerarquía de niveles: Report, Page, Column y Group. Exiten una serie de variables inherentes a todo informe (built-in variables), a las que se puede hacer referencia en todo momento. • PAGE_NUMBER • REPORT_COUNT • COLUMN_COUNT En el entorno de iReports se les puede hace referencia como $V{NOMBRE_DE_VARIABLE} 54
  • 62. Construccion de informes con openFWPAEl diseño XML Ya sea con la herramienta iReports o con cualquier otra disponible, el objetivo será obtener un fichero XML que se considerará el diseño del documento. Este fichero será pasado al proceso de compilación, completado y visualización de las aplicaciones construidas bajo el framework, en la forma que se verá más adelante. El código XML generado es extenso y pesado de tratar con herramientas de edición de texto o editores XML. Es por esto la necesidad del uso de herramientas como iReports para diseñar los informes de las aplicaciones. A continuación se pueden ver trozos de XML que generan las herramientas: <?xml version="1.0" encoding="UTF-8" ?> <!-- Created with iReport - A designer for JasperReports --> <!DOCTYPE jasperReport PUBLIC "//JasperReports//DTD Report Design//EN" "http://jasp <jasperReport name="listadoTipoQuema" columnCount="1" printOrder="Vertical" orientation="Portrait" pageWidth="595" pageHeight="842" columnWidth="535" columnSpacing="0" leftMargin="30" rightMargin="30" topMargin="20" bottomMargin="20" whenNoDataType="AllSectionsNoDetail" isTitleNewPage="false" isSummaryNewPage="false"> <property name="ireport.scriptlethandling" value="2" /> <parameter name="P_Titulo" isForPrompting="false" class="java.lang.String"> <defaultValueExpression ><![CDATA["ESPACIO PARA EL TITULO DEL INFORME"]]></defaul </parameter> <parameter name="P_AmpliacionTitulo" isForPrompting="false" class="java.lang.Strin <defaultValueExpression ><![CDATA["Descripción extendida del propósito del info </parameter> <parameter name="fecha_inicio" isForPrompting="false" class="java.lang.String"> <defaultValueExpression ><![CDATA["01/01/1900"]]></defaultValueExpression> </parameter> <parameter name="fecha_fin" isForPrompting="false" class="java.lang.String"> <defaultValueExpression ><![CDATA["31/12/2999"]]></defaultValueExpression> </parameter> <queryString><![CDATA[select f.dl_finalidad, c.dl_concejo, r.cn_tiporesol, s.ca_pe from quesolicitud s, queresolucion r, quefinalidad f, queconcejo c where s.ca_permiso = r.ca_permiso and s.cn_finalidad = f.cn_finalidad and s.cn_concefin = c.cn_concejo and (s.fe_registro between to_date($P{fecha_inicio},dd/mm/rrrr) and to_date($P{fecha_fin},dd/mm/rrrr)) ORDER BY f.dl_finalidad asc, c.dl_concejo asc, r.cn_tiporesol asc]]></queryString> 55
  • 63. Construccion de informes con openFWPA <field name="DL_FINALIDAD" class="java.lang.String"/> <group name="DL_CONCEJO" isStartNewColumn="false" isStartNewPage="false" <groupExpression><![CDATA[$F{DL_CONCEJO}]]></groupExpression> <groupHeader> <band height="23" isSplitAllowed="true" > <rectangle radius="3" > <reportElement mode="Opaque" x="13" y="3" width="515" height="17" forecolor="#8080FF" backcolor="#B6CBEB" key="element-25" stretchType="NoStretch" positionType="FixRelativeToTop" isPrintRepeatedValues="true" isRemoveLineWhenBlank="false" isPrintInFirstWholeBand="false" isPrintWhenDetailOverflows="false"/> <graphicElement stretchType="NoStretch" pen="Thin" fill="Solid" /> </rectangle> <staticText> <reportElement mode="Transparent" x="20" y="4" width="65" height="14" forecolor="#6666FF" backcolor="#FFFFFF" key="element-26" stretchType="NoStretch" positionType="FixRelativeToTop" isPrintRepeatedValues="true" isRemoveLineWhenBlank="false" isPrintInFirstWholeBand="false" isPrintWhenDetailOverflows="false"/> <textElement textAlignment="Left" verticalAlignment="Middle" lineSpacing="Sing <font fontName="Verdana" pdfFontName="Helvetica-Bold" size="10" isBold="true" </textElement> <text><![CDATA[Concejo de]]></text> </staticText>Bugs en el XML generado por iReport Se han detectado una serie de problemas que hacen que el XML generado por iReports no responda directamente a lo que JasperReports espera encontrar, produciéndose fallos de compilación del XML una vez se intenta visualizar el informe en las aplicaciones construidas bajo el framework. Si el desarrollador se encuentra con este problema, intente lo siguiente: 1. Editar el XML en un editor de texto 56
  • 64. Construccion de informes con openFWPA 2. Utilizar como encoding UTF-8 en lugar de ISO-8859-1. El XML debería comenzar con la siguiente línea: <?xml version="1.0" encoding="UTF-8"?> 3. Eliminar todas las referencias al atributo pdfEncoding. Los elementos de texto son generados por defecto con el atributo pdfEncoding=”CP1252”. Borrar todos los literales pdfEncoding=”XXXXXX” del fichero XML. 4. Si se utilizan imágenes en los informes, modificar todas las rutas. La herramienta introduce rutas absolutas para mapear las imágenes. Deberá cambiar estas rutas a relativas de acuerdo a la estructura de las aplicaciones que se estén desarrollando. 5. El creador de iReports ha introducido una opción para que las expresiones puedan ser multilínea (una facilidad para el diseñador del informe) Esta posibilidad se controla en Menú # Tools # Options (Pestaña General) # CheckBox Using multi line expresions. Si está habilitada, en el XML aparecerá el atributo isCode en ciertos elementos. Este atributo no forma parte del DTD, por lo que no será posible parsear el XML convenientemente y mucho menos compilar el diseño por JasperReports. Eliminar el atributo isCode del XML.Es posible que en futuras versiones de JasperReports el atributo isCode forme parte del DTD, si fructifican las conversaciones a este respecto entre el creador de JasperReprots y el de iReports. 6. Si no se va a utilizar una clase Scriptlet en el informe, no será necesario que esto conste en el diseño. Es posible que al generar un nuevo diseño con la herramienta iReport se añada por defecto la clase dori.jasper.engine.JRDefaultScriptlet. En la versión de jasperreports actualmente utilizada (0.6.0) esta clase no existe, con lo que si se intenta compilar el informe ocurrirá una excepción ClassNotFoundException. Para eliminar esta referencia se puede editar el XML y eliminar la siguiente línea scriptletClass="dori.jasper.engine.JRDefaultScriptlet” del elemento raíz <jasperReport>. También se puede hacer directamente con la herramienta iReport mediante Project # ProjectOptions # Pestaña Scriptlety configurando aquí la clase que se desea utilizar, o indicando que no se va a usar una clase Scriptlet. “En la versión 0.4 de la herramienta iReports, estos bugs están resueltos, por lo tanto, no es necesario realizar ninguna modificación al fichero XML generado.”Compilación de Reports Para poder utilizar los reports generados con la herramienta iReports, en las aplicaciones que utilizan el framework, es necesario que los ficheros XML obtenidos sean compilados al formato .jasper. Para ello, se puede utilizar la clase JasperCompilerManager o, mejor aún, el propio programa editor iReports.Clases en el openFWPA para la renderización deinformes. En el openFWPA se han incluido dos clases para la generación de informes: PrincastReport y PrincastMultiReport. La primera permite generar informes simples y la segunda de ellas, crear multi-reports a partir de la agregación de varios informes sencillos.Informes simples Para crear un informe simple se usará la clase PrincastReport. Para poder crear un PrincastReport, es necesario indicarle, en su constructor, los parámetros: Nombre del informe El informe debe tener un nombre. Este nombre puede ser el que se utilice para generar un fichero PDF. 57
  • 65. Construccion de informes con openFWPA Fuente de datos El origen de datos para cargar el informe también debe ser especificado. Por defecto, se utilizará el origen de datos nulo: JREmptyDataSource. El informe no tomará datos de ninguna fuente. Report compilado Se debe suministrar un stream de entrada (InputStream) que permita leer la definición compilada del informe. Este stream debe estar abierto sobre un fichero .jasper. Además, si en el diseño del informe se han definido parámetros, sus valores deben ser especificados al PrincastReport utilizando el método setParams(Map). Atención Antes de obtener el informe es MUY recomendable pregenerarlo (para validar que no se producen errores antes de exportar el informe). Para pregenerar un informe se debe utilizar el método process(). Para obtener un informe de un PrincastReport (correctamente creado y con los parámetros que necesite asignados) se puede utilizar uno de los siguientes métodos: a. getReportToPrint(). Devuelve el objeto jasper imprimible: JasperPrint. Este objeto puede ser manejado por las clases de Jasper Reports (se puede exportar a múltiples formatos, imprimir, etc.) b. getPDFContent(). Devuelve el informe exportado a formato PDF, en un array de bytes, listo para ser volcado a un fichero (o enviado a un cliente, etc.) c. exportPDF(OutputStream). Exporta el informe, en formato PDF, al OutputStream que se pasa como parámetro. A continuación se muestra un ejemplo de construcción de un PrincastReport. //Obtener el fichero del Report (.jasper) InputStream stream = loadReport(REPORT_NAME+".jasper"); //Obtener origen de datos List listaProducto = CarritoDelegateFactory.getCarritoDelegate().getListaProducto() JRDataSource dataSource = new JRBeanCollectionDataSource(listaProducto); //Obtener parametros Map parameters = new HashMap(); MessageResources messages = (MessageResources) request.getAttribute(Globals.MESSAGE parameters.put("P_Titulo", messages.getMessage("report.title")); parameters.put("P_AmpliacionTitulo", messages.getMessage("report.description")); //Crear report PrincastReport report = new PrincastReport(REPORT_NAME, dataSource, stream); report.setParams(parameters); report.process();Multi Reports Los Multi-reports son informes compuestos de informes simples. El objetivo de los multi-reports es agrupar un conjunto de informes para que puedan ser volcados en un mismo fichero PDF. El openFWPA incluye la clase PrincastMultiReport para la generación de informes compuestos. Esta clase es muy simple de manejar: basta con especificarle un nombre (para el fichero PDF) y añadir todos los informes simples que se quieran adjuntar. 58
  • 66. Construccion de informes con openFWPA Los multi-reports también deben ser procesados, al igual que los informes simples, utilizando el método process(). El informe se puede obtener con el método getPDFContent() que, al igual que en la clase PrincastReport, devuelve un array de bytes con el informe en formato PDF, listo para ser volcado a un fichero. También se puede exportar con el método exportPDF(OutputStream). Si, durante la composición de un multi-report, se produce un error en la generación de alguno de los informes que componen, existen dos posibles políticas para su tratamiento: a. Ignorar el error. Se trata del comportamiento por defecto. El multi-report se compondrá normalmente, se ignora el informe defectuoso. b. Propagar el error. En este caso, se interrumpe la generación del multi-report y se eleva una excepción PrincastReportException. Para activar esta política, el multi-report se debe crear utilizando el constructor: PrincastMultiReport(String name, bolean failOnError), asignando el valor true al parámetro failOnError.Las fuentes de datos de los informes. Los informes visualizarán contenido dinámico, provenientes de dos tipos diferentes de fuentes de datos. 1. DataSources JDBC Connection: conexión con una base de datos relacional. 2. JRDataSources: fuentes de datos JasperReports. La librería de clases de Jasper Reports provee de una serie de implementaciones de este tipo de fuentes de datos. Las más interesantes son aquellas que gestionan collecciones de Beans java (en forma de Collection, Array o Map de Beans). Es recomendable el uso de estas fuentes de datos, en detrimento de las anteriores, ya que de esta forma no se rompe la encapsulación de las capas de la aplicación.Utilizando JasperReports en Linux / Unix sin X11. ADVERTENCIA: No es posible generar informes con imágenes (escudos, marcas de agua, códigos de barras, etc.) en un entorno Unix/Linux sin las librerías X11.Clase para la generación de tablas en PDF. El openFWPA incluye la clase PDFTableExporter para la generación de informes PDF en forma de tabla, de esta manera es posible realizar de una manera sencilla listados en PDF. Para usar esta clase se hará en combinación con una PrincastPDFReportAction, sobrescribiendo el método getReport(). Un ejemplo de su uso es el siguiente: // número de columnas de la tabla, las filas se ajusta automáticamente int columnas = 3; // nombre del report String REPORT_NAME = "tabla"; PDFTableExporter tableExporter = new PDFTableExporter(REPORT_NAME, columnas); // se establece el título del report tableExporter.setTitle("Mi título"); // se añaden celdas de cabecera (aparecen sombreadas) tableExporter.addCellHeader("Enero"); tableExporter.addCellHeader("Febrero"); tableExporter.addCellHeader("Marzo"); // se añaden las celdas de la tabla tableExporter.addCell("1"); 59
  • 67. Construccion de informes con openFWPAtableExporter.addCell("2");tableExporter.addCell("3");// se retorna el objetoreturn tableExporter; 60
  • 68. Capítulo 6. OperacionesSistema de Inicialización y Arranque El núcleo del openFWPA se construye sobre un sistema de inicialización que permite definir, de forma declarativa, los componentes que deben ser creados, configurados e iniciados durante el período de arranque de las aplicaciones. Generalmente estos componentes son objetos de utilidad que tienen alcance global a toda la aplicación, como el sistema de logging, de configuración, monitorización, contadores, consola de administración, etc. A partir de la versión 1.3 del openFWPA el Sistema de Inicialización se basa en el framework IoC Spring (www.springframework.org).Declaración de objetos inicializables El fichero de arranque de las aplicaciones que utilizan el openFWPA es: princast-init- script.xml. En este fichero se deben definir los objetos que serán accesibles durante el periodo de inicialización. La estructura del fichero princast-init-script.xml se ajusta a la DTD de los ficheros de inicialización del framework Spring (http://www.springframework.org/dtd/spring-beans.dtd). Para definir un objeto, se utilizará la etiqueta <bean>, indicando, como atributos un identificador para dicho objeto y su clase (opcionalmente se puede indicar si es un Singleton). Dentro de la etiqueta <bean> se pueden establecer propiedades de los objetos de forma declarativa utilizando la etiqueta <property> (se considera propiedad de un objeto a todo método que empiece por la cadena “set” y tenga un solo parámetro. Ver documentación oficial de Java Beans de Sun en: java.sun.com). Este sistema para dar valor a propiedades de métodos se llama “Inversion Of Control (IoC)”. Las propiedades pueden ser valores introducidos directamente (etiqueta <value>), o referencias a otros objetos definidos en el mismo fichero (etiqueta <ref id=”…”>). <bean id="securityRulesPlugin" class="es.princast.framework.web.filter.security.cor <constructor-arg><value>security-rules</value></constructor-arg> <property name="file"><value>WEB-INF/princast-security-rules.xml</value></property <property name="contexts"> <list> <value>SECURITY</value> </list> </property> </bean> <bean id="jmxBasePluginCap" class="es.princast.framework.core.management.configurat <property name="plugin"><ref bean="baseConfigurationPlugin"/></property> </bean> Existen muchas otras etiquetas que permiten: especificar valores para los parámetros del constructor, asignar colecciones a propiedades de los objetos, definir variables al estilo ANT (${name}), etc. Para mas información al respecto consulte la página web del framework Spring o consulte la guía de referencia incluida en la documentación del openFWPA. 61
  • 69. OperacionesVariables ${…} en el fichero de inicialización En el fichero princast-init-script.xml es posible utilizar variables al “estilo ANT”: ${nombre}. Los valores de estas variables se obtienen de un fichero de propiedades. Para poder utilizar este tipo de variables, es necesario incluir, en el propio fichero princast- init.script.xml, el siguiente bean: <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.Prope <property name="location"><value>”ruta del fichero”</value></property> </bean> Donde “ruta del fichero” es el path del fichero de properties del que se cargarán l Un ejemplo de uso de variables es el que sigue: <bean id="myBean" class="es.princast.framework.examples.MyBean” lazy-init="false" singleton="true"> <property name="exampleProp"><value>${PROP}</value></property> </bean> Es posible utilizar rutas de fichero absolutas. Para ello, es necesario utilizar la construcción de Spring “FileSystemResources”, tal y como se indica en el siguiente ejemplo: <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.Prope <property name="location"> <bean class="org.springframework.core.io.FileSystemResource"> <constructor-arg><value>c:/deploy.properties</value></constructor-arg> </bean> </property> </bean>Desarrollo de objetos inicializables Es posible implementar objetos para que sean arrancados de forma automática, durante la inicialización, por el openFWPA. Todos los objetos que están definidos en el fichero princast-init-script.xml pueden ser inicializados y arrancados automáticamente. No es necesario que los objetos implementen ningún interfaz específico pero se tendrá en cuenta: a. Si los objetos implementan el interfaz Configurable, además de ser creados serán configurados de forma automática por el framework. b. Si los objetos implementan el interfaz Launchable, serán creados de forma automática (ejecutándose cualquier tarea que tengan implementada bajo el método create()). c. Si los objetos implementan el interfaz RegistrableMBean, éstos serán registrados bajo el sistema de gestión JMX. El sistema de inicialización siempre arranca los siguientes objetos: • Manager de logging. • Factoría del sistema de control y gestión JMX. ManagementFactory. • Sistema de condiguración. FrameworkConfigurator 62
  • 70. OperacionesArranque de aplicaciones web A partir de la versión 1.3 del openFWPA, el componente inicialziador de las aplicaciones es: PrincastStartupListener. El servlet de inicialziación: PrincastStartupServlet, utilizado en versiones anteriores, debido a problemas de estabilidad en los ClassLoaders del servidor de aplicaciones, ha quedado deprecado. El startup-listener implementa el interfaz ServletContextListener y, por tanto, debe ser declarado en el fichero web.xml. Además, también se debe declarar un parámetro de contexto (<context- param>) que indique la ruta del fichero de arranque princast-init-script.xml, tal y como se muestra en el siguiente ejemplo: <context-param> <param-name>INIT.SCRIPT.FILE</param-name> <param-value>/WEB-INF/princast-init-script.xml</param-value> </context-param> <listener> <listener-class>es.princast.framework.web.startup.PrincastStartupListener</li </listener>Arranque manual El arranque automático de los servicios del openFWPA (configuracion, management, etc.) unicamente está operativo en aplicaciones web (al estar basado en un ServletContextListener). Para cualquier otro tipo de aplicaciones (consola, EJBs, etc.) en las cuales no hay una parte web disponible, para acceder a los servicios del openFWPA es necesario lanzar la inicialziación de forma manual. Para realizar esta tarea, se incluye la clase PrincastStandaloneInitializer. Esta clase se debe iniciar (utilizando el método estático init()) al arrancarse la aplicación. Durante la ejecución pone a disposición de la aplciación todos los beans declarados en el fichero de inicialización (princast- init.script.xml), a través del PrincastApplicationContext, que se obtiene con el método getInitScriptContext(). InputStream stream = this.getClass().getClassLoader().getResourceAsStream(INIT_SCRI //Default configuration properties Properties props = new Properties(); props.put("myProp", "myValue"); PrincastStandaloneInitializer.init(stream, props); Para finalizar la aplicación se debe llamar al método estático finish(). El método init() también permite tamabién cargar todos los ficheros XML de definición de beans (de Spring) que sean necesarios para la aplicación. Para especificar los ficheros a cargar se indicarán los patrones correspondientes (siguiendo el convenio de nombrado habitual en Spring), teniendo en cuenta que se tomarán relativos al classpath.Sistema de Configuración de Aplicaciones Otro de los sistemas del núcleo del openFWPA es el Sistema de Configuración. El sistema de configuración permite, tanto a los componentes de las aplicaciones como del propio framework, recibir parámetros de 63
  • 71. Operaciones configuración de forma completamente transparente, sin necesidad de preocuparse por la forma o el lugar en que éstos están almacenados. Figura 6.1. Estructura del sistema de configuración El Sistema de Configuración actúa como un almacén centralizado de parámetros de configuración. En la configuración de aplicaciones intervienen los siguientes componentes: • Plugins de configuración (ConfigurationPlugin). Se trata de objetos que pueden acceder a un almacén de parámetros de configuración, ya sea para únicamente recuperar sus valores, o también para actualizarlos. • Contextos de configuración. Los contextos son conjuntos de parámetros agrupados por su funcionalidad. Los parámetros agrupados en un mismo contexto sirven, habitualmente, a objetos relacionados entre sí. • Configurador de aplicaciones (FrameworkConfigurator). Es el componente central del sistema de configuración. Se encarga de cargar los parámetros y repartirlos a los objetos que los necesiten. • Objetos configurables (Configurable). Este tipo de objetos escuchan eventos en el “Configurador de Aplicaciones” y pueden actualizar su estado a medida que la configuración de la aplicación cambia. Los listeners son, generalmente, objetos de aplicación que necesitan acceder a los parámetros que proporciona el Sistema de Configuración del openFWPA.Implementación de objetos configurables Para que un objeto pueda acceder a los parámetros del sistema centralizado de configuración del openFWPA debe: 1. Implementar el interface Configurable. Atención El interface ConfigurationListener ha sido marcado como "deprecated". En su lugar, se debe utilizar el interface Configurable. 2. Registrarse en el configurador: FrameworkConfigurator.Implementando el interface Configurable El interface Configurable define una serie de métodos para la gestión del ciclo de vida del objeto configurable. Los tres métodos definidos son, en realidad, métodos callback. Únicamente van a ser utilizados por el sistema de configuración y no se deben invocar directamente desde objetos de la aplicación. Estos métodos son: configure() Se invoca cuando el objeto se configura por primera vez. Cuando se llama a este método, se presupone que el objeto no ha sido configurado con anterioridad. Este método recibe como argumento un conjunto de parámetros de configuración (ConfigurationParameters). reconfigure() Se invoca cuando el valor de algún parámetro que pueda ser relevante para la configuración del objeto, es actualizado. Este método recibe como parámetro un evento de configuración (ConfigurationEvent). 64
  • 72. OperacionesEn la implementación de estos métodos, el objeto configurable debe obtener, bien del conjunto deparámetros (ConfigurationParameters), bien del evento (ConfigurationEvent) los datosque necesite para su configuración.Conjunto de Parámetros de Configuración (ConfigurationParameters)Se trata de un almacén que contiene todos los parámetros de configuración de la aplicación, agrupadosen contextos. Es responsabilidad del objeto configurable seleccionar aquellos parámetros que le puedanser de utilidad.Se pueden obtener todos los parámetros de un conjunto utilizando el método getKeys(). Este métododevuelve una enumeración (Enumeration) con los nombres (String) de todos los parámetrosexistentes en el sistema. Otra opción es buscar únicamente los parámetros clasificados bajo un contextodeterminado. En este caso, se utilizará el método getKeys(String), pasándo como argumento elnombre del contexto cuyos parámetros se quieren enumerar.Para obtener un parámetro del conjunto ConfigurationParameters, se utilizará el métodogetParameter(String), si se sabe con certeza que el valor del parámetro es una cadena decaracteres, o el método getConfigurationObject(String) si se quiere recuperar un Objectgenérico. Ambos métodos reciben como argumento el nombre del parámetro a localizar.Utilizando estos métodos, si dos parámetros coinciden en nombre, se devolverá el primero quese encuentre. Para evitar colisiones de nombrado, se pueden utilizar los métodos alternativos:getParameter(String, String) y getConfigurationObject(String, String). Alos cuales, se les especifica, como primer argumento, el nombre del contexto en el que se quiere buscarel parámetro.public void configure(ConfigurationParameters params) { String param = params.getParameter(”MY_FOO_KEY_1”) ; if (param != null) { this.fooKey = param; } param = params.getParameter(“FOO_CONTEXT”, “MY_FOO_KEY_2”); if (param != null) { this.fooKey2 = param; }}Eventos de Configuración (ConfigurationEvent)Los eventos de configuración se disparan cuando se produce una actualización en los valores de losparámetros de configuración. Cuando un evento de configuración se dispara se invocan los métodosreconfigure() de los objetos configurables.Es responsabilidad del desarrollador de los objetos configurables implementar la respuesta que tendránsus objetos ante determinados eventos.En realidad, el método reconfigure(), son dos métodos:• reconfigure(ConfigurationEvent). Recibe un evento de configuración de carácter general. No existe una causa concreta que peda disparar este evento: reinicio del sistema, recarga de un fichero de configuración, etc. El objeto event proporciona: nombre del contexto afectado 65
  • 73. Operaciones por el evento (getContextName()) y conjunto de parámetros de configuración actualizados (getParameters()). • reconfigure(ConfigurationParameterUpdatedEvent). Recibe un evento de reconfiguración más específico que indica la actualización de un único parámetro de configuración. El objeto event, además de la información suministrada por la superclase (ConfigurationEvent), proporciona,: nombre del parámetro actualizado (getParameterName()), valor anterior (getFormerValue()) y nuevo valor (getCurrentValue()). public void reconfigure(ConfigurationParameterUpdatedEvent event) { if (event.getContext().equals(this.configurationConetxtName)) { if (event.getParameterName().equals(“MY_FOO_KEY_1”)) { this.fooKey = event.getCurrentValue().toString(); } else if (event.getParameterName().equals(“MY_FOO_KEY_2”)) { this.fooKey2 = event.getCurrentValue().toString(); } } } public void reconfigure(ConfigurationEvent event) { if (event.getContext().equals(this.configurationConetxtName)) { this.configure(event.getParameters()); } }Registro de un objeto en el FrameworkConfigurator. Por último, para que un objeto (que implemente Configurable) pueda ser gestionado por el sistema de configuración, éste debe ser registrado en el configurador del openFWPA. Para registrar un objeto, basta con llamar al método configureMe() del FrameworkConfigurator. public MyClass() { FrameworkConfigurator.getConfigurator().configureMe(this, true); } Este método recibe dos parámetros: el objeto configurable y un valor booleano que indica si el objeto quiere escuchar, o no, eventos de reconfiguración.Plugins de Configuración Los plugins de configuración son los componentes que proporcionan los parámetros que maneja el Sistema de Configuración. Un plugin de configuración se encarga de gestionar un único almacén de datos (un fichero, una base de datos, etc.), sin embargo, un plugin puede servir parámetros a varios contextos de configuración. Los plugins de configuración, implementan el interface ConfigurationPlugin.Añadir un plugin de configuración Si una aplicación tiene alguna necesidad de configuración, la mejor opción suele ser definir plugins de configuración específicos. Para ello, es necesario seguir los siguientes pasos: 1. Seleccionar, de los tipos de plugins que contiene el openFWPA, el más adecuado. 66
  • 74. Operaciones 2. Si no se encuentra ninguno que se ajuste a las necesidades, implementar uno propio. 3. Escribir los parámetros de configuración en el almacén seleccionado. 4. Declarar el plugin en el fichero de arranque de la aplicación (princast-init-script.xml). 5. Opcionalmente, definir un bean manager para la gestión del plugin a través de la consola JMX. 6. En el propio fichero princast-init-script.xml, registrar el plugin en la declaración del bean FrameworkConfigurator.Declaración de plugins en el fichero de inicailización. Antes de poder utilizar un plugin de configuración es necesario definirlo en el fichero de arranque de la aplicación (princast-init-script.xml). Los plugins de configuración son objetos inicializables del openFWPA (Ver “Declaración de objetos inicializables”) y se deben definir como beans en el fichero de arranque. <bean id="baseConfigurationPlugin" class="es.princast.framework.core.configuration. <constructor-arg><value>basePlugin</value></constructor-arg> <property name="priority"><value>12</value></property> <property name="file"><value>${base.module.file}</value></property> <property name="contexts"> <list> <value>SECURITY</value> <value>ROOT.CONTEXT</value> <value>ACTION.CONTEXT</value> <value>JMX.CONTEXT</value> </list> </property> </bean> Los parámetros que se definen en el ejemplo anterior son: • <constructor-arg><value>pluginId</value></constructor-arg>. (Obligatorio para todos los plugins) Se debe definir, como argumento de constructor, el nombre identificativo del plugin (en este caso: “basePlugin”). • <property name="priority">. Se trata de la prioridad del plug-in. En caso de que haya varios plugins que, para un mismo contexto ofrezcan parametros de igual nombre (es decir, en case de que haya conflicto de nombres de los parámetros), se tomarán primero los parámetros ofertados por el plug- in de prioridad mas alta (numero más bajo). Si no se establece ningún valor, por defecto, la prioridad de todos los plug-ins es 10 (un valor intermedio). La prioridad permite definir jerarquías de plug-ins de configuración. De esta forma, se permite definir plug-ins a nivel de contenedor (con parámetros comunes a todas las aplicaciones) que pueden sobreescritos para definir parámetros específicos para cada aplicación. • <property name="file">. Algunas propiedades se deben definir, o no, en función del tipo de plugin. En este caso, el plugin PropertiesFileConfigurationPlugin, exige la definición de la property “file”. • <property name="contexts">. (Obligatorio para todos los plugins) Por ultimo, se debe definir la lista de contextos a los que el plugin sirve parámetros. En este caso, el plugin sirve parámetros al 67
  • 75. Operaciones contexto raíz (ROOT.CONTEXT), al contexto de seguridad (SECURITY) , al contexto del sistema de gestión JMX (JMX.CONTEXT) y al contexto de las Actions de la aplicación (ACTION.CONTEXT). Opcionalmente, una vez definido el plugin, se le puede asignar un adaptador JMX de forma que éste pueda ser manejado desde la consola HTML de la aplicación (Ver “Consola de gestión”). Si se define un adaptador, será posible, a través de la consola, actualizar o referescar, en caliente, los valores de los pa-rámetros de configuración del plugin (siempre y cuando el plugin soporte estas operaciones). <bean id="jmxBasePluginCap" class="es.princast.framework.core.management.configurat <property name="plugin"><ref bean="baseConfigurationPlugin"/></property> </bean> El adaptador JMX es otro bean, del tipo: ConfigurationPluginJMXAdapter. Únicamente es necesario indicar la referencia del plugin (utilizando su identificador de bean: <bean id=…) que debe gestionar, al definir la propiedad “plugin”, tal y como se muestra en el ejemplo anterior. Para finalizar, es necesario registrar el plugin en la definición del FrameworkConfigurator (que no es más que otro bean en el fichero princast-init-script.xml). Se debe añadir la referencia al plugin en la propiedad: “plugins”. <bean id="configurationManager" class="es.princast.framework.core.configuration.Fra factory-method="getConfigurator" lazy-init="false" singleton="true"> <property name="plugins"> <list> <ref bean="baseConfigurationPlugin"/> <ref bean="jaasConfigPlugin"/> <ref bean="securityRulesPlugin"/> </list> </property> </bean>Plugins en openFWPA. En el openFWPA se incluyen algunos plugins de configuración listos para ser utilizados. Además, se proporcionan clases para facilitar el desarrollo de plugins por parte de los usuarios. Figura 6.2. Jerarquía de Plugins Plugins basados en Properties Los plugins basados en Properties almacenan parámetros como pares {atributo, valor}. En el openFWPA se empaquetan dos clases para gestionar este tipo de parámetros: • PropertiesConfigurationPlugin, para cargar los datos de configuración de un objeto java.util.Properties en memoria. <bean id="myPropsConfigurationPlugin" class="es.princast.framework.core.configura <constructor-arg><value>propsPlugin</value></constructor-arg> <property name="priority"><value>12</value></property> <property name="properties"> <map> <entry key="PARAMETRO.UNO"> <value>Un valor</value> 68
  • 76. Operaciones </entry> <entry key="PARAMETRO.DOS"> <value>Otro valor</value> </entry> </map> </property> <property name="contexts"> <list> <value>CONTEXTO.UNO</value> </list> </property> </bean> En la propiedad “properties” se deben indicar todos los pares {clave, valor} que va a servir el plugin. Este plugin puede ser actualizado, pero no se pueden guardar los cambios realizados.• PropertiesFileConfigurationPlugin, que obtiene los parámetros de un fichero de properties. <bean id="baseConfigurationPlugin" class="es.princast.framework.core.configuratio <constructor-arg><value>basePlugin</value></constructor-arg> <property name="priority"><value>12</value></property> <property name="file"><value>${base.module.file}</value></property> <property name="contexts"> <list> <value>SECURITY</value> <value>ROOT.CONTEXT</value> <value>ACTION.CONTEXT</value> <value>JMX.CONTEXT</value> </list> </property> </bean> En la propiedad “file” se debe especificar la ruta del fichero .properties a cargar. Esta ruta puede ser absoluta, relativa al contexto de la aplicación web, o al classpath (siempre que empiece por la cadena “classpath://”).Plugins basados en XMLEste tipo de plugins obtienen la configuración de un documento XML. Este documento puede estar enmemoria (en un String, por ejemplo), o en un fichero.• XMLStringConfigurationProperties. Carga la configuración directamente de un String. <bean id="xmlStringConfigurationPlugin" class="myapp.MyXMLStringConfigurationPlug <constructor-arg><value>xmlStringPlugin</value></constructor-arg> <property name="priority"><value>12</value></property> <property name="xml"> <value> <![CDATA[ <elements> <element value=myId label=bar/> <element value=myId2 label=bar2/> </elements> ]]> </value> 69
  • 77. Operaciones </property> <property name="contexts"> <list> <value>EXAMPLE.CONTEXT</value> </list> </property> </bean> Para configurar este plugin, debe escribirse el documento XML (que contiene la configuración) en la propiedad “xml”. Es importante observar que debe escribirse en una sección CDATA, de lo contrario, el fichero de inicio no sería valido de acuerdo a su DTD. Este plugin es de solo-lectura.• XMLFileConfigurationPlugin. Carga la configuración de un fichero xml. <bean id="xmlFileConfigurationPlugin" class="myapp.MyXMLFileConfigurationPlugin"> <constructor-arg><value>xmlFilePlugin</value></constructor-arg> <property name="priority"><value>12</value></property> <property name="file"> <value>classpath://myfile.xml</value> </property> <property name="contexts"> <list> <value>EXAMPLE.CONTEXT</value> </list> </property> </bean> Para configurar este plugin, se debe indicar el fichero xml de configuración en la propiedad “file”. El path de este fichero puede ser absoluto, relativo respecto al contexto de la aplicación web, o respecto al classpath (si empieza por la cadena “classpath://”, como ocurre en el ejemplo).Debido a la flexibilidad del formato XML, este tipo de plugins delegan el análisis del documento, y lagestión de los parámetros de configuración, en un objeto auxiliar: XMLConfigContentHandler.Para definir un plugin de configuración XML, es necesario extender la claseXMLConfigContentHandler. Se deben implementar métodos para:• Analizar el documento XML. El análisis del documento se realiza utilizando el API Apache Commons Digester. Los métodos a implementar son: setDigesterRules(Digester), en el que se definirán las reglas del Digester para analizar el fichero y registerDTDs(Digester), para registrar las DTD que se quieran utilizar para validar el documento XML proporcionado.• Gestionar los parámetros de configuración. Además, se deben implementar métodos que permitan obtener un determinado parámetro (getParameter()), escribir un valor (setParameter()), etc./** * * El esquema de la configuración que acepta es: * * <elements> * <element value="myId" label="bar"/> * <element value="myId2" label="bar2"/> * </elements> */public class FooContentHandler extends XMLConfigContentHandler { 70
  • 78. Operacionespublic FooContentHandler(String name) { super(name);}protected void setDigesterRules(Digester digester) { digester.addObjectCreate("elements", "java.util.ArrayList"); digester.addObjectCreate("elements/element", "es.princast.framework.core.vo.PropertyBean"); digester.addSetProperties("elements/element"); digester.addSetNext("elements/element", "add", "es.princast.framework.core.vo.PropertyBean");}public void setParameter(String key, String value) { throw new UnsupportedOperationException();}public List getConfigurationObjects() { return (List) getXMLObject();}public String getParameter(String key) { PropertyBean pb = (PropertyBean) getConfigurationObject(key); return (pb != null) ? pb.getLabel() : null;}public Enumeration getKeys() { List objects = getConfigurationObjects(); Vector keys = new Vector(objects.size()); Iterator it = objects.iterator(); while (it.hasNext()) { PropertyBean pb = (PropertyBean) it.next(); keys.add(pb.getValue()); } return keys.elements();}public Object getConfigurationObject(String key) { List objects = getConfigurationObjects(); Iterator it = objects.iterator(); while (it.hasNext()) { PropertyBean pb = (PropertyBean) it.next(); if (key.equals(pb.getValue())) { return pb; } } return null; 71
  • 79. Operaciones } public void setConfigurationObject(String key, Object value) { PropertyBean pb = (PropertyBean) getConfigurationObject(key); if (pb != null) { if (value instanceof PropertyBean) { pb.setLabel(((PropertyBean) value).getLabel()); } else { pb.setLabel(value.toString()); } } else { List objects = (List) getXMLObject(); if (value instanceof PropertyBean) { objects.add(pb); } else { objects.add(new PropertyBean(key, value.toString())); } } } public String getParameter(String path, String key) { return getParameter(key); } public Object getConfigurationObject(String path, String key) { return getConfigurationObject(key); } protected void registerDTDs(Digester digester) { //No se registran DTDs } }Logging Para la gestión de las sentencias de log, en el openFWPA, se utiliza la librería: Log4j [18] (http:// logging.apache.org/log4j/). Log4j tiene tres componentes principales: loggers, appenders y layouts. Estos tres tipos de componentes trabajan juntos para permitir a los desarrolladores escribir mensajes de log de acuerdo a un tipo de mensaje y prioridad, y para controlar en tiempo de ejecución la forma en que estos mensajes se formatean y donde se escriben.Log4J. ComponentesLoggers Son entidades con nombre. Sus nombres son sensibles al contexto y siguen una regla de nombrado jerárquica. Por ejemplo, el logger de nombre “com.foo” es padre del logger de nombre “com.foo.Bar”. El logger root reside en la cima de la jerarquía de logres. Siempre existe y no puede ser recuperado por nombre. Para recuperarlo se ha de invocar a método estático Logger.getRootLogger(). Es posible asignar un nivel a un logger. Los niveles disponibles por orden de menor a mayor son: DEBUG para mostrar mensajes de depuración 72
  • 80. Operaciones INFO para mostrar información sobre lo que está haciendo la aplicación WARN para mostrar mensajes de alerta sobre eventos de los que se desea mantener constancia, pero que no afectan al correcto funcionamiento del problema ERROR para mostrar mensajes de error que afectan a la aplicación, pero que lo permiten seguir funcionando (por ejemplo, algún error en un parámetro de configuración) FATAL para mostrar mensajes críticos del sistema, generalmente después del mensaje la aplicación finalizará Si a un logger no se le asigna un nivel, lo hereda de su antecesor más cercano que tenga asignado uno. Para asegurarse que todos los loggers tienen un nivel, el logger raíz siempre tiene asignado un nivel. Las escrituras se realizan invocando a alguno de los métodos de impresión del logger: debug, info, warn, error, fatal y log. El método de impresión define el nivel de la escritura. Una escritura se dice que está habilitada si su nivel es mayor o igual que el nivel de su logger. De otra forma se dice que la escritura esta deshabilitada. El orden de los niveles es: DEBUG < INFO < WARN < ERROR < FATAL.Appenders Log4j permite que los mensajes se impriman en múltiples destinos, a cada uno de los cuales se le denomina Appender. Algunos de los Appenders disponibles son: ConsoleAppender escribe los mensajes de log en la consola FileAppender escribe los mensajes de log en un fichero RollingFileAppender escribe los mensajes de log a un fichero al que se le pueden definir políticas de rotación para que no crezca indefinidamente DailyRollingFileAppender escribe los mensajes de log en un fichero a que se le puede definir políticas de rotación basadas en la fecha SocketAppender escribe los mensajes de log hacia un servidor remoto de log SMTPAppender envía un correo electrónico con los mensajes de log, generalmente se utiliza para los niveles ERROR y FATAL JDBCAppender escribe los mensajes de error a una base de datos SyslogAppender escribe los mensajes de error hacia el daemon syslog de los sistemas operativos Unix NTEventLogAppender escribe los mensajes de log en los log del sistema de Windows NT JMSAppenders serializa los eventos y los transmite como mensaje JMS de tipo ObjectMessage Para más información sobre las opciones de configuración de los Appenders, consultar la documentación de Log4j.Layouts El Layout es el responsable de formatear los mensajes de log de acuerdo a las definiciones del desarrollador. Los tipos de Layouts disponibles son: 73
  • 81. Operaciones SimpleLayout prioridad del mensaje seguida por “-“ y luego del mensaje de log PatternLayout especifica el formato de salida de acuerdo a unos patrones de conversión similares a los de la función printf del lenguaje C (para más información consultar la documentación) HTMLLayout especifica que la salida será una tabla HTML XMLLayout especifica que la salida será un fichero XML que cumple con el log4j.dtd TTCCLayout consiste en la fecha, thread, categoría y NDC, cualquiera de estos cuatro campos puede ser deshabilitado y habilitado individualmenteConfiguración En el diagrama de la Figura 6.3, “Estados del Sistema de Logging” se puede ver el ciclo de vida de la configuración del Sistema de Log de una aplicación que utiliza openFWPA. Los estados por los que puede pasar son los que siguen: 1. No Log: En este estado no hay configuración alguna cargada. Si se lanza alguna sentencia de log, aparecerá un WARNING en la salida estándar del servidor OC4J. Este estado no debería producirse pero se puede dar en caso de que se inicialicen componentes antes que el framework (listeners, ejbs, etc.) 2. Arranque: Se carga la configuración de arranque del openFWPA. Esta configuración se definirá en el fichero log4j.properties ubicado en el classpath de la aplicación. En este fichero se puede definir una configuración de log, únicamente activa durante el proceso de arranque de la aplicación. 3. Runtime: En este estado se supone activa la configuración de log de la aplicación. Esta configuración se define en el fichero xml indicado en el parámetro de configuración LOGGING_XMLCONF, o en el fichero de properties, cuyo path se indica en el parámetro de configuración, LOGGING_PROPERTIES. Figura 6.3. Estados del Sistema de LoggingEjemplo de Configuración El sistema de logging se configura a través de, por ejemplo, el fichero WEB-INF/log4j.xml (path que se especifica bajo la constante de configuración: LOGGING_XMLCONF). Este fichero debiera estar dentro del fichero war de la aplicación. Puede verse un ejemplo de utilización en la aplicación en blanco (App Blank) y en la de ejemplo (Sample App). <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j=http://jakarta.apache.org/log4j/> <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/> </layout> </appender> <appender name="HTML" class="org.apache.log4j.DailyRollingFileAppender"> 74
  • 82. Operaciones <param name="File" value="carrito-log.html" /> <layout class="org.apache.log4j.HTMLLayout"> </layout> </appender> <appender name="AUDIT" class="org.apache.log4j.DailyRollingFileAppender"> <param name="File" value="carrito-audit.html" /> <layout class="org.apache.log4j.HTMLLayout"> </layout> </appender> <category name="es.princast.framework"> <appender-ref ref="HTML" /> </category> <category name="PISTA_AUDITORIA"> <priority value ="INFO" /> <appender-ref ref="AUDIT" /> </category> <root> <priority value ="info" /> <appender-ref ref="HTML" /> </root> </log4j:configuration> Atención Obsérvese que a pesar de haberse definido el appender STDOUT, no se está utilizando en ningún caso. Este "no uso" es intencionado para evitar que, por descuido o por accidente, se vuelque indiscriminadamente el log de las aplicaciones en la consola de los servidores, ya que se puede producir un error por desbordamiento, en caso (muy probable) de que los administradores hagan una redirección a fichero de la salida. En este fichero se definen varios Appenders (o destinos donde el logger va a escribir). La salida estándar, un fichero Html (que cambiará todos los dias) y otro Appender de nombre Audit similar al anterior. Para cada uno de ellos se define también el layout o plantilla que se utilizará para escribir la salida. También se definen categorías, donde se especifica para cada logger el nivel que tendrá y los Appenders que utilizará. A continuación se muestra la salida de un Appender que escribe en un fichero Html. Para más información sobre la configuración del logger consultar la documentación del mismo en la dirección http://logging.apache.org/log4j/docs/manual.htmlComponentes del openFWPA para Logging.LoggingManager En el núcleo (core) del openFWPA se define la clase LoggingManager, cuyo objeto es ser un controlador centralizado para todo el Sistema de Logging. Esta clase permite: • Cargar configuraciones Log4J desde ficheros (xml o de properties) 75
  • 83. Operaciones • Obtener los loggers estándar del framework: Pista de Auditoría y Pista de Rendimiento. El LoggerManager se puede gestionar desde la consola JMX.PrincastHTMLLayout Además de los layouts incluidos en Log4J (Ver “Layouts”), las aplicaciones también pueden utilizar PrincastHTMLLayout. Este objeto es igual que el HTMLLayout estándar, con la diferencia de que muestra la fecha y hora en que se escribe la sentencia de log.Pista de auditoría Las aplicaciones han de registrar todas las operaciones de usuario que realicen en la denominada pista de auditoria. Para cada entrada aparecerá el usuario y la dirección IP desde la que se realizó la operación (registro NDC), más una descripción de la operación realizada. La salida es configurable, pudiendo ser de distintos formatos y en distintos soportes. Por defecto, se vuelca en un fichero en formato HTML. Cada día se mueve el contenido de este log a un fichero con la extensión .YYYY-MM-DD. El siguiente ejemplo se ha sacado de la aplicación de ejemplo, y muestra la ejecución de dos operaciones marcadas como auditables: Un ejemplo de código que escribe información en la pista de auditoria sería el siguiente: LoggingManager.getLogging().getAuditingTrack().info("[" + NDC.peek() + "] Añadiendo Se hace uso de la clase LoggingManager de openFWPA, la cual se obtiene mediante el método estático getLogging(). El método getAuditingTrack() del LoggingManager devolverá la pista de auditoria, en la cual se escribirá como si se tratase de cualquier otro logger. Es necesario incluir la información sobre el usuario y la IP desde la que esta accediendo, información disponible mediante el método peek de la clase NDC de Log4j. La pista de auditoria es un logger (Log4J) sobre el que se escribe la traza de accesos. Este logger se configura, de igual forma que los loggers de aplicación, utilizando el fichero log4j.xml. <appender name="AUDIT" class="org.apache.log4j.DailyRollingFileAppender"> <param name="File" value="example-audit.html" /> <layout class="es.princast.framework.core.logging.layouts.PrincastHTMLLayout" </layout> </appender> <category name="PISTA_AUDITORIA"> <priority value ="INFO" /> <appender-ref ref="AUDIT" /> </category> El nombre del logger de auditoría se puede configurar en el fichero de inicialización del openFWPA: princast-init-script.xml, como se indica en el ejemplo. <bean id="loggingManager" class="es.princast.framework.core.logging.LoggingManager" factory-method="getLogging" lazy-init="false" singleton="true"> <property name="auditingTrack"><value>MY_AUDIT_TRACK</value></property> </bean> El nombre por defecto del logger de auditoría es: “PISTA_AUDITORIA”. 76
  • 84. OperacionesPista de Rendimiento La Pista de Rendimiento es un logger, muy similar a la Pista de Auditoría, que permite escribir las mediciones de rendimiento que se realicen en la aplicación. La configuración de la Pista de Rendimiento, se realizará en el fichero log4j.xml. <appender name="PERFORMANCE" class="org.apache.log4j.DailyRollingFileAppender"> <param name="File" value="example-audit.html" /> <layout class="es.princast.framework.core.logging.layouts.PrincastHTMLLayout" </layout> </appender> <category name="PISTA_RENDIMIENTO"> <priority value ="INFO" /> <appender-ref ref="AUDIT" /> </category> El nombre del logger de rendimiento se puede configurar en la definición del LoggingManager en el fichero de arranque princast-init-script.xml. <bean id="loggingManager" class="es.princast.framework.core.logging.LoggingManager" factory-method="getLogging" lazy-init="false" singleton="true"> <property name="performanceTrack"><value>MY_PERFORMANCE_TRACK</value></property> </bean> El nombre por defecto del logger de la Pista de Rendimiento es: “PISTA_RENDIMIENTO”.Ficheros de Configuración Para la implementación de aplicaciones web, utilizando el openFWPA, es necesario definir correctamente los ficheros web.xml y struts-config.xml. En los apartados siguientes, se describe el contenido que éstos deberían tener.web.xml Como ejemplo de fichero web.xml, se tomará el incluido en la aplicación en blanco (Blank App). El primer bloque que se encuentra es el referente a la configuración de filtros. Se define el filtro Gzip (llamado GZIPFILTER), y se indica que todas las peticiones servlet de nombre action (que es el ActionServlet) pasen por dicho filtro. <!-- filtro GZip --> <filter> <filter-name>GZIPFILTER</filter-name> <filter-class>es.princast.framework.web.filter.gzip.GZIPFilter</filter-class> </filter> <!-- Mapear el filtro GZip con el ActionServlet --> <filter-mapping> <filter-name>GZIPFILTER</filter-name> <servlet-name>action</servlet-name> </filter-mapping> Otro filtro que se proporciona en la aplicación en blanco es el de control de autenticación. Para su activación es necesario incluir los siguientes elementos XML: 77
  • 85. Operaciones<filter> <filter-name>SecurityFilter</filter-name> <filter-class>es.princast.framework.web.filter.security.corp.PrincastSecurityFilte </filter><!-- Mapeo del filtro de seguridad --><filter-mapping> <filter-name>SecurityFilter</filter-name> <servlet-name>/action/*</servlet-name></filter-mapping>El siguiente conjunto de elementos hacen referencia al arranque e inicialización de la aplicación. Setrata de la variable de contexto que indica la ubicación del fichero de arranque (princast-init-script.xml) y el listener de inicialización: <context-param> <param-name>INIT.SCRIPT.FILE</param-name> <param-value>/WEB-INF/princast-init-script.xml</param-value> </context-param> <listener> <listener-class>es.princast.framework.web.startup.PrincastStartupListener</li </listener>En el siguiente bloque se configura el ActionServlet. Se le pasan como parámetros:config nombre del fichero de configuración.debug el nivel de detalle de debug que controla cuanta información se escribe en el log. Acepta como valores: 0 apagado, y de 1 (menos detalle) a 6 (más detalle).detail el nivel de detalle del Digester que se usa para procesar los ficheros de configuración de la aplicación. Acepta los mismos valores que el parámetro debug.application el nombre del fichero de recursos que contendrá los mensajes de la aplicación.validating especifica si se debe usar un parser validador de XML para procesar el fichero de configuración.definitions-config nombre del fichero donde se especifican las definitions de pantallas.<servlet> <servlet-name>action</servlet-name> <servlet-class>es.princast.framework.web.action.PrincastActionServlet</servle<init-param><param-name>config</param-name><param-value>/WEB-INF/struts-config.xml</param-value></init-param><init-param><param-name>debug</param-name><param-value>3</param-value></init-param><init-param> 78
  • 86. Operaciones <param-name>detail</param-name> <param-value>3</param-value> </init-param> <init-param> <param-name>application</param-name> <param-value>resources.ApplicationResources</param-value> </init-param> <init-param> <param-name>validating</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>definitions-config</param-name> <param-value>/WEB-INF/tiles-defs.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> El siguiente bloque indica al contenedor que debe redirigir cualquier petición que contenga “/action/” al ActionServlet, es decir, al servlet configurado en el bloque anterior. <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>/action/*</url-pattern> </servlet-mapping> En este bloque se indica que fichero servirá por defecto la aplicación. Tiene que ser un fichero físico, no es posible indicar una acción. Si se desea, en el propio fichero se puede redirigir a una acción como se hace en la aplicación de ejemplo (Sample App). <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> A continuación se configuran las librerías de tags que utiliza la aplicación. A partir de la versión del Framework 1.5, se utiliza la versión de Struts 1.2, por lo que no se necesitan configurar las tags de Struts. <taglib> <taglib-uri>/WEB-INF/princast.tld</taglib-uri> <taglib-location>/WEB-INF/princast.tld</taglib-location> </taglib>struts-config.xml En este apartado se explicará el formato del fichero struts-config.xml incluido en la aplicación en blanco (Blank App). Este fichero está dividido en varios bloques: form-beans, global- exceptions, global-forwards, action-mappings, message-resources y plug-ins. <form-beans> Aquí se definen los beans de formulario, es decir, clases que heredan de es.princast.framework.web.action.PrincastActionForm, y que sirven para almacenar 79
  • 87. Operacioneslas propiedades introducidas en formularios enviados mediante peticiones Http. Estos formularios seránutilizados por las acciones.<form-bean name="loginForm" type="es.princast.framework.blankPA.forms.LoginForm" />Otro tipo de beans de formulario con lases.princast.framework.web.action.PrincastDynaActionForm. Sirven para definir unbean de formulario a través del fichero de configuración sin tener que escribir una clase para ello. Suspropiedades son visibles con métodos get y set de la misma forma que un bean convencional.<form-bean name="detalleProductoForm"dynamic="true"type="es.princast.framework.web.action.PrincastDynaActionForm"><form-property name="detalle" type="es.princast.framework.carrito.vo.ProductoVO" /></form-bean><global-exceptions>Se utilizan para tratar excepciones que no han sido tratadas en la Action.En el siguiente ejemplo se indica que las excepciones no capturadas del tipoes.princast.framework.exceptions.PrincastException, sean reenviadas a la páginaindicada en el atributo path (en este caso se trata de una definition). El mensaje de error quese mostrará en la página de error con el tag <html:errors/>, estará en el fichero de recursos(ApplicationResources.properties) con la clave cuyo valor se indique en el atributo key.Mediante el atributo handler se especifica qué manejador de errores se utilizará para tratar la excepción.En este caso se trata del manejador por defecto de Struts (que en el método execute no realiza nada), peropodría definirse un manejador que heredase de él y sobrescribiese el método execute.Se recomienda utilizar el método catchException() de las acciones para tratar las excepciones y norecurrir a excepciones globales.<exception key="global.princastexception" type="es.princast.framework.exceptions.Pr<global-forwards>Son ActionForwards (asociaciones entre nombres lógicos y URIs) disponibles para todas las acciones.Cuando una acción finaliza, devuelve un ActionForward o null. Si la acción no devuelve null, elActionServlet redirige el control al path que sea devuelto por la ActionForward.<forward name="welcome" path="/action/login" /><action-mappings>En esta sección se definen los mapeos de las acciones que manejará la aplicación. Los parámetros comunesa los diferentes tipos de acciones existentes son:path Path relativo al módulo de la acción, comenzando por el carácter /, y sin la extensión del nombre de archivo si se utiliza ésta para el mapeo (por ejemplo, para un mapeo del tipo accion.do, el path sería /accion).type Nombre de la clase, totalmente calificado, de la acción que procesará las peticiones para este mapeo. El atributo no es válido si se especifican los atributos forward o include.scope El contexto (request o session) que se utiliza para acceder a los beans de formulario. 80
  • 88. Operacionesvalidate (def. true) Poner a true si el método validate del bean de formulario debe ser llamado antes de llamar a este mapeo.A continuación se comentarán de forma más detallada los distintos tipos de acciones que maneja elframework.PrincastAction Clase abstracta de la que deben heredar todas las acciones del openFWPA. A continuación se muestra un ejemplo de mapeo: <action path="/login" type="es.princast.framework.carrito <forward name="success" path="/action/viewperfil" redirec <forward name="failure" path="/action/login" redirect="tr </action> La acción lleva anidadas dos ActionForwards a los que se redirigirá en función de que la acción se haya ejecutado con éxito o no. La sintaxis es similar a la de las Global forwards. En el caso de no encontrar la Forward que devuelve el método execute de la acción local a la propia acción, se buscará la misma a nivel global. Es necesario que a ámbito local o global haya definidos dos forwards success y failure. Una acción también puede llevar anidadas excepciones. La sintaxis es la misma que la de las Global Exceptions. En el caso de no tener mapeada una excepción local a la acción, se intentaría buscar a nivel global.PrincastDispatchAction Encapsula diferentes métodos de ejecución de una acción en una misma clase. Para ello, se especifica en el atributo parameter del mapeo de la acción en el fichero struts-config.xml, el nombre del parámetro cuyo valor será el nombre del método que ejecutará la acción. El siguiente ejemplo muestra el mapeo de una acción que hereda de PrincastDispatchAction. La acción ejecutará el método cuyo nombre se le pase en el parámetro method. <action path="/carrito" type="es.princast.framework.carri <forward name="success" path="carrito.viewcarrito" redire </action>PrincastCRUDAction Hereda de la PrincastDispatchAction. Está pensada para manejar métodos de creado, consulta, actualizado y borrado (New, Create, Retrieve, List, Update y Delete). Los posibles nombres de los métodos a ejecutar están defini-dos como constantes en la propia clase: • CREATE_KEY: cuyo valor es create • RETRIEVE_KEY: cuyo valor es retrieve • UPDATE_KEY: cuyo valor es update • DELETE_KEY: cuyo valor es delete • NEW_KEY: cuyo valor es new 81
  • 89. Operaciones • LIST_KEY: cuyo valor es listPrincastForwardAction Redirecciona a la URI relativa al contexto que se especifique en la propiedad parameter del mapeo de la acción. Un ejemplo de utilización de una acción de este tipo es el siguiente: <action path="/welcome" parameter="carrito.login" type="e La acción redirecciona a la definition de nombre carrito.login.PrincastParameterAction Busca un parámetro en la request de nombre dispatch y lo usa para obtener un ActionForward. Una vez conseguido esto, va en busca de un segundo parámetro en la request cuyo nombre debe ser especificado en la propiedad parameter del mapeo de la acción. Este valor se concatena con la URI obtenida de la propiedad path del ActionForward que se buscó con el valor del parámetro dispatch, y se redirecciona a la URI resultante.PrincastExistsAttributeAction Verifica la existencia de un atributo en alguno de los ámbitos posibles (request, session o application). En la propiedad parameter del mapeo de la acción se le indicará el ámbito y el nombre del atributo a buscar siguiendo la siguiente sintaxis: Parameter=”ambito;ATRIBUTO” Si se quiere buscar en cualquier ámbito, se especificará el valor *. Si no se especifica alguno de los dos parámetros se produce un error. parameter="application;HOURS" parameter="*;HOURS"PrincastRemoveAttributeAction de eliminar un atributo en alguno de los ámbitos Trata posibles (request, session o application). Si el atributo existe devuelve el control a un ActionForward instanciado con Tokens.SUCCESS y sino con uno instanciado con Tokens.FAILURE. La sintaxis es idéntica a la de la PrincastExistsAttributeAction. <controller> En el siguiente bloque se configura el controlador (RequestProcessor). Se recomienda utilizar el controaldor proporcionado por el openFWPA (PrincastTilesRequestProcessor). El uso de este controlador permite añadir alguna funcionalidad extra que no implementa el controaldor por defecto (por ejemplo, múltiples “input” por action, forwards a entradas de menú, etc.) <controller processorClass="es.princast.framework.web.act El uso de este RequestProcessor limita los posibles mapeos que se pueden hacer sobre el ActionServlet (ver a continuación). Las Actions únicamente se pueden mapear a un path del tipo: “/path/*”. No se permiten mapeos del tipo: “/path/ *.do”. <message-resources> 82
  • 90. Operaciones Indica que fichero contiene los mensajes que mostrará la aplicación. En este caso se trata del ApplicationResources.properties del paquete resources. <message-resources parameter="resources.ApplicationResour <plug-in> Aquí se indica que plug-ins va a utilizar la aplicación. En primer lugar se indica que se utilizará el validador de Struts, y que las reglas de validación se encuentran en validator-rules.xml y los mapeos entre los formularios y las reglas en validation.xml. <plug-in className="org.apache.struts.validator.Validator <set-property property="pathnames" value="/WEB-INF/valida </plug-in> A continuación se indica que se utilizara tiles, y que las definitions se describirán en el fichero tiles-defs.xml. <plug-in className="org.apache.struts.tiles.TilesPlugin"> <set-property property="definitions-config" value="/WEB-I <set-property property="moduleAware" value="true" /> <set-property property="definitions-parser-validate" valu </plug-in> Y por último se indica que se utilizara el Struts Menu [16] y que su configuración residirá en el fichero menu-config.xml. <plug-in className="net.sf.navigator.menu.MenuPlugIn"> <set-property property="menuConfig" value="/WEB-INF/menu- </plug-in>Filtros web Con vistas a poder acceder de forma sencilla a las peticiones y respuestas HTTP que circulan entre el navegador del cliente y el servidor Web se proporciona una arquitectura de filtros extensible de forma sencilla.Filtros del openFWPA. PrincastFilter. Todos los filtros (Servlet Filters) proporcionados por el openFWPA extienden del filtro base: PrincastFilter. Este filtro extiende la jerarquía básica de Servlet Filters proporcionada por la plataforma J2EE y ofrece algunas funcionalidades extra. En caso de que las aplicaciones vayan a implementar sus propios filtros, es recomendable siempre extender la clase PrincastFilter.Implementación de un nuevo filtro El proceso de implementación de un nuevo filtro que haga uso de la infraestructura facilitada por el openFWPA es muy sencillo. A continuación se detallan los pasos a seguir: 83
  • 91. Operaciones 1. Crear una clase Java que extienda la clase PrincastFilter. 2. Sobrescribir el método filter() para añadir la lógica de negocio del filtro. Atención El método que se debe sobreescribir es filter() y no doFilter() como se haría para escribir un filtro normal. El método doFilter() es final en la clase PrincastFilter. 3. Crear un interfaz java que extienda el interfaz PrincastFilterMBean. Este interfaz debe tener como nombre el de la clase Java que define el nuevo filtro seguido de MBean. Esto es necesario para poder hacer uso de las facilidades JMX [19] aportadas por el openFWPA para la gestión de los filtros. No es necesario que el nuevo interfaz tenga métodos. 4. Si se quiere implementar alguna lógica al inicializar el filtro, se debe extender el método initFilter(). Atención A partir de la versión 1.5, el método init() de la clase PrincastFilter es final, por lo tanto ya no se puede extender dicho método.Gestión de los filtros de forma dinámica Una de las características más novedosas aportadas por el openFWPA en cuanto a la gestión de los filtros es que estos pueden ser conectados y desconectados “en caliente” utilizando para ello una consola JMX. De esta forma, el administrador de la aplicación, en base a determinadas métricas de rendimiento, puede decidir en un momento dado si desea tener un filtro que comprima la respuesta enviada al cliente, arrancándolo o apagándolo sin necesidad de tener que para la aplicación para aplicar la nueva configuración en el fichero web.xml.Filtros activables por reglas A partir de la versión 1.4 del openFWPA, los filtros que extienden PrincastFilter pueden ser desactivados para un conjunto determinado de URLs. Este tipo de filtros se pueden mapear sobre un patrón de URL (como cualquier otro filtro) pero es posible definir un subconjunto de patrones de URL, para las cuales el filtro no entrará en acción. Cada uno de los patrones de URL excluidos de la acción del filtro se definirá utilizando un “init param”, en el fichero web.xml. Estos parámetros deben cumplir el siguiente convenio de nombrado: “EXCLUDED.URL.<Identificador descriptivo del patrón>”. <filter> <filter-name>ExampleByRuleFilter</filter-name> <filter-class> es.princast.example.web.filter.ExampleFilter </filter-class> <init-param> <param-name>EXCLUDED.URL.SELECCION</param-name> <param-value> /action/seleccionaExplotacion, </param-value> </init-param> <init-param> 84
  • 92. Operaciones <param-name>EXCLUDED.URL.LOGOUT </param-name> <param-value> /action/logout </param-value> </init-param> </filter> Además, utilizando la consola JMX, es posible actualizar, “en caliente”, los patrones de URLs que se excluirán del efecto del filtro.Configuración del filtro GZIPFilter openFWPA tiene integrado un filtro (GZIPFilter) que se encarga de comprimir la respuesta usando compresión gzip siempre que está esté soportada por el navegador. Para poder hacer uso del filtro que viene integrado con el openFWPA es necesario definirlo en el fichero de configuración web.xml. Un ejemplo de la configuración a añadir en este fichero se presenta a continuación: <!-- filtro GZip --> <filter> <filter-name>GZIPFILTER</filter-name> <filter-class>es.princast.framework.web.filter.GZIPFilter</filter-class> </filter> <!-- Mapear el filtro GZip con el ActionServlet --> <filter-mapping> <filter-name>GZIPFILTER</filter-name> <servlet-name>carrito</servlet-name> </filter-mapping>Configuración del filtro SecurityFilter Para el control de acceso de las aplicaciones que usen el openFWPA se facilita un filtro al efecto. Para poder hacer uso de este filtro es necesario definirlo en el fichero de configuración web.xml. Un ejemplo de la configuración a añadir en este fichero se presenta a continuación: <filter> <filter-name>SecurityFilter</filter-name> <filter-class> es.princast.framework.web.filter.security.corp.PrincastSecurityFilter </filter-class> </filter> El filtro de seguridad se enmarca dentro del sistema de seguridad del openFWPA. Para mas información, refiérase al apartado: “Seguridad”, de este mismo documento.Filtro de navegación El Filtro de Navegación (clase NavigationFilter) tiene dos objetivos: 1. Mantener siempre el conocimiento de las entradas de menú (y submenu) activas, evitando la necesidad de utilizar tecnologías del lado del cliente como JavaScript y Cookies. 85
  • 93. Operaciones 2. Mantener el estado de la barra de navegación (Ver ???). <filter> <filter-name>NavigationFilter</filter-name> <filter-class>es.princast.framework.web.filter.navigation.NavigationFilter</filter </filter> <filter-mapping> <filter-name>NavigationFilter</filter-name> <url-pattern>/action/*</url-pattern> </filter-mapping> El Filtro de Navegación debe estar mapeado a todas las URLs referenciadas por alguna entrada de menú o submenu. El Filtro de Navegación, extrae de la Request los valores de los parámetros GET que indican la entrada de menú y submenu activas y, a partir de ellas, se encarga de mantener el estado del menú y de la barra de navegación, manteniendolos siempre sincronizados. Para conocer los nombres de los parámetros GET a los que tiene que acceder, el Filtro de Navegación implementa el interface ConfigurationListener (es decir, es un objeto configurable por el sistema de configuración del openFWPA) que escucha el contexto del menú (MENU.CONTEXT), lo que significa, que no se requiere ninguna configuración especial para el Filtro de Navegación, siempre y cuando, se configure convenientemente el menú.Filtro de activación El Filtro de Activación (clase AppActivationFilter) permite definir, cuando una aplicación está activa y cuando no. Este filtro tiene dos funciones: 1. Implementar lógica para activar/desactivar las aplicaciones en función de la fecha actual. 2. Servir como clase base para la implementación de otros filtros que permitan determinar nuevas condiciones para la activación/desactivación de aplicaciones.Activación de aplicaciones en función de fechas El Filtro de Activación permite determinar cuando, en función de la fecha actual, y de unos determiandos intervalos de actividad, está activa la aplicación. Si la fecha actual no encaja en ninguno de los intervalos determinados, se redireccionará a una URL de advertencia y no se dejará entrar a la aplicación. El filtro acepta los siguientes parámetros de inicialización (init-params en web.xml): CONTEXT Nombre del contexto de configuración que se utilzará para especificar los intervalos de actividad de la aplicación. Por defecto vale: ACTIVATION.FILTER. DATE.FORMAT Formato (aceptado por SimpleDateFormat) que se utiliza para especificar el formato de fechas de los intervalos. Por defecto vale: "dd/MM/yyyy HH:mm:ss". ERROR.URL URL a la que se redirigirá en caso de no estar activa la aplicación para la fecha actual. Este atributo es obligatorio. Los intervalos de actividad de la aplicación se definirán en el contexto de configuración (ver Sistema de Configuración del openFWPA) cuyo nombre se indica en el parámetro de inicialización del filtro de nombre: CONTEXT (por defecto: ACTIVATION.FILTER). El nombre de cada parámetro de configuración para estos intervalos debe seguir el patrón: ACTIVE.INTERVAL.X. Donde X es un identificador que diferencia a cada intervalo del resto. 86
  • 94. Operaciones Los intervalos se definen utilizando dos fechas separadas por el caracter "-". El formato de las fechas se define con el parámetro de inicialización: DATE.FORMAT. Si el extremo de un intervalo está indeterminado, se puede utilizar el caracter "?". Por ejemplo: ACTIVE.INTERVAL.1: 22/10/2005 11:11:11 - 22/10/2006 11:11:15 ACTIVE.INTERVAL.2: 22/10/2005 11:11:11 - ? ACTIVE.INTERVAL.3: ? - 22/10/2006 11:11:15 ACTIVE.INTERVAL.4: ?- ?Extensión del Filtro de Activación Es posible extender el Filtro de Activación para definir nuevas condiciones de activación y/o un nuevo tratamiento para el caso en que la aplicación no esté activa. Basta con extender los métodos: isActive() Determina si la aplicación está activa. Recibe como parámetro la request. handleInactive() Se encarga de realizar la lógica adecuada en caso de que la aplicación no esté activa.Filtros de Activación En ocasiones, es necesario poder activar (o desactivar) una aplicación o determinadas partes de la misma, de forma declarativa. El openFWPA, desde la versión 1.5, incluye una clase base (ActivacionFilter) que facilita la implementación de filtros web para la gestión de la activación de las aplicaciones. Básicamente este tipo de filtros tienen dos responsabilidades: a) determinar cuando la aplicación está activa (o no) y b) en caso de que la aplicación esté inactiva ejecutar alguna operación (generalmente redireccionar a una página de aviso). Para implementar filtros de activación es necesario extender la clase ActivationFilter. Los métodos más relevantes de esta clase son: isActive() Determina cuando el recurso solicitado (URL) está activo. Este método devolverá true si el recurso está activo. Se trata de un método abstracto y, por tanto, es de implementación obligatoria por parte de las subclases. handleInactive() Realiza la lógica correspondiente en caso de que el recurso solicitado por el cliente no esté activo. La implementación por defecto redireccionará a una página previamente configurada. configure() Los filtros de activación pueden ser configurados mediante el Sistema de Configuración de openFWPA. Por defecto, todos los filtros de activación toman parámetros del contexto "ACTIVATION.FILTER". Es posible cambiar el nombre del contexto utilizando el parámetro de inicialización del filtro (init- param en web.xml) de nombre: "CONTEXT". Dentro de este contexto, se puede definir una página de redirección (en caso de que el recurso no esté activo), utilizando el parámetro "ERROR.URL".Filtro de Temporalidad El Filtro de Temporalidad permite activar/desactivar una aplicación (o partes de la misma) en función de la fecha en la que se produzca el acceso. 87
  • 95. Operaciones El Filtro de Temporalidad (clase TimingFilter) es un filtro de activación y, por tanto, tiene todas sus características. El Filtro de Temporalidad permite que los recursos que protege estén activos únicamente durante determinados intervalos temporales, definidos como parámetros de configuración. Estos parámetros seguirán el siguiente convenio de nombrado: ACTIVE.INTERVAL.<id del intervalo>. El valor del intervalo, se especificará siguiendo el convenio: <inicio intervalo> - <fin intervalo>, pudiendo especificarse el inicio o el fin del intervalo de las siguientes formas: a. Con una fecha, especificada según cualquier formato estándar válido (SimpleDateFormat). El formato concreto a utilizar se define con el parámetro de configuración: DATE.FORMAT. Por defecto vale: ."dd/MM/yyyy HH:mm:ss". b. Utilizando el caracter "?". En este caso, se supone que se trata de un intervalo abierto, cuyo extremo está inespecificado. Algunos ejemplos de intervalos pueden ser: ACTIVE.INTERVAL.1 = 22/10/2002 11:11:11 - 22/10/2004 11:11:15 ACTIVE.INTERVAL.2 = 22/01/2005 11:11:11 - ? ACTIVE.INTERVAL.3 = ? - 22/10/2106 11:11:15 ACTIVE.INTERVAL.4 = ? - ?Consola de gestión Los componentes del openFWPA, están diseñados para que puedan ser monitorizados y gestionados, en tiempo de ejecución, utilizando una consola de gestión. A través de la consola de gestión, el administrador de sistemas puede arrancar, detener y reconfigurar “en caliente” los componentes del framework que estime conveniente. Para la implementación de la consola de administración se utiliza el API JMX (Java Management Extensions). Este API permite la gestión y configuración de objetos de la aplicación. El sistema de gestión JMX del openFWPA se configura utilizando el contexto de configuración de nombre JMX.CONTEXT. Los parámetros necesarios para activar la consola de gestión son los siguientes: MBEAN.SERVER.NAME Nombre del servidor JMX. Por defecto, su valor es “frameworkpa”. Cada aplicación debería poner su propio nombre como nombre de servidor. JMX.SERVER.ADAPTOR Existen varias implementaciones del estándar JMX y no todas son completamente compatibles entre sí. Cada implementación tiene sus particularidades (criterio de nombrado de MBeans, consola HTML integrada, etc.). Por esta razón, es necesario incluir una capa de abstracción que permita a las aplicaciones, sin necesidad de recompilar, ejecutarse correctamente con cualquier implementación de JMX. Esta capa de abstracción es un “adaptador” para el servidor de MBeans. En función de la implementación JMX del entorno de ejecución, se debe utilizar un adaptador u otro. En el parámetro de configuración JMX.SERVER.ADAPTOR, se indicará el nombre completamente cualificado de la clase que actuará como adaptador para el servidor JMX. Los posibles valores son: • es.princast.framework.core.management.adapters.NullMBeanServe Para no utilizar ningún servidor. La consola de administración JMX aparecerá desconectada. 88
  • 96. Operaciones • es.princast.framework.core.management.adapters.JMXRIMBeanServ Para conectar con la implementación JMX de referencia (JMX-RI). Este adaptador se debe utilizar con el contenedor OC4J 9.0.3. Si se va a utilizar este adaptador, se deben definir, además, los siguientes parámetros de configuración. • JMX.HTTP.PORT. Puerto donde escuchará la consola HTML. • JMX.HTTP.USERNAME. Nombre de usuario válido para acceder a la consola. • JMX.HTTP.PASSWORD. Contraseña válida para acceder a la consola. • es.princast.framework.core.management.adapters.OC4JMBeanServe Para conectar con la implementación incluida en el servidor OC4J 10g. Si se utiliza este adaptador, todo el sistema de operación de las aplicaciones estará integrado en la consola de administración del contenedor.Para conocer más detalles a cerca de la consola de gestión, consultar el Manual de Operaciones. 89
  • 97. Capítulo 7. Seguridad en aplicacionescon openFWPASeguridad En el apartado Seguridad se contemplan todas las políticas y herramientas que permiten, tanto controlar (y monitorizar) el acceso a determinadas partes de una aplicación, como aquellas que permiten garantizar la integridad y confidencialidad de los datos que se transmiten. Los conceptos más importantes dentro del control de acceso a una aplicación son la Autentificación y la Autorización. La Autentificación es el proceso mediante el cual los privilegios de acceso de un usuario son comprobados antes de que pueda tener acceso a un área protegida de la aplicación. Dentro de la autentificación se pueden distinguir diferentes alternativas, siendo las más recurridas la básica (Basic authentication) y la basada en formulario (Form-based authentication).Autentificación básica La autentificación básica delega el control de acceso al servidor Web. El usuario podrá navegar libremente por el sitio Web sin necesidad de facilitar una contraseña. Sin embargo, cuando se intente acceder a una página protegida será el propio navegador el que solicite al usuario un nombre de usuario (username) y una contraseña (password) mostrándole una ventana de dialogo. Tanto el nombre de usuario como la contraseña son enviados sin encriptar al servidor Web, quien se encarga de validar-los contra un fichero plano, un base de datos o servidor de directorios. Si el usuario consigue validarse se comprueba que se tenga privilegio suficiente para acceder al recurso basándose en ciertas políticas definidas, por ejemplo, en un fichero tipo http.conf. Si la comprobación es positiva se sirve la página al cliente. En caso contrario, se le solicita de nuevo la combinación usuario/ contraseña o se le muestra una página de error denegando el acceso.Autentificación basada en formulario Suele ser la alternativa más usada por los sitios Web dada su sencillez. Al igual que en la autentificación básica, el usuario puede navegar libremente por los recursos desprotegidos. En el momento que se intenta acceder a un área protegida se redirecciona al usuario a una página de login con un formulario con los campos nombre de usuario y contraseña. Para distinguir qué áreas están protegidas y cuáles no se emplean patrones de URLs. Dado que las aplicaciones desarrolladas sobre el openFWPA deben correr en el servidor de aplicaciones OC4J [7] el control de acceso se deja en manos de un proveedor de seguridad que viene integrado con el servidor llamado XMLUserManager. Esta integración está implementada como un filtro de URLs y se integra con las APIs Java de Seguridad (JAAS).Autentificación basada en el Filtro de Seguridad delopenFWPA La autentificación basada en el Filtro de Seguridad, es la opción estándar propuesta por openFWPA para el control de acceso. Este tipo de autenticación se basa en un Servlet Filter que se encarga de gestionar tanto el control de acceso como los protocolos de seguridad (http/https) a utilizar. 90
  • 98. Seguridad en aplicaciones con openFWPA Figura 7.1. Esquema del sistema de autenticación Como resultado del proceso de autenticación y autorización, el Filtro de Seguridad del openFWPA, pone a disposición de las aplicaciones todos los datos obtenidos en dicho proceso. Para utilizar la autenticación del openFWPA , se tendrán que tener en cuenta los siguientes puntos: 1. Configuración del Contenedor. El contenedor debe estar configurado convenientemente para permitir el acceso a las aplicaciones bajo el protocolo HTTPS y utilizando sockets SSL-3 (https con certificado digital). 2. Uso de la seguridad web j2ee estándar. En la clase HttpServletRequest, se puede interrogar por las credenciales del usuario para realizar la autenticación. 3. Configuración. Es necesario definir los plugins de configuración adecuados. 4. Paso de credenciales. Usando una extensión de la clase Principal, donde se proporcionan a la aplicación todos los parámetros que se recogieron durante el proceso de autenticación. 5. Diseño de páginas de login. Las aplicaciones necesitan mostrar a los usuarios páginas que les permitan introducir todos los datos necesarios para su autentificación. Estas páginas deben tener unas características determinadas para poder integrarse con la seguridad del openFWPA.Integración con seguridad web J2EE En la clase javax.servlet.http.HttpServletRequest hay tres métodos para controlar el acceso a recursos de la aplicación. Los siguientes párrafos se han extraído de la documentación de la versión 1.3 (JDK 1.3): java.lang.String getRemoteUser() Returns the login of the user making this request, if the user has been authenticated, or null if the user has not been authenticated. java.security.Principal getUserPrincipal() Returns a java.security.Principal object containing the name of the current authenticated user. boolean isUserInRole(java.lang.String role) Returns a boolean indicating whether the authenticated user is included in the specified logical "role". El método getRemoteUser devuelve el nombre del usuario que realiza la HttpRequest. Es equivalente a ((PrincastIdentifier)getUserPrincipal()).getId()(ver abajo). El método getUserPrincipal devuelve el Principal más importante (DNI) – en este caso, como una instancia de la clase PrincastIdentifier. Soporta el método getName() (definido en Principal) y getId() (definido en PrincastIdentifier). getName() devuelve la cadena “DNI” y getId() devuelve el DNIdel usuario. Para la gestión de roles (isUserInRole()) se emplean los roles asignados en el esquema de empleado público. Los roles son tanto para ciudadanos como para empleados públicos.Configuración El sistema de seguridad estándar se configura utilizando el Sistema de Configuración del openFWPA. Todo el sistema tomará parámetros del contexto de seguridad llamado “SECURITY”, por lo tanto, es importante recordar que cualquier plugin que sirve parámetros al sistema de seguridad debe registrarse para el contexto “SECURITY”. 91
  • 99. Seguridad en aplicaciones con openFWPAParámetros básicos Los parámetros básicos son: app-config Bajo este parámetro debe indicarse el nombre de la aplicación. Este es el nombre de la configuración JAAS que se utilizará para realizar la autenticación. http.PORT Puerto http que se utiliza para acceder a la aplicación. Si no se especifica este parámetro, se utilizará el puerto por defecto (80). https.PORT Puerto https que se utiliza para acceder a la aplicación. Si no se especifica este parámetro se utilizará el puerto SSL (443) por defecto. https/cert.PORT Puerto https (SSL-3) con certificado digital de cliente, que se utiliza para acceder a la aplicación. Si no se especifica este parámetro se utilizará el puerto SSL (443) por defecto. http.IP Dirección IP (o nombre DNS) del servidor que atiende peticiones http. Si no se especifica este parámetro, la redirección se realizará sobre la IP del propio contenedor. https.IP Dirección IP (o nombre DNS) del servidor que atiende peticiones https (SSL v2), con certificado de servidor. Si no se especifica este parámetro, la redirección se realizará sobre la IP del propio contenedor. https/cert.IP Dirección IP (o nombre DNS) del servidor que atiende peticiones https (SSL v3), con certificado de cliente. Si no se especifica este parámetro, la redirección se realizará sobre la IP del propio contenedor. Por ejemplo: <bean id="baseConfigurationPlugin" class="es.princast.framework.core.configuration. <constructor-arg><value>basePlugin</value></constructor-arg> <property name="file"><value>ejemplo.properties</value></property> <property name="contexts"> <list> <value>SECURITY</value> <value>ROOT.CONTEXT</value> <value>ACTION.CONTEXT</value> <value>JMX.CONTEXT</value> </list> </property> </bean> Siendo el contenido del fichero ejemplo.properties, el que sigue: # #Fri Jan 07 10:08:36 CET 2005 HIT.COUNTER=es.princast.framework.core.management.mcounters.historic.HistoricalCoun ACTION_MGMT=es.princast.framework.web.action.monitoring.PrincastActionMgmtInterface LOGGING_XMLCONF=/WEB-INF/log4j.xml app-config=EjemploApp http.PORT=8888 https.PORT=4443 https/cert.PORT=8844 https/cert.IP=192.168.7.7 JMX.SERVER.ADAPTOR = es.princast.framework.core.management.adapters.OC4JMBeanServer 92
  • 100. Seguridad en aplicaciones con openFWPAConfiguración JAAS El sistema de autenticación del openFWPA está basada en el estándar JAAS (Java Authentication and Authorization Service). En realidad, el Filtro de Seguridad, no realiza ninguna tarea de autenticación. Estas funciones son delegadas en un Módulo de Login JAAS (LoginModule). La configuración del módulo a utilizar para autenticar y autorizar usuarios se realiza a través del plug-in de configuración JAAS (JAASConfigurationPlugin), que debe ser declarado en el fichero de inicialización princast- init-script.xml. Por ejemplo: <bean id="jaasConfigPlugin" class="es.princast.framework.facilities.security.jaas.c <constructor-arg><value>jaas-config</value></constructor-arg> <property name="file"><value>WEB-INF/jaas-config.xml</value></property> <property name="contexts"> <list> <value>SECURITY</value> </list> </property> </bean> Los módulos JAAS se configuran, en el openFWPA, mediante el fichero jaas-config.xml: <!DOCTYPE jaas PUBLIC "-//Framework PA - Team//DTD JAAS Configuration 1.3F//ES" "jaas-config.dtd"> <jaas> <application name="Carrito" controlFlag="required"> <module>es.princast.framework.modules.security.standalone.StandaloneLoginModule</ <options> <option> <name>USERS.FILE</name> <value>/WEB-INF/authorized-users.xml</value> </option> </options> </application> </jaas> En este fichero se definen los módulos de configuración a utilizar. Cada módulo se define con la etiqueta <application ..>. Cuando se vaya a autenticar a un usuario, se utilizará el módulo cuyo valor del atributo “name” coincida con el parámetro app-config (de los parámetros básicos de autenticación, ver sección anterior). Eventualmente, el atributo “controlFlag” está desactivado. No se tiene en consideración el valor especificado para realizar la autenticación. Para cada módulo, es obligatorio indicar el nombre de la clase que implementa la lógica de autenticación, utilizando la etiqueta anidada: <module>. Esta clase debe implementar el interface: LoginModule, definido en el paquete JAAS. Por último, en la etiqueta <options> se pueden definir todas las opciones de configuración específicas del módulo JAAS que se vaya a utilizar.Reglas de Seguridad Por último, es necesario configurar las reglas de seguridad que definirán que recursos de la aplicación están protegidos, y de que forma. Un recurso sólo puede estar protegido una vez. Si una URL encaja en varios patrones definidos en el fichero de reglas de seguridad, se tendrá en cuenta el patrón más restrictivo. 93
  • 101. Seguridad en aplicaciones con openFWPAPara configurar las reglas de seguridad, se debe definir, en el fichero princast-init-script.xml,un plug-in de configuración de tipo: SecurityRulesConfigurationPlugin. Por ejemplo:<bean id="securityRulesPlugin" class="es.princast.framework.web.filter.security.cor <constructor-arg><value>security-rules</value></constructor-arg> <property name="file"><value>WEB-INF/princast-security-rules.xml</value></propert <property name="contexts"> <list> <value>SECURITY</value> </list> </property> </bean>Las reglas de seguridad se definirán, por su lado, en el fichero princast-security-rules.xml.<!DOCTYPE resources PUBLIC "-//Framework PA - Team//DTD Security Rules Configuration 1.3F//ES" "princast-security-rules.dtd"><resources> <!-- Acceso para ciudadanos --> <resource actor="CITIZEN" level="0" protocol=”https”> <url-pattern>/*</url-pattern> </resource> <resource actor="CITIZEN" level="1"> <url-pattern>/action/*</url-pattern> <forwards> <forward name="login" path="/pages/login.jsp"/> <forward name="no-login" path="/pages/login.jsp"/> <forward name="no-roles" path="/pages/login.jsp"/> <forward name="error" path="/pages/login.jsp"/> </forwards> <roles> <role>EXAMPLE.ROLE.1</role> <role>EXAMPLE.ROLE.2</role> </roles> <options> <option> <option-name>example option</option-name> <option-value>example value</option-value> </option> <options> </resource></resources>En este fichero, cada etiqueta <resource> define un recurso protegido. Un recurso tiene tres atributos:actor, nivel de seguridad y protocolo.El atributo “actor” define el usuario-tipo que va a acceder al recurso. En función de este valor, el procesode autenticación puede ser diferente. La semántica exacta de este atributo la establece el módulo JAASque realice la autenticación.El atributo “level”, define el nivel de protección establecido para el recurso. Generalmente, hay 3: nivel0 para recursos sin protección, nivel 1 para recursos protegidos bajo par usuario/contraseña y nivel 2 pararecursos protegidos con certificado digital. 94
  • 102. Seguridad en aplicaciones con openFWPA Generalmente, un nivel de seguridad permite el login por los mecanismos para sí definidos y también mediante los definidos para niveles superiores. Por ejemplo, un recurso protegido bajo usuario/ contraseña también será accesible con certificado digital. La semántica exacta del nivel de seguridad, la define el módulo JAAS. El protocolo (atributo “protocol”) define el tipo de acceso deseado al recurso. Puede tomar dos valores: “http” si no se quiere ninguna encriptación del recurso o “https” si se desea garantizar la privacidad de los datos transmitidos entre el cliente y la aplicación. Este atributo puede omitirse, en tal caso, se permite el acceso al recurso bajo cualquier protocolo. Un recurso protegido es, exactamente, un patrón URL que se aplica sobre el espacio de direcciones de la aplicación. Este patrón URL se define en la etiqueta anidada <url-pattern>. Si, en un mismo fichero de reglas, varios patrones, de varios recursos, coinciden, el recurso que se utiliza para autenticar es el que defina el patrón más restrictivo de todos. Cuando se produce un error autenticando al usuario, en función de la naturaleza de dicho error, el Filtro de Seguridad, actuará redireccionando a un path determinado. Por ejemplo, si no se puede autenticar al usuario porque la contraseña es incorrecta, se le redirigirá a una página donde se muestra el formulario de entrada. Sin embargo, si no se puede autenticar al usuario porque no tiene privilegios suficientes (roles) para acceder a un recurso, se le puede redireccionar a otra página distinta informándole de dicha situación. Las redirecciones se especifican bajo la etiqueta “<forwards>”. Cada redirección tendrá un nombre que la identifica (“name”) y una url a la que se redirigirá al usuario (“path”). Los nombres de forwards válidos son: login Se redirige a login la primera vez que un usuario trata de acceder a un recurso protegido. no-login Esta redirección se ejecuta cuando no se puede autenticar al usuario por cualquier motivo (por ejemplo, contraseña equivocada) no-roles Se redirige a este forward cuando el usuario, que está correctamente autenticado, no tiene autorización para acceder al recurso. error Esta redirección está reservada a situaciones de autenticación no controladas por los forwards definidos para el recurso. Si no se define forward de error, cuando se de un fallo de autenticación, se devolverá un error http 515. En cada recurso también se definirán los nombres de los roles que tienen acceso. Si no se incluye la etiqueta <roles> , no se realizará chequeo de roles. Por último, se pueden definir las opciones de configuración (<options>) que se estimen convenientes para el recurso. La semántica de estas opciones depende exclusivamente del módulo JAAS que realiza la operación de login.Paso de Credenciales Durante el proceso de autenticación, se recaba una serie de datos del usuario. Estos datos se guardan en la sesión http en un objeto de tipo javax.security.auth.Subject, bajo la clave SecurityGlobals.SUBJECT. Este objeto tiene los siguientes métodos: 1. java.util.Set getPrincipals(). Devuelve un Set con los Principals (datos de usuario) conocidos durante el proceso de autenticación. 95
  • 103. Seguridad en aplicaciones con openFWPA 2. java.util.Set getPrincipals(PrincastIdentifier.class). Devuelve un Set con el Principal más importante, el DNI. Es equivalente a request.getUserPrincipal(). 3. java.util.Set getPrincipals(PrincastCompositePrincipal.class). Devuelve un Set con los Principal secundarios, de tipo java.util.Properties. Este objeto contiene todas las propiedades capturadas en cada escenario en concreto, que son específicas a él (por ejemplo, puede haber o no un IDTercero en función del escenario). 4. java.util.Set getPrincipals(Group.class). Devuelve un Set con todos los roles que soporta un usuario. 5. java.util.Set getPrincipals(ScenarioPrincipal.class). Devuelve un Set que contiene el Principal con los datos del escenario. Este Principal es un mapa que contiene los parámetros que definen el escenario bajo el que se realizó el proceso de autenticación (nivel de seguridad, canal de acceso, tipo de actor, etc.)Recuperación de credenciales de usuario Una vez que el usuario ha sido autenticado, sus credenciales son almacenadas en su sesión bajo la clave SecurityGlobals.SUBJECT. Éste es un objeto de tipo javax.security.auth.Subject. Para recuperar las diferentes credenciales almacenadas en este objeto basta con emplear las diferentes constantes definidas en la clase SecurityConstants como parámetros para las llamadas al método getPrincipal() de la clase Principals contenida en el objeto Subject . Atención No es recomendable obtener los datos de autenticación (j_username y j_password) directamente de los parámetros de la request. No se garantiza que estos datos vayan a ser los correctos. Además, desde la versión 1.5, cualquier parámetro que se utilice como contraseña será enmascarado (reemplazado por la cadena "*********") por el filtro de autenticación. A continuación se muestran diferentes ejemplos de recuperación de credenciales.Recuperación del identificador de tercero de un usuario Subject subject = (Subject)request.getSession(true).getAttribute(SecurityGlobals.SU Set data = subject.getPrincipals(PrincastCompositePrincipal.class); PrincastCompositePrincipal principals = (PrincastCompositePrincipal) data.iterator( String idTercero = principals.getPrincipal(SecurityConstants.ID_THIRD_PARTY);Recuperación de las Unidades Organizativas asociadas a un usuario ... PrincastCompositePrincipal principal = //Obtener el principal normalemente... Map uniOrgs = new HashMap(); String id = principal.getPrincipal(SecurityConstants.ORGANIZATIONAL_UNIT_ID); String name = principal.getPrincipal(SecurityConstants.ORGANIZATIONAL_UNIT_NAME); if (id != null) { uniOrgs.put(id, name); } 96
  • 104. Seguridad en aplicaciones con openFWPA int index = 1; do { id = princpal.getPrincipal(SecurityConstants.ORGANIZATIONAL_UNIT_ID+"_"+index); name = princpal.getPrincipal(SecurityConstants.ORGANIZATIONAL_UNIT_NAME+"_"+index) if (id != null) { uniOrgs.put(id, name); } } while (id != null); ...Recuperación de los dominios de usuario. Subject subject = (Subject) request.getSession(true).getAttribute( SecurityGlobals.SUBJECT); Set principals = subject.getPrincipals(PrincastRole.class); Iterator it = principals.iterator(); while (it.hasNext()) { PrincastRole pr = (PrincastRole) it.next(); Iterator itDom = pr.getDomains().iterator(); while (itDom.hasNext()) { PrincastDomain pd = (PrincastDomain) itDom.next(); if (!pd.isProcedure()) { logger.info("Nombre del dominio: " + pd.getName()) } } }Diseño de páginas de login En las aplicaciones que utilicen el Filtro de Seguridad del openFWPA, deben escribirse páginas JSP para la obtención de los datos de autentificación. Estas páginas varían en función del tipo de autenticación que se quiera realizar. A partir de la versión 1.4 del openFWPA se dispone de un nuevo diseño para las páginas de login en aplicaciones de tramitación (no de portal). Con el fin de facilitar el diseño e implementación de estas páginas, se ha incluido una etiqueta, <ui:login>, en el fichero princast-ui.tld, así como un conjunto de clases CSS definidas en el fichero login.css. Los atributos permitidos por la etiqueta esta descritos en la ??? El filtro de autenticación deja, en el scope session, la URL del recurso protegido solicitado, al que se estaba intentando acceder. El nombre del atributo bajo el que se almacena esta URL está definido por la constante: SecurityGlobals.REQUESTED_URL. Este atributo se puede utilizar para crear formularios de login sin utilizar la etiqueta <ui:login>. Por ejemplo, como sigue: 97
  • 105. Seguridad en aplicaciones con openFWPA <form action="<%=session.getAttribute(SecurityGlobals.REQUESTED_URL)%>" method="pos //Añadir aquí los inputs necesarios para leer el username, contraseña, etc… </form>Login de empleado público En las páginas de login para el escenario de empleado público (tipo de actor: EMPLOYEE), deben incluise, al menos, un campo de texto para obtener el nombre de usuario del empleado (j_username, por defecto) y un campo de tipo “password” para leer su contraseña (j_password por defecto). <?xml version="1.0" encoding="iso-8859-1"?> <!-- No se puede poner DTD. Si se pone DTD no funciona. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtm --> <%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html" %> <%@ taglib uri="/WEB-INF/princast.tld" prefix="princast" %> <%@ page import="es.princast.framework.web.filter.security.common.SecurityGlobals" <html:html locale="es" xhtml="true" > <head> <html:base /> <title> Bienvenido al Principado de Asturias </title> <link type="text/css" rel="stylesheet" href="css/general.css" /> <link type="text/css" rel="stylesheet" href="css/components.css" /> <link type="text/css" rel="stylesheet" href="css/login.css" /> </head> <body onload="document.forms[0].j_username.focus()"> <princast:login> <princast:panel layouterClass="es.princast.framework.web.view.layout.DivLayouter <princast:panel-legend>Login</princast:panel-legend> <princast:block cssClass="login_message"> <p>Bienvenido a la aplicación de ejemplo SampleApp</p> </princast:block> <princast:block cssClass="login_inputs"> <label for="j_username">Usuario:</label> <input type="text" name="j_username" id="j_username" /> </princast:block> <princast:block cssClass="login_inputs"> <label for="j_password">Contraseña:</label> <input type="password" name="j_password" id="j_password" /> </princast:block> <princast:block cssClass="login_submit"> <p> <input type="image" src="images/botones/enter-button.gif" alt="Entrar"/> <input type="image" src="images/botones/right-arrow.gif" alt="Entrar"/> </p> 98
  • 106. Seguridad en aplicaciones con openFWPA </princast:block> </princast:panel> <princast:login-footer> Por favor, si no va a utilizar la aplicación cierre la sesion para evitar que ot </princast:login-footer> </princast:login> </body> </html:html>Login de ciudadano a través del portal. Para el control de acceso de ciudadanos (tipo de actor: CITIZEN), con nivel de seguridad 1 (usuario / contraseña), se requieren los siguientes campos: • Input de tipo “text” para recoger el identificador de usuario (NIF) del ciudadano. Por defecto, el nombre de este campo será “j_username”. • Input de tipo “password” para recoger la contraseña del ciudadano. Por defecto, el nombre de este campo será “j_password”. • Input de tipo “text” para recoger un CIF, en el caso de que el cuidadano represente a una persona jurídica. El nombre de este campo será “cif”Login de ciudadano a través de agente telefónico En este tercer escenario, un ciudadano accede a una aplicación a través de un agente telefónico. En este caso, la página de login no pedirá la contraseña completa. En su lugar, se solicitarán caracteres correspondientes a posiciones de la contraseña del ciudadano, seleccionados de forma aleatoria. Los campos de entrada que se deben definir en este caso son: • Input de tipo “text” para recoger el identificador de usuario (NIF) del ciudadano. Por defecto, el nombre de este campo será “j_username”. • Input de tipo “text” para recoger un CIF, en el caso de que el cuidadano represente a una persona jurídica. El nombre de este campo será “cif” • Un campo de tipo “hidden” para indicar las posiciones de la contraseña que se solicitan. El nombre para estos campos será PASSWORD_POSITIONS. • Un campo de tipo “text” para recoger cada una de las posiciones de la contraseña que se deben suministrar. Por defecto, el nombre para cada uno de estos campos será, “PASSWORD_POSITIONS”+posición. A continuación se muestra un ejemplo de cómo generar los campos de solicitud de posiciones de clave con etiquetas JSP: <table> <logic:iterate id="pos" name="PASSWORD_POSITIONS"> <tr> <td>Introduzca la posicion <%=pos%><input type="hidden" name="PASSWORD_POSITIONS <td><input type="text" name="PASSWORD_POSITIONS_<%=pos%>" size="1" maxlength="1" </tr> </logic:iterate> </table> 99
  • 107. Seguridad en aplicaciones con openFWPALogin de ciudadano a través de colaborador En este escenario (tipo actor COLABORADOR), un ciudandao accede a un recurso a través de un agente intermediario. Este escenario requiere la autenticación tanto del ciudadano como del propio agente intermediario. La autenticación del agente colaborador se realiza con un par usuario/contraseña. La autenticación del ciudadano, se realiza con el par NIF / posiciones de la contraseña. Los campos requeridos en este caso son: • Identificador (username) del colaborador. Se trata de un input de tipo “text” cuyo nombre por defecto será “j_username”. • Contraseña del colaborador. Debe ser un input de tipo “password”. Su nombre por defecto es: “proxyPassword”. • NIF/NIE del ciudadano. Será un input de tipo “text” cuyo nombre por defecto es “nif”. • CIF. Si el ciudadano representa a una persona jurídica, este campo recogerá si CIF. Será un campo input de tipo “text” y nombre: “cif”. • Posiciones de la contraseña. Las posiciones de la contraseña del ciudadano se recogerán en n campos input text de nombre “j_password_x”, siendo x la posición solicitada de la contraseña. Para facilitar la implementación de este tipo de páginas, el filtro de autenticación deja los siguientes atributos en el scope session: • SecurityGlobals.LOGIN_EXCEPTION. Excepción disparada que provoca la aparición de la página de login. • SecurityConstants.USERNAME. Nombre de usuario del colaborador introducido en intentos de login anteriores, erroneos, (si hubo). • SecurityConstants.PASSWORD. Contraseña del colaborador introducida en intentos anteriores, erroneos, de login (si hubo). • SecurityConstants.NIFNIE. NIF del ciudadano introducido en intentos anteriores, erroneos, de login (si hubo). • “PASSWORD_POSITIONS”. Lista de objetos de tipo PropertyBean que contienen, como valor (getValue()) el numeor de posición que se debe solicitar y como label (getLabel()) el valor introducido en intentos anteriores, erróneos, si hubo. <?xml version="1.0" encoding="iso-8859-1"?> <!-- No se puede poner DTD. Si se pone DTD no funciona. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtm --> <%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html" %> <%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean" %> <%@ taglib uri="/WEB-INF/princast.tld" prefix="princast" %> <%@ taglib uri="http://jakarta.apache.org/struts/tags-logic" prefix="logic" %> <%@ page import="es.princast.framework.web.filter.security.common.SecurityGlobals" <%@ page import="es.princast.framework.facilities.security.SecurityConstants" %> <%@ page import="es.princast.framework.core.vo.PropertyBean" %> 100
  • 108. Seguridad en aplicaciones con openFWPA<%@ page import="es.princast.framework.web.filter.security.common.SecurityGlobals"<html:html locale="es" xhtml="true" > <head> <html:base /> <title> Bienvenido al Principado de Asturias </title> <link type="text/css" rel="stylesheet" href="css/general.css" /> <link type="text/css" rel="stylesheet" href="css/components.css" /> <link type="text/css" rel="stylesheet" href="css/login.css" /> </head><body onload="document.forms[0].j_username.focus()"> <princast:login> <princast:panel layouterClass="es.princast.framework.web.view.layout.DivLayouter <princast:panel-legend>Login</princast:panel-legend> <princast:block> <princast:instanceOf variableName="<%=SecurityGlobals.LOGIN_EXCEPTION%>" class <div id="errors"> <ul> <li>Validaci&oacute;n incorrecta de NIF/NIE y Clave personal</li> </ul> </div> </princast:instanceOf> <princast:instanceOf variableName="<%=SecurityGlobals.LOGIN_EXCEPTION%>" clas <div id="errors"> <ul> <li>Introduzca todos los datos que se solicitan</li> </ul> </div> </princast:instanceOf> </princast:block> <princast:block cssClass="login_message"> <p>Bienvenido a la página de login de colaborador de ejemplo</p> </princast:block> <princast:block cssClass="login_inputs"> <p>Identificador de Colaborador</p> <input type="text" name="j_username" id="j_username" value="<%=session.getAttri </princast:block> <princast:block cssClass="login_inputs"> <p>Contraseña del Colaborador</p> <input type="password" name="proxyPassword" id="proxyPassword" value= "<%=session.getAttribute(SecurityConstants.PASSWORD) != null ? session.get </princast:block> <princast:block cssClass="login_inputs"> <p>NIF/NIE del ciudadano</p> <input type="text" id="nif" name="nif" value= "<%=session.getAttribute(SecurityConstants.NIFNIE)!=null ? session.getAttr </princast:block> <princast:block cssClass="login_inputs"> <p>Si representa a una persona jurídica, introduzca su CIF</p> 101
  • 109. Seguridad en aplicaciones con openFWPA <input type="text" id="cif" name="cif"/> </princast:block> <princast:block cssClass="login_inputs"> <p>Introduzca las siguientes posiciones de la contraseña del ciudadano:</p> <logic:iterate id="pos" name="PASSWORD_POSITIONS"> <table> <tr> <td style="text-align: left; width:90%"><span class="textos">Introduzca la p <td style="text-align: right; width:10%"><input type="text" name="j_password value="<%=((PropertyBean) pos).getLabel()%>" size="1" maxlength="1"/> </td </tr> </table> </logic:iterate> </princast:block> <princast:block cssClass="login_submit"> <p> <input type="image" src="images/botones/enter-button.gif" alt="Entrar"/> <input type="image" src="images/botones/right-arrow.gif" alt="Entrar"/> </p> </princast:block> </princast:panel> <princast:login-footer> Por favor, si no va a utilizar la aplicación cierre la sesion para evitar que ot </princast:login-footer> </princast:login> </body> </html:html> La página HTML correposndiente al JSP del ejemplo anterior se puede ver en la imagen siguiente:Excepciones durante el proceso de autenticación En caso de que durante el proceso de autenticación se lance alguna excepción, ésta es almacenada en la sesión del usuario bajo la clave LOGIN_EXCEPTION, que se puede encontrar en la clase SecurityGlobals. Pudiera darse el caso de querer mostrar un determinado mensaje de error en la capa de presentación en función de la excepción que se haya producido. Para ello se proporciona un tag JSP denominada <instanceOf> y que recibe los siguientes parámetros: variableName Nombre de la clave bajo la cual se busca la excepción. className Nombre cualificado de la excepción con la que se realiza la comprobación. Un ejemplo de uso de esta etiqueta se presenta a continuación: <princast:instanceOf variableName="<%=SecurityGlobals.LOGIN_EXCEPTION%>" classNam <div id="errores"> <ul> <li>Validaci&oacute;n incorrecta de NIF/NIE y Clave personal</li> </ul> </div> </princast:instanceOf> 102
  • 110. Seguridad en aplicaciones con openFWPA Como se puede apreciar se comprueba que la excepción que se encuentra almacenada en la clave SecurityGlobals.LOGIN_EXCEPTION sea de la misma clase que es.princast.framework.facilities.security. exceptions.WrongPasswordException. En caso afirmativose incluyen los elementos contenidos entre <princast:instanceOf></princast:instanceOf>. Las excepciones que se pueden disparar durante el proceso de autenticación, se encuentran en el paquete: es.princast.framework.facilities.security.exceptions.Niveles de Seguridad Para la autenticación se contemplan tres niveles de seguridad: Nivel 0 Con este nivel de seguridad no se requiere autenticación. Nivel 1 Con este nivel es necesaria la validación mediante usuario y contraseña. Para ello, deben especificarse dos parámetros denominados j_username y j_password con dicha información. Nivel 2 Este nivel de seguridad se corresponde a la autenticación utilizando certificado digital. Debe tratarse de un certificado digital válido de la FNMT (Fábrica Nacional de la Moneda y Timbre). Para forzar a esta validación debe incluirse en la petición http un parámetro denominado forceLevel con el valor 2. Un ejemplo de uso de este nivel de seguridad se presenta a continuación: http://localhost:8888/prueba/action/suscribe?forceLevel=2 Para mayor información acerca de los niveles de seguridad soportados acúdase a la documentación del Módulo de Login que esté utilizando para su aplicación.Single Sign On Con el fin de facilitar la integración de aplicaciones en un mismo portal, el Principado de Asturias dispone de una solución Single Sign On (SSO), basada en el producto Novell iChain. El sistema Single Sign On permite a un usuario, acceder a varias aplicaciones autenticándose una única vez, valiendo esta autenticación para todas ellas. Actualmente, se pueden autenticar contra el sistema SSO todos los usuarios dados de alta en el LDAP corporativo del Principado de Asturias. El filtro de seguridad del openFWPA soporta, a partir de la versión 1.5 , de forma automática, autenticación basada en el SSO del Principado de Asturias para todos los escenarios de autenticación de Nivel 1 (salvo los que utilizan posiciones de contraseña). Atención Para que el filtro de seguridad se pueda integrar correctamente con SSO, éste debe enviar, en la cabecera de la petición http (header) el par usuario/contraseña codificado en Base64, utilizando es esquema básico (Basic) de autenticación http. Para utilizar el SSO en versiones anteriores a la 1.5, se debe utilizar el "parche para compatibilidad con SSO". 103
  • 111. Capítulo 8. Integración de SistemasTecnologías de Integración Frecuentemente, las aplicaciones desarrolladas con el openFWPA se deben integrar con los sistemas corporativos del Principado de Asturias (bases de datos, módulos comunes, etc). El mecanismo estándar para la interconexión de sistemas, en el Principado de Asturias, es el intercambio de documentos XML sobre el protocolo HTTP 1.1. La estructura de los documentos a intercambiar, se ajusta a la DTD de XML Genérico (para más información solicítese documento correspondiente al Principado de Asturias). <?xml version="1.0" encoding="ISO-8859-1" ?> <SERVICIO siscliente="POR_DEFINIR" sisservidor="BDGENERICOS" nomservicio="CONS_COD_PAIS" fecha="25/06/2003 13:46:46"> <ENTRADAS> <entrada dato="CN_PAIS">108</entrada> </ENTRADAS> <SALIDAS> <ERROR> <salida dato="COD">0</salida> <salida dato="DESC">OK</salida> </ERROR> <salida dato="PAIS">ESPAÑA</salida> </SALIDAS> </SERVICIO> En el recuadro superior, se muestra un ejemplo de XML Genérico recibido como respuesta de la consulta del país, cuyo código es 108. La respuesta, sin errores, es: “ESPAÑA”. El openFWPA ofrece soporte para la integración de aplicaciones con otros sistemas del Principado de Asturias, utilizando tanto intercambio de XML Genérico, como servicios web (allá donde se apliquen). Los componentes del openFWPA que facilitan la integración entre sistemas, implementan el interface: es.princast.framework.facilities.backends.BackEndProxy. Todos los clientes, que proporciona el openFWPA para la integración con los sistemas corporativos del Principado de Asturias son configurables. Es decir, pueden ser configurados en caliente a través del sistema de configuración del openFWPA o, dicho de otra forma, implementan el interface ConfigurationListener.XML Genérico: Configuración La mayoría de los sistemas del Principado de Asturias se comunican entre sí mediante el intercambio de XML Genérico, por esta razón, los componentes del openFWPA que trabajan con este tipo de documentos XML son habituales. Los clientes de sistemas corporativos que utilizan intercambio de XMLGenérico se configuran siguiendo el mismo proceso: 1. Cada cliente dispone de un nombre de contexto identificativo. Por ejemplo: “GENERICOS”, “SMS”, “CLAVES”, etc. 104
  • 112. Integración de Sistemas2. Se debe definir un plug-in de configuración de tipo PropertiesFileConfigurationPlugin en el fichero princast-init-script.xml, que accede a un fichero de configuración (un fichero .properties) específico para el cliente. Este plugin, se debe registrar para el contexto identificativo del cliente.3. Definir en el fichero de properties los siguientes parámetros: • CLIENT_SYS. Identificador de la aplicación cliente. Se trata de una cadena que identifique a la aplicación cliente que solicita el servicio. • SERVER_SYS. Identificador del sistema servidor. Es una cadena identificativa del sistema que recibe la petición. El identificador de cada sistema concreto debe solicitarse al Principado de Asturias. • DTD. URL donde está publicada la DTD del documento XML Genérico que se envía. Se debe solicitar al Principado de Asturias la URL de la DTD que se debe utilizar para cada proyecto concreto. • URL. Dirección del servicio al que se debe enviar el XML Genérico. • USER. Nombre de usuario. Se utiliza para autenticar la petición de servicio. • PASSWORD. Contraseña de acceso. Junto al parámetro USER, se utiliza para la autenticación de la petición. • CONNECTION. Nombre completamente cualificado de la clase que se utilizará para establecer la conexión con el sistema. Este parámetro es opcional. Las conexiones existentes actualmente son: XML_generico.ConexionXML Establece una conexión HTTP 1.1. con el sistema servidor. Es el tipo de conexión por defecto. Establece una conexión con el sistema, sin efectuar validación es.princast.framework.facilities.backends.genericXML.connection. FrameworkConnectionXML de la DTD. Se recomienda su uso únicamente en entornos con conectividad limitada. Realiza URL encode del contenido a enviar al sistema XML Genérico con el que interactua. es.princast.framework.facilities.backends.genericXML.connection. sistema, sin efectuar validación Establece una conexión con el FrameworkConnectionXMLNoEncoding la DTD. Se recomienda su uso únicamente en entornos con de conectividad limitada. Esta conexión es igual a la anterior con la salvedad de que no se realiza el URL encoding. Es el tipo de conexión a utilizar cuando el sistema a conectar sea BDGENERICOS. Si se utiliza este tipo de conexión, no se producirá ninguna es.princast.framework.facilities.backends.genericXML.connection. MockConnectionXML comunicación real con el sistema cliente. La conexión será únicamente simulada. Este tipo de conexión se debe utilizar únicamente para pruebas de desarrollo. 105
  • 113. Capítulo 9. PruebasPruebas unitarias Toda aplicación J2EE desarrollada utilizando el openFWPA debería tener presente, en todo momento, la realización de pruebas unitarias de todos sus componentes relevantes. De esta forma se tiene una batería de pruebas que pueden ejecutarse en cualquier momento como pruebas de regresión. Debiera disponerse de un proceso en el servidor que se encargue de lanzar tales pruebas un número determinado de veces y genere un informe con los resultados de la ejecución de las pruebas. Todo esto se ha tenido en cuenta a la hora de desarrollar el openFWPA y se proporciona una infraestructura basada en JUnit [11] que se puede utilizar a tal efecto. Esta infraestructura base para la implementación de pruebas unitarias se empaqueta en la librería de herramientas del openFWPA: fwpa-toos.jar Se proporciona un conjunto de clases plantilla para facilitar la implementación de pruebas unitarias. Estas clases de prueba se pueden clasificar en dos grandes grupos: • Pruebas locales. Se ejecutan en el propio PC de desarrollo. • Pruebas en contenedor. Se ejecutan en una aplicación web, sobre el servidor de aplicaciones. El openFWPA proporciona soporte para la implementación de pruebas unitarias en contenedor, utilizando la librería Cactus, de Apache.Jerarquía de clases para las pruebas unitarias En función del tipo de prueba unitaria que se desee realizar (prueba de bases de datos, de Actions Struts, etc.) se dispone de una clase base específica que debe ser extendida. Las clases base para pruebas unitarias suministradas en openFWPA se muestran en la figura que sigue: Figura 9.1. Set de pruebas unitarias disponibles • PrincastTestCase. Se utiliza para la implementación de pruebas unitarias para clases estándar. • PrincastContextTestCase. Se utiliza para probar beans de Spring. • PrincastJMXComponentTestCase. Para la implementación de pruebas de MBeans JMX. • PrincastDatabaseTestCase. Se debe extender para implementar pruebas unitarias de componentes que acceden a bases de datos. • PrincastDAOTestCase. Se debe extender para implementar pruebas unitarias de DAOs (Data Access Objects). • PrincastReportTestCase. Se utiliza para probar generación de reports. • PrincastMockStrutsTestCase. Se utiliza para definir pruebas unitarias de Actions fuera del contenedor. • PrincastActionTestCase. Esta clase se utiliza para implementar pruebas unitarias de Actions Struts que se ejecuten en contenedor. 106
  • 114. Pruebas • PrincastJspTestCase. Se utiliza para implementar pruebas para páginas JSP o para etiquetas (Custom Tags). Este tipo de pruebas se ejecuta en contenedor. • PrincastFilterTestCase. Se utiliza para pruebas unitarias de filtros (Servlet Filter). Este tipo de pruebas se ejecuta en contenedor. • PrincastServletTestCase. Se utiliza para probar Servlets. Este tipo de pruebas se ejecuta en contenedor. • PrincastWebTestCase. Se utiliza para probar la navegación Web, envío y respuesta de formularios, etc. Este tipo de pruebas se ejecuta en contenedor.Convenciones a seguir A la hora de implementar las diferentes pruebas unitarias se recomienda que las clases sigan el convenio de nombrado masivamente seguido que establece que éstas deben llamarse <nombre_de_la_clase>Test.java. En cuanto a la signatura de los métodos que implementen los diferentes casos de pruebas, se suele emplear: public void test<nombre_caso_prueba> () {}; JUnit reconoce como casos de prueba todos aquellos métodos que sigan esta convención de nombrado.Ejecución de las pruebas unitarias Con el fin de facilitar la labor de ejecución de los diferentes tests, en el fichero build.xml de la aplicación en blanco que se proporciona con el openFWPA existe un target de Ant [10] que se encarga de esto. Su nombre es test.unit y se encarga de ejecutar todos los tests cuyo nombre de clase termina en Test y genera una serie de informes HTML en la carpeta testreports con los resultados de la ejecución de los mismos. Para la ejecución de los tests tan sólo es necesario el siguiente comando en línea de comandos: ant test.unit o bien, la forma análoga desde el entorno de desarrollado integrado que se use.Consultando los resultados de los tests Una vez ejecutadas las pruebas unitarias, se pueden consultar los resultados de las mismas en una serie de informes HTML que son guardados en la carpeta testreports. En la captura siguiente, se puede ver un ejemplo de informe del resultado de la ejecución de una batería de tests. En la imagen siguiente se puede ver el informe detallado de una de las pruebas pertenecientes a la batería de pruebas anterior. 107
  • 115. PruebasPruebas unitarias de objetos que acceden a bases dedatos. Para una aplicación web es muy recomendable realizar pruebas unitarias para todos los objetos de acceso a bases de datos (DAOs), de esta forma se garantiza que el back-end de la aplicación está correctamente implementado y se agiliza en muy buena medida el desarrollo de la capa web. Las pruebas unitarias de acceso a bases de datos, se realizan siempre fuera del contendor. Esto es lógico ya que en un buen diseño nunca un componente de acceso a BD tendrá dependencias respecto al contendor donde se ejecuta. Para realizar pruebas de clases que deben acceder a una BD, se utilizará la clase de test PrincastDatabaseTestCase. Si estos objetos son DAOs de una aplicación, siempre se puede utilizar la subclase PrincastDAOTestCase que ofrece más funcionalidad para la prueba de este tipo de objetos. Las pruebas unitarias de objetos que acceden a bases de datos, están basadas en el framework Spring. Spring puede simular el acceso a la base de datos utilizando transacciones serializables que son canceladas una vez termina la prueba. Por este motivo, es importante que las tablas involucradas en las pruebas que se implementan, dispongan de soporte para transacciones (por ejemplo, las tablas de tipo MyISAM, por defecto en MySQL, no soportan transacciones, sin embargo las tablas de Oracle o las tablas InnoDB de MySQL sí que las soportan). Atención Desde la versión 1.5 del openFWPA, las pruebas unitarias de acceso a base de datos están basadas en el framework Spring. El hecho de que este tipo de pruebas unitarias esté basado en Spring supone que: 1. Es necesario declarar los DataSources que se van a utilizar en un fichero de definición de beans de Spring. 2. No es necesario controlar las transacciones que se realicen durante el test. 3. La clase de prueba es, a su vez, un bean de Spring. La clase de pruebas realizará autowiring por tipo para todas sus propiedades setter. Es decir, para todos los setter que tenga la clase de pruebas busca, en los ficheros de definición de beans, un bean que encaje en el tipo de la propiedad y se lo asigna. Atención Es muy importante tener en cuenta que el autowiring de beans con la clase de test se realiza por tipo. Si más de un bean encaja en el tipo de la propiedad puede producirse un error. El DataSource a utilizar también se asigna a la clase de test utilizando autowiring, por lo tanto, únicamente se puede declarar un DataSource en los ficheros de definición de beans. Para implementar tests extendiendo PrincastDatabaseTestCase, se seguirán todas las normas y convenciones definidos para implementar casos de prueba estándar (Ver “Convenciones a seguir”). Además, se debe implementar el método: • getDataSourceFile(), que devuelve el path donde se encuentra el fichero de definción de beans en el que se declara el DataSource a utilizar. Por ejemplo: protected String getDataSourceFile() { return "classpath*:/es/princast/framework/facilities/dao/datasource-beans.xml"; 108
  • 116. Pruebas } La clase base PrincastDatabaseTestCase proporciona las siguientes utilidades: • logger. Como todos los tests, se dispone de un atributo, de nombre logger, que permite acceder al log de la clase de prueba. • getDataSource(). Este método permite obtener la fuente de datos (DataSource) que conecta con la base de datos. Este origen de datos se puede asignar, posteriormente, a los objetos que se vayan a probar. • jdbcTemplate. La clase base para pruebas en base de datos dispone de un atributo, de la clase org.springframework.jdbc.core.JdbcTemplate, que permite ejecutar, directamente consultas contra la base de datos. Para más información sobre cómo usar esta clase, consúltese el manual de referencia del framework Spring. • onSetUpInTransaction(). Extendiendo este método, se pueden realizar todo tipo de actualizaciones (inserciones, borrados, etc.) que sean necesarios para la posterior ejecución de la prueba. Todas las actualizaciones que se realicen estarán disponibles, únicamente durante la ejecución del test. No quedará rastro de estas operaciones en la base de datos. Este método sustituye al fichero DataSet XML de dbUnit. Si se utiliza este método, y se quiere utilziar también dbUnit, se debe hacer una llamada a super.onSetUpInTransaction(). • onSetUpBeforeTransaction(). Este método es análogo al anterior con al diferencia de que las actualizaciones realizadas en este método sí que son persistentes en la base de datos. Un ejemplo de uso de estos elementos es el que sigue: protected void onSetUpInTransaction() throws Exception { jdbcTemplate.execute("insert into foo values (1, foo foo)"); }Utilizando dbUnit con PrincastDatabaseTestCase. Es posible utilizar la librería dbUnit (http://www.dbUnit.org) con la clase PrincastDatabaseTestCase. Para ello, se deben realizar los siguientes pasos: 1. Escribir el fichero XML (DataSet XML) de datos para la prueba. El fichero DataSet XML está definido por la librería DBUnit y tiene el formato que se muestra en el siguiente ejemplo: <dataset> <PROPS id=1 value=pepe/> <PROPS id=2 value=ramon/> </dataset> Cada registro de la base de datos se escribirá en una etiqueta independiente, indicando, el valor de cada campo, como atributos de dicha etiqueta. En estos test siempre se puede suponer que la base de datos está cargada con los registros definidos en el fichero DataSet XML. Al finalizar los tests, la base de datos, siempre volverá a su estado inicial (antes de lanzar los test). 2. Extender el método getDataSetXML() para especificar el path de dicho fichero. Este path siempre debe ser relativo al classpath. 109
  • 117. Pruebas 3. Indicar la operación a realizarse por defecto para incializar los tests. Se debe extender el método getDatabaseSetupOperation(). Las operaciones soportadas son: DatabaseOperation.CLEAN_INSERT todo lo que haya en la tabla e inserta los datos del fichero Elimina XML obtenido con el metodo getDataSetXMLFile(). DatabaseOperation.INSERT Inserta los datos del fichero XML DatabaseOperation.UPDATE Actualiza los datos de la base de datos con los proporcionados por el fichero XML. DatabaseOperation.REFRESH Inserta los registros del fichero XML que no existan en la base de datos. Actualiza los que ya existan previamente. DatabaseOperation.NONE No se realiza ninguna operación con la base de datos. Estas operaciones solamente tienen efecto durante la ejecución del test. No se realizarán cambios permanentes en la base de datos. Por defecto, este método devuelve la operación: DatabaseOperation.CLEAN_INSERT. 4. Configurar las conexiones (DataSource) como cualquier otro tipo de prueba unitaria de base de datos.Pruebas unitarias de DAOs Por habitual, un caso especial de las pruebas unitarias que acceden a bases de datos, es el de las pruebas unitarias de DAOs. Para este tipo específico de pruebas, se proporciona una clase (que extiende de PrincastDataBaseTestCase) llamada: PrincastDAOTestCase. Atención La clase PrincastIBatisTestCase, existente en versiones del openFWPA anteriores a la 1.5, cuyo objetivo era probar DAOs implementados con iBatis, ha sido eliminada del openFWPA. El uso de la clase PrincastDAOTestCase permite probar cualquier tipo de DAOs, independientemente de la tecnología con que se implementen (por supuesto incluyendo iBatis). Todas las características propias de la superclase (PrincastDataBaseTestCase) se aplican a PrincastDAOTestCase, incluyendo, por supuesto, la gestión y obtención de DataSources. Los objetos DAO a probar se deben declarar en un fichero de definción de beans de Spring. La ubicación de este ficho se proprocionará extendiendo el método: getDaoFile(). Figura 9.2. Autowiring de DAOs y DataSources Para obtener instancias de los DAOs a probar, la clase de pruebas debe definir un método setter para cada uno de ellos. La clase base (PrincastDatabaseTestCase) se encargará de asignar a estas propiedades los DAOs, declarados en el fichero de definción de beans, cuyo tipo sea compatible (autowiring por tipo). Es importante tener en consideración que, si más de un bean es compatible con el tipo de una propiedad del test, se puede producir un error. /** 110
  • 118. Pruebas * Clase para probar DAOs (interface <code>PrincastDAO</code>). * * @see PrincastDAO * */public class PrincastDAOTest extends PrincastDAOTestCase { /** * El objeto a probar */ protected FooDAO2 dao; /** * Establece el DAO que se va a probar. * No hace falta llamar directmente a este método. Spring se encargará, al lanzar * de asignar el DAO, utilziando autowiring por tipo * * @param dao el DAO a probar */ public void setFooDAO2(FooDAO2 dao) { this.dao = dao; } protected String getDaoFile() { return "classpath*:/es/princast/framework/facilities/dao/mock/mock-dao-beans.xml" } protected String getDataSourceFile() { return "classpath*:/es/princast/framework/facilities/dao/datasource-beans.xml"; } protected void onSetUpInTransaction() throws Exception { jdbcTemplate.execute("insert into foo values (10, foo1)"); jdbcTemplate.execute("insert into foo values (20, foo2)"); } /** * Prueba una consulta. * * @throws Exception */ public void testSelect() throws Exception { assertNotNull(dao); List l = dao.getFooList("20"); assertNotNull(l); assertEquals(1, l.size()); Map m = (Map) l.get(0); assertEquals("foo2", m.get("nombre")); }} 111
  • 119. Pruebas Con solamente implementar el método setFooDAO2(), al ejecutar el test se asignará automáticamente cualquier bean de la clase FooDAO2. En el método getDaoFile() se debe definir la ubicación del fichero de definición de beans donde se declaran los DAOs. En este caso, se llama mock-dao-beans.xml, está ubicado en el classpath y su contenido es el siguiente: <beans> <bean id="foo2" autowire="byType" class="es.princast.framework.facilities.dao. </beans> Con el método getDataSourceFile() se define la ubicación del fichero de declaración de beans donde se especifica el DataSource a utilizar. En este ejemplo, el fichero se llama datasource- beans.xml, está ubicado en el classpath y su contenido es el que sigue: <beans> <!-- DataSource para Test --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManager <property name="driverClassName"><value>org.gjt.mm.mysql.Driver</value> <property name="url"><value>jdbc:mysql://localhost/foo</value></propert </bean> <!-- Es necesario también definir un TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.Da <property name="dataSource"><ref bean="dataSource"/></property> </bean> </beans> Todas las operaciones realizadas sobre la base de datos en este método será unicamente visibles a lo largo del test. Finalmente, se puede realizar el test normalmente utilizando los beans declarados en los ficheros correspondientes.Pruebas unitarias de Spring Beans Se ha incluido una clase base (PrincastContextTestCase) para la implementación de pruebas unitarias de objetos definidos como beans de Spring. Este tipo de pruebas unitarias son de utilidad siempre que se quieran probar objetos (o estructuras de objetos), creados, inicializados y construidos por Spring a través de uno o varios ficheros de definición de beans. Para definir el path (o paths) donde se realizará la búsqueda de los ficheros de definición de beans (estos paths siempre serán relativos al classpath, involucrados en la prueba, se debe sobreescribir el método:getDefinitionsPath(). Si este método no se sobreescribe, el path de búesqueda por defecto será: "/beans/**/*-beans.xml". En cualquiera de los métodos de la prueba unitaria, es posible acceder a cualquier bean (definido en cualquiera de los ficheros de configuración) utilizando el método: getBean(), especificando como parámetro el nombre del bean a buscar.Pruebas unitarias de objetos Configurables Un tipo especial de objetos son los que implementan el interface Configurable. Este tipo de objetos requiere que, para poder ser utilizados, esté activo todo el sistema de configuración del openFWPA. En las pruebas unitarias, el runtime del openFWPA no suele estar activo, por este motivo, es necesario, si se quieren hacer pruebas unitarias de objetos Configurables, tener preconfigurado el objeto FrameworkConfigurator. 112
  • 120. Pruebas Con el fun de facilitar la pre-configuración de objetos Configurables, de cara a su prueba unitaria, se ha incluido en el openFWPA una clase (ConfigurableObjectTestingHelper) que permite realizar esta preconfiguración de una forma fácil. Esta clase dispone de las siguientes operaciones: loadParameters Carga en el FrameworkConfigurator un conjunto de parámetros. Este método se utiliza para establecer un estado inicial (pre-configurar) el Sistema de Configuración. loadPropertiesFile Esta familia de métodos es similar a la anterior con la diferencia de que los parámetros se cargan de un fichero de properties. loadPlugin Pre-configura el Sistema de Configuración con un plug-in. configureObject Configura un objeto con un conjunto de parámetros. Estos métodos, pre-cargan el Sistema de Configuración y, posteriormente, configuran el objeto. La configuración unicamente se puede hacer indicando un conjunto de parámetros (como Properties), para realizar configuraciones más sofisticadas (varios contextos, plugins espcíficos, etc.) deberían utilizanrse los métodos anteriores. Properties props = new Properties(); props .put("HIT.COUNTER", "es.princast.framework.core.management.mcounters.simple.Sim props .put("EXCEPTION.COUNTER", "es.princast.framework.core.management.mcounters.simple.Sim props .put( "es.princast.ejemplo.Foo@EXCEPTION.COUNTER", "es.princast.framework.core.management.mcounters.historic.H ConfigurableObjectTestingHelper.configureObject(props, CounterFactory.getFacPruebas unitarias en contenedor En el openFWPA, las pruebas unitarias en contenedor se realizan utilizando la librería Cactus. Las pruebas unitarias con Cactus se ejecutan, parte en el cliente y parte en el servidor. Un breve resumen de los pasos necesarios para ejecutar pruebas unitarias con Cactus es: 1. Escribir el fichero cactus.properties. 2. En el fichero web.xml declarar y mapear el objeto [Servlet|Filter|Jsp]Redirector. Antes de escribir los tests, es importante recordar que los métodos que se ajustan al patrón testXXX() se ejecutan en el servidor (por lo tanto, todas las sentencias de log que en ellos se escriban aparecerán en el log del servidor y no en la consola) y los método que se ajustan a los patrones beforeXXX() y endXXX(), se ejecutan en el cliente (las sentencias de log aparecerán en la consola y no en el log del servidor).Pruebas de filtros Para facilitar las pruebas unitarias de filtros, la clase base PrincastFilterTestCase, ofrece los siguientes métodos de utilidad: 113
  • 121. Pruebas assertPassingThruFilter() Sirve para verificar que el flujo que pasa por una cadena de filtros no se ha interrumpido. No se ha realizado ni redirect, ni forward. assertForwardTo() Verifica que alguno de los filtros en la cadena ha realizado una redirección a través de un forward.Pruebas de validadores Se ha incluido una clase base para facilitar la implementación de pruebas de validadores de formularios. Este tipo de pruebas se ejecutan en contenedor. La clase base que debe ser extendida es PrincastValidatorTestCase. Esta clase pone a disposición de sus clases hijas los siguientes atributos: request La petición http que se está utilizando errors Un conjunto de ActionErros de prueba va Una instancia de la clase ValidatorAction para probar Estos tres atributos se pueden utilizar para realizar llamadas a los métodos de validación (sin necesidad de disponer de instancias con valores reales). Si se necesitan unos valores específicos para estos atributos, se pueden extender los métodos createValidatorAction() y createErrors(), de la clase base. public void testValidateTwoFieldsOk() throws Exception { FooForm form = new FooForm(); form.setDate1("21/11/2005"); form.setDate2("21/11/2005"); Field field = configureField(); assertTrue(FieldValidations.validateTwoFields(form, va, field, errors, request)); }Pruebas unitarias de informes Las pruebas unitarias para informes permiten generar y previsualizar reports (utilizando JasperReports y las clases del openFWPA), sin necesidad de que éstos se ejecuten en el contenedor. La clase base para este tipo de pruebas unitarias es PrincastReportTestCase. Define los siguientes métodos: getReportName() Este método abstracto debe ser extendido por las subclases para especificar el nombre del informe. El nombre del informe se utilizará para encontrar el fichero de definción del mismo (con el convenio de nombrado <nombre del informe>.xml) y para nombrar el fichero PDF resultante (con el convenio <nombre del informe>.pdf). getOutputDir() Las subclases pueden extender este método para especificar el directorio donde se dejará el fichero PDF generado. Por defecto, vale "" (cadena vacía) que dejará el informe en el directorio raíz del proyecto. 114
  • 122. Pruebas getInputDir() Las subclases deberían extender este método para especificar el directorio donde se encuentra el fichero XML que define el informe. Por defecto, este directorio será "reports", siendo el path relativo al directorio raíz del proyecto. Es posible especificar paths absolutos o relativos al classpath utilizando el prefijo "classpath://". generateReport() Gener el PDFProvider correspondiente al informe para ser utilizado en las pruebas. generatePDFReport() Genera el informe y lo exporta al fichero PDF. public class ProductosReportTest extends PrincastReportTestCase { protected String getReportName() { return "productosReport3"; } protected String getInputDir() { return "extras/sampleapp/src/reports"; } /** * Prueba la generación de un informe sencillo * @throws Exception */ public void testReportGen() throws Exception { //Obtener origen de datos List data = new LinkedList(); data.add(new ProductoVO(1, "fooName1", "fooDescr1", 1.0, "url1")); data.add(new ProductoVO(2, "fooName2", "fooDescr2", 2.0, "url2")); data.add(new ProductoVO(3, "fooName3", "fooDescr3", 3.0, "url3")); //Obtener parametros HashMap parameters = new HashMap(); parameters.put("P_Titulo", "Titulo Fooo"); parameters.put("P_AmpliacionTitulo", "Ampliacion Titulo Fooo"); generatePDFReport(data, parameters); } } El código superior se puede encontrar en la aplicación de ejemplo SampleApp.Más información sobre la implementación de pruebasunitarias Dado que los diferentes tipos de pruebas unitarias que se han presentado con anterioridad están basados en proyectos de terceros, la mejor forma de conseguir información detallada de los mismos es acudiendo a los sitios web de estos. Por ello, a continuación se presentan los enlaces a las guías de desarrollo de cada uno de los proyectos: 115
  • 123. Pruebas • Proyecto JUnit [http://junit.sourceforge.net/] • Proyecto StrutsTestCase [http://jtestcase.sourceforge.net/] • Proyecto DbUnit [http://dbunit.sourceforge.net/howto.html] • Cactus [http://jakarta.apache.org/cactus]Pruebas de Rendimiento Las pruebas de rendimiento de aplicaciones web, se deben realizar, de forma automatizada, utilizando herramientas inyectoras de carga. La herramienta recomendada para aplicaciones desarrolladas con el openFWPA es OpenSta[22].Modo de Prueba de Rendimiento La herramienta OpenSta no soporta la automatización de tests sobre el protocolo https. Es ncesario que toda la aplicación corra sobre http. Debido a que el protocolo utilizado (http/https) es ajeno a la programación de la aplicación (en realidad, su uso está gestionado por el framework), para realizar pruebas de rendimiento, basta con poner el parámetro de configuración PERFORMANCE.TEST.MODE al valor ON. Este parámetro se puede definir en cualquier plug-in de configuración que sirva parámetros al contexto de seguridad: SECURITY. 116

×