Академический Документы
Профессиональный Документы
Культура Документы
INDUSTRIALES Y DE TELECOMUNICACIN
UNIVERSIDAD DE CANTABRIA
INGENIERO DE TELECOMUNICACIN
Mayo - 2009
TTULO Sistema basado en J2EE para la visualizacin de informacin meteorolgica con tecnologas Web 2.0
AUTOR Max Tuni San Martn
DIRECTORES Daniel San Martn Segura
Antonio Cofio Gonzlez
TITULACIN INGENIERO DE TELECOMUNICACIN FECHA Mayo2009 TOMOIDEI
Agradecimientos
A Daniel San Martn y Antonio S. Cofino, mis directores de proyecto, por su continua predispo-
sicion para aclarar mis dudas y realizar sugerencias.
Al Grupo de Meteorologa y minera de Datos de Santander por la cesion de equipos informaticos
para la realizacion del proyecto.
A mi familia, por su apoyo a lo largo de la carrera.
1
English title
J2EE-based system for displaying weather information with Web 2.0 technologies.
Palabras clave
Meteorologa, J2EE, MVC, Struts 2, Spring, Acegi, Apache Tomcat, Google Maps, netCDF, Web
2.0, Personalizacion, Javascript, AJAX.
2
Indice general
1. Introduccion 7
1.1. Organizacion del documento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2. Conceptos previos 9
2.1. La Programacion de Sitios Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.1.1. Alternativas para la programacion desde el lado del servidor . . . . . . . . . 9
2.2. Programacion Web en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Eleccion del Entorno de Desarrollo . . . . . . . . . . . . . . . . . . . . . . . 10
Control de versiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3. Patrones de diseno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.1. Patron de Modelo Vista Controlador . . . . . . . . . . . . . . . . . . . . . . 12
2.3.2. Arquitectura de capas (Layers) . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4. Web 2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4.1. AJAX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4. Frameworks utilizados 19
4.1. Arquitectura de la Aplicacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.2. Framework para el desarrollo Web . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.2.1. Struts 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.2.2. MVC en Struts 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.2.3. Introduccion al uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
struts.xml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Archivos.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Archivos de Vista . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.2.4. Internacionalizacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.3. Framework para Inyeccion de Dependencias . . . . . . . . . . . . . . . . . . . . . . 24
4.3.1. Inyeccion de Objetos con Spring . . . . . . . . . . . . . . . . . . . . . . . . 25
Inyeccion automatica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3
Definicion de dependencias en applicationContext.xml . . . . . . . . . . . . 25
4.4. Framework para la persistencia de Objetos . . . . . . . . . . . . . . . . . . . . . . 26
4.4.1. Persistencia con iBATIS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.4.2. Configuracion de iBATIS . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.4.3. DAOs de nuestra aplicacion . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.4.4. iBATOR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.5. Framework de cacheado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.5.1. EHCache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.5.2. Creando una cache para almacenar los resultados de los metodos . . . . . . 28
4.6. Framework de seguridad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.6.1. Spring Security como Framework de Seguridad . . . . . . . . . . . . . . . . 29
4.7. Capa Visualizacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.7.1. JSP, Freemarker, Sitemesh . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Java Server Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Freemarker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Sitemesh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.7.2. Tag-Libs Utiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
AjaxTags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Display tag library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.7.3. Librera de creacion de graficos JFreeChart . . . . . . . . . . . . . . . . . . 34
5. Desarrollo 35
5.1. Definicion del Modelo de Datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.1.1. Analizando los requerimientos . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5.1.2. Base de datos de la Aplicacion . . . . . . . . . . . . . . . . . . . . . . . . . 36
5.1.3. Tablas de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5.2. Manejador de la Aplicacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.2.1. Generacion de los modelos y DAO de la Aplicacion . . . . . . . . . . . . . . 37
5.2.2. Manejador de la Aplicacion . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.3. Panel de Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.3.1. Configuracion de Struts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.3.2. El Panel de Control (CP) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Arquitectura y clases controladoras . . . . . . . . . . . . . . . . . . . . . . . 41
La parte de Vista (los JSP) . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Filtros de Usuario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Funcionalidades apoyadas en Struts . . . . . . . . . . . . . . . . . . . . . . 43
Utilidades Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Clases auxiliares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.4. Filtrado de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4
5.4.1. Filtrado de fechas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.4.2. Filtrado de estaciones por identificador y filtrado de variables . . . . . . . . 48
5.4.3. Filtrado de estaciones por localizacion . . . . . . . . . . . . . . . . . . . . . 48
5.5. Fuentes de datos de la aplicacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.5.1. Modelos genericos de estaciones, predicciones y observaciones . . . . . . . . 49
5.5.2. Manejador de Predicciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.5.3. Predicciones de Prometeo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Contenido de la Base de datos de Predicciones Prometeo . . . . . . . . . . 51
DAO de la base de datos de predicciones Prometeo . . . . . . . . . . . . . . 52
5.5.4. Observaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Dao generico de lectura de Observaciones . . . . . . . . . . . . . . . . . . . 53
Fuente de datos de Observaciones del INM . . . . . . . . . . . . . . . . . . 54
5.5.5. Lectura de datos desde ficheros NetCDF . . . . . . . . . . . . . . . . . . . . 54
DAO para la generacion de imagenes para mapas desde NetCDF . . . . . . 55
Ejemplo de fuente NetCDF . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.6. Registro de usuarios. Permisos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.6.1. Registro de usuarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Arquitectura del registro de usuarios . . . . . . . . . . . . . . . . . . . . . . 56
5.6.2. Control de Permisos. Acceso de usuarios . . . . . . . . . . . . . . . . . . . . 58
Spring Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Formulario de autenticacion . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
5.6.3. Manejador de Usuarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
5.7. Representacion de datos. Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Estandares para widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
5.7.1. Persistencia en la Base de Datos: la tabla Layouts . . . . . . . . . . . . . . 61
5.7.2. La pagina principal. HomeAction.java . . . . . . . . . . . . . . . . . . . . . 62
5.7.3. El manejador de Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5.7.4. JavaScript para controlar los Widgets . . . . . . . . . . . . . . . . . . . . . 64
5.7.5. Un Widget generico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Clase BaseWidget . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Extendiendo BaseWidget. Un Widget concreto . . . . . . . . . . . . . . . . 66
Plantilla de un Widget . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
5.8. Widget de prediccion por Localidades . . . . . . . . . . . . . . . . . . . . . . . . . 66
5.8.1. Arquitectura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
5.8.2. Entrada de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Actualizando los selectores mediante AjaxTags . . . . . . . . . . . . . . . . 68
5.8.3. Generacion de la tabla de resultados . . . . . . . . . . . . . . . . . . . . . . 70
5.8.4. Capa de Vista (visualizacion de los datos) . . . . . . . . . . . . . . . . . . . 71
5
5.9. Widget de Mapa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
5.9.1. Arquitectura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.9.2. Codigo del lado del cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.9.3. Creacion de los Marcadores . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Action de retrieveMarkers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Conversion a resultado JSON . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Generacion de los Markers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Vision de conjunto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Detalles al pulsar en el Marker . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.9.4. Generacion de la capa de marcadores . . . . . . . . . . . . . . . . . . . . . . 79
Transformacion de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Generando la imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Indicando a la API la forma de encontrar las imagenes . . . . . . . . . . . . 80
5.9.5. Mostrando capas salida de ficheros NetCDF . . . . . . . . . . . . . . . . . . 81
5.9.6. Generacion de las barras de colores . . . . . . . . . . . . . . . . . . . . . . . 83
5.10. Widget de lnea temporal de Observaciones . . . . . . . . . . . . . . . . . . . . . . 83
5.10.1. Arquitectura particular . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
5.10.2. Cargando las variables dinamicamente mediante JavaScript . . . . . . . . . 85
5.10.3. Visualizacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
5.11. Otros Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.11.1. Widget de Termometro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.11.2. Widget de Histograma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
6. Conclusiones 90
6.1. Lneas futuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
7. Apendices 94
7.1. Instalacion de aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
7.1.1. Instalacion de Eclipse con Web Tools Platform (WTP) . . . . . . . . . . . . 94
7.1.2. Plug-In para control de versiones Subversion . . . . . . . . . . . . . . . . . 94
7.1.3. Instalacion de Tomcat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
7.1.4. Instalacion de Maven y configuracion de las variables de entorno . . . . . . 95
7.1.5. Appfuse Light para integrar Struts2 y Spring . . . . . . . . . . . . . . . . . 96
7.1.6. Integracion de los demas frameworks en la aplicacion . . . . . . . . . . . . . 96
Sitemesh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Spring Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
6
Captulo 1
Introduccion
Aprovechando que se creara la nueva aplicacion desde cero, se hara un modelo de datos flexible,
que permita la sencilla introduccion de nuevos tipos de predicciones, en nuevos formatos o regiones,
con diferentes modos de visualizacion.
7
1.1. Organizacion del documento
Para facilitar la comprension del documento, se ha dividido en una serie de captulos.
En primer lugar se introduciran una serie de conceptos previos, con el objetivo de familiarizar
al lector con la programacion web en general, y como se pretende afrontar durante el proyecto.
En el captulo 3, se analizara el estado del arte para poder contextualizar el trabajo frente a
otros existentes.
Se mostraran las herramientas empleadas en el captulo 4, justificando por que fueron elegidas
as como sus aspectos mas fundamentales.
Por ultimo se detallara el proceso llevado para la ejecucion material del proyecto.
8
Captulo 2
Conceptos previos
9
PHP. Acronimo recursivo para PHP Hypertext Pre-processor, este lenguaje interpretado tiene
una larga tradicion. Fue creado originalmente por Rasmus Lerdorf en 1994, esta actualmente
en su quinta version la cual mejora el soporte de objetos. Es ampliamente utilizado, sobre
todo en aplicaciones web de mediana complejidad. El framework para desarrollo en PHP mas
empleado es el Zend Framework.
Ruby. Es un lenguaje de programacion interpretado, orientado a objetos, creado por el pro-
gramador japones Yukihiro Matsumoto, quien lo presento publicamente en 1995. Ha cobrado
mayor protagonismo recientemente con la aparicion del framework Ruby on Rails, que sigue
el paradigma de la arquitectura Modelo Vista Controlador (MVC).
Python. Python es un lenguaje de programacion interpretado creado por Guido van Rossum
en el ano 1991. Con el tiempo ha ido ganando popularidad ya que es un lenguaje sencillo de
aprender, con una completa librera estandar. Su framework mas famoso es Django.
Todos ellos tienen sus ventajas e inconvenientes. PHP, Python y Ruby tienen una sintaxis mas
relajada, y pueden ser la opcion ideal para proyectos pequenos. Nosotros emplearemos sin embargo
Java que nos ofrece una mayor robustez y tiene diversos Frameworks que nos seran de utilidad.
Servidor con soporte para Webs realizadas en Java Las aplicaciones que desarrollemos
tendran que situarse en un Contenedor de Servlets, como Tomcat 2 o Jetty 3 . All se situaran los
recursos y servlets que componen cada aplicacion, generalmente empaquetados en y comprimidos
en un archivo .war.
Como IDE (Entorno de Desarrollo) emplearemos Eclipse, el entorno por excelencia para la pro-
gramacion en Java, aunque tambien se emplee para otros lenguajes.
2 http://tomcat.apache.org/
3 http://jetty.mortbay.org/jetty
10
Eclipse es un entorno de desarrollo integrado de codigo abierto y multiplataforma, pensado para
desarrollar aplicaciones RIA (o Aplicaciones de Cliente Enriquecido). Fue desarrollado origina-
riamente por IBM, aunque hoy es software libre mantenido por la Fundacion Eclipse.
Control de versiones
En todo proyecto de cierta envergadura conviene utilizar un software de control de versiones. Uno
de los mas famosos es Subversion (SVN). Este software permite que diversos usuarios trabajen
simultaneamente en un proyecto, guardando constancia de todos los cambios que se producen en
los archivos. Aunque en este caso no habra varias personas, es interesante el control de versiones
para poder realizar pruebas y volver con facilidad a versiones anteriores, ademas de ser una forma
potente de guardar una copia de seguridad.
11
2.3.1. Patron de Modelo Vista Controlador
Una posible solucion a estos problemas es el patron de Modelo Vista Controlador. El patron MVC
separa los datos de una aplicacion, la interfaz de usuario, y la logica de control en tres componentes
distintos.
El patron fue descrito por primera vez en 1979 por Trygve Reenskaug, entonces trabajando en
Smalltalk en los laboratorios de investigacion de Xerox. Ha habido diversas derivaciones del patron,
una de las mas conocidas es el Model View Presenter (MVP) de desarrollado por Microsoft. Sin
embargo, el patron original sigue siendo ampliamente empleado, en especial en el ambito de la
programacion Web.
En este ambito la vista es la pagina HTML y el codigo que provee de datos dinamicos a la pagina;
el modelo es el sistema de gestion de base de datos y la logica de negocio; y el controlador es el
responsable de recibir los eventos de entrada desde la vista.
La logica de negocio dependera fuertemente del objetivo de la aplicacion concreta, pero el resto
del codigo sera posible reutilizarlo con mayor facilidad.
Las capas son independientes entre s. Realizar cambios en una capa no afectara a las demas.
Da soporte a la arquitectura MVC reforzando su separacion.
Mediante el empleo de interfaces se puede abstraer la tecnologa que subyace en una capa.
Esta separacion de funciones nos aportara una mayor flexibilidad en el momento que necesite-
mos cambiar alguna parte concreta de la aplicacion, por ejemplo el motor de bases de datos, sin
necesidad de retocar toda la aplicacion. Esto es posible gracias a tener una capa encargada del la
persistencia de Objetos.
12
Figura 2.2: Acceso a base de datos mediante capas de servicio y de acceso a datos (DAO).
que nunca, con apasionantes nuevas aplicaciones y con sitios web apareciendo con sorprendente
regularidad. Lo que es mas, las companas que haban sobrevivido al desastre parecan tener algu-
nas cosas en comun. La Web 2.0 podra haber surgido tras el giro que supuso el fin de la burbuja
punto-com.
El termino se ha popularizado con el paso del tiempo y hoy en da se refiere a una serie de
tecnologas. As, podemos entender como 2.0 aquellos sitios web centrados en el usuario, de forma
que estos generen contenido o puedan personalizarse el modo en el que el contenido se muestra.
El sitio no debe actuar como un jardn cerrado: la informacion debe poderse introducir y
extraer facilmente.
Los usuarios deberan controlar su propia informacion.
Basada exclusivamente en tecnologas estandarizadas de la Web, segun las directrices del
Consorcio World Wide Web 5 . Los sitios Web 2.0 con mas exito pueden ser utilizados ente-
ramente desde un navegador sin plug-ins adicionales.
Emplean tecnicas de aplicaciones ricas no intrusivas (como AJAX).
5 http://www.w3.org/
13
2.4.1. AJAX
AJAX (Asynchronous JavaScript And XML) es un conjunto de tecnologas que nos permiten
interactuar con el usuario dentro de la web de manera asncrona, sin necesidad de recargar la
pagina por completo. Su uso esta en auge ya que permite una mayor velocidad e interactividad,
acercando mas el potencial de las aplicaciones Web a lo que sera una aplicacion de escritorio.
Jesse J. Garret, fue el que desarrollo la idea original denominandola AJAX. El objeto XMLHtt-
pRequest fue originariamente inventado por Microsoft, usado desde Internet Explorer 5.0 como
un objeto ActiveX, siendo accesible mediante JavaScript. Mozilla en su version 1.0 implementa el
objeto compatible.
Esta transferencia asncrona de datos se realiza mediante JavaScript y el objeto XMLHttpRequest,
que realiza peticiones al servidor (generalmente tras recibir una accion del usuario, por ej., pulsar un
boton). Una vez recibida la respuesta del servidor, JavaScript nuevamente se encarga de modificar
una parte de la pagina ya cargada, sin necesidad de recargarla totalmente.
Figura 2.4: Diferencias entre las interacciones web clasicas y las realizadas mediante AJAX
Los datos devueltos por el servidor, que nos sirve para modificar la pagina al vuelo, vienen
normalmente en forma de XML 6 , JSON 7 , o bien un fragmento de HTML.
Formato para la transferencia de datos Para las paginas que que mas datos asncronos
necesiten, escogeremos JSON para empaquetarlos. Este lenguaje fue disenado para ser mas com-
pacto que el XML (tiene menos overhead), y permitir simplificar la interpretacion de datos desde
JavaScript.
6 http://www.w3.org/XML/
7 http://www.json.org/
14
Ejemplo de datos empaquetados en XML.
< menu id =" file " value =" File " >
< popup >
< menuitem value =" New " onclick =" CreateNewDoc () " / >
< menuitem value =" Open " onclick =" OpenDoc () " / >
< menuitem value =" Close " onclick =" CloseDoc () " / >
</ popup >
</ menu >
15
Captulo 3
Para poder contextualizar mejor el proyecto, analizaremos algunos servicios web de informacion
meteorologica caractersticos tanto nacionales como internacionales.
Meteo Galicia El servicio meteorologico gallego [16] dispone de una web donde, de forma similar
a la de AEMET, podemos acceder a predicciones y observaciones de la zona de Galicia. Lo mas
destacado de su servicio es la posibilidad de ver sobre el mapa las predicciones de diferentes modelos
atmosfericos y martimos.
Universidad de las Islas Baleares Desde la el grupo de meteorologa de la UIB han desa-
rrollado una aplicacion [14] que permite ver las predicciones para tres dominios diferentes, con
resoluciones de 22.5 Km 7.5 Km y 2.5 Km. Generan imagenes transparentes con los resultados y
los superponen en un mapa estatico.
Fox Weather News Forecast La cadena de television americana Fox [17] tiene un servicio
meteorologico basado en Google Maps. Han incorporado funcionalidades extra valiendose de la
tecnologa Adobe Flash, lo que hace necesario un plug-in del navegador para poder visualizar
correctamente el menu y las animaciones que incorporan.
16
Figura 3.1: Imagenes de prediccion para Santander y de masas de aire desde Satelite, ofrecidas por
AEMET.
apoyandose en Google Maps. A medida que se aumenta el zoom aparecen mas estaciones, pa-
ra no saturar de informacion a zooms alejados. Permite la visualizacion de diferentes tipos de
predicciones, aunque las preferencias se pierden al salir.
En conclusion, podemos observar como la mayora de los servicios analizados no ofrecen la infor-
macion a traves de un mismo interfaz. A traves de un menu se puede acceder a predicciones por
localidades, o bien acceder a las salidas de un modelo, generalmente sobre mapas estaticos para
zonas concretas. Cada web esta disenada para mostrar un tipo concreto de predicciones. Weather
underground por el contrario s integra varios tipos de predicciones en un mapa, pero no es capaz
de almacenar las preferencias del usuario, ni de ofrecer otro tipo de visualizaciones.
Nuestra aplicacion pretende ir un paso mas alla, permitiendo una visualizacion conjunta y perso-
nalizable. Podremos ofrecer informacion heterogenea gracias a una arquitectura basada en pequenas
mini-aplicaciones llamadas widgets, que pueden ser muy diferentes entre s.
17
Figura 3.2: Modelo de ondas martimas para la zona de Rias Baixas de Meteogalicia
Figura 3.3: Detalles del estado actual de una estacion por Weather Underground
18
Captulo 4
Frameworks utilizados
19
Figura 4.1: Arquitectura J2EE de la Aplicacion
20
4.2.1. Struts 2
Struts 2 surge en el ano 2007 como fruto de la union de los frameworks Struts y WebWork. El
nuevo framework, redisenado totalmente, aprovecha las ventajas de ambos y se rebautizo con el
nombre de Struts 2 [9].
Tiene una amplia comunidad de usuarios, e incorpora gran cantidad de mejoras sobre Struts,
como un diseno mas claro, el empleo de estandares inteligentes y soporte para AJAX.
En el proceso de transferencia interna de datos dentro del Framework participan una serie de
componentes esenciales: los interceptores, OGNL y el ValueStack. Ellos nos permiten mantener la
separacion entre los distintos elementos del MVC.
21
Los interceptores se invocan antes y despues de la accion, y realizan tareas que conviene separar
de la accion en s, por ser rutinarios y no estar relacionadas. Un ejemplo sera realizar un Log, o
calcular el tiempo consumido en crear la pagina mostrada. El ValueStack es una zona de memoria
donde se almacenan todos los datos de la aplicacion. OGNL es el lenguaje empleado por Struts
para acceder y manipular esos datos.
struts.xml
Archivos.java
Seran las acciones. El struts.xml que hemos visto anteriormente mapeara el codigo que tendra que
ejecutarse segun la peticion. Basicamente realizara las tareas de logica del negocio, preparando
todos los datos que seran mostrados al usuario, dejandolos en memoria (ValueStack) mediante
propiedades JavaBeans.
public class HelloWorld extends ActionSupport {
public static final String MESSAGE = " Hola Mundo " ;
public String execute () throws Exception {
setMessage ( MESSAGE ) ;
return SUCCESS ;
}
private String message ;
// getters y setters omitidos
}
Archivos de Vista
Los archivos de vista, generalmente .jsp, contienen la interfaz de usuario. Contienen elementos
estaticos y dinamicos y pueden acceder a la memoria en busca de la informacion preparada por el
action.
< html > < body >
< h2 > <s : property value = " message " / > </ h2 >
1 URL significa Uniform Resource Locator. Es una secuencia de caracteres, de acuerdo a un formato estandar,
que se usa para nombrar recursos, como documentos e imagenes en Internet, por su localizacion.
22
</ body > </ html >
Entrando mas en detalles, esta memoria donde se almacenan las variables es el ValueStack.
Desde los archivos JSP podemos acceder a ella empleando el lenguaje OGNL. Es un lenguaje
potente, pero una buena practica sera emplearlo solamente para buscar las variables, dejando
toda la logica en el fichero .java.
Esta estructura sencilla puede valernos para realizar nuestras aplicaciones. Sin embargo Struts es
un framework bastante potente y podra ahorrarnos bastante trabajo si sabemos emplearlo. Entre
otras cosas tiene facilidades para internacionalizar la aplicacion y realizar validacion de datos de
formularios.
4.2.4. Internacionalizacion
Struts proporciona ayudas para facilitar la internacionalizacion. Si definimos los mensajes de texto
en un fichero ResourceBundle con la nomenclatura estandar, Struts sera capaz de proporcionar los
mensajes en el idioma apropiado. Por defecto lo hace segun el localedefinido en el navegador del
usuario (el idioma del navegador que se emplea), aunque se podra cambiar esta conducta, haciendo
que, por ejemplo, el usuario pueda elegir en que idioma mostrar los mensajes. Si no esta disponible
el ResourceBundle en el idioma solicitado, se mostrara el idioma por defecto.
Estos ficheros estan formados por parejas clave-valor [8]. As por ejemplo un fichero Resource-
Bundle llamado prueba.properties contendra el idioma ingles (idioma por defecto).
admin . title = Prometeo Control Panel
admin . description = Control Panel for application s configuration
admin . goback = Go Back
Si el usuario tiene el navegador en castellano, Struts buscara un prueba es.properties que podra
ser de esta forma:
admin . title = Panel de Control de Prometeo
admin . description = Panel de control para la configuracion de la aplicacion
admin . goback = Ir atras
Definidos los recursos externos con los mensajes, luego en nuestros ficheros de la parte de vista (los
JSP que generaran la pagina HTML resultante que se enviara al usuario), usaremos unas etiquetas
especiales a las que, proporcionandoles la clave correspondiente, mostraran el texto en el idioma
apropiado.
Podremos usar dos opciones:
Para indicar a Struts donde esta el ResourceBundle apropiado, podemos utilizar la etiqueta de
Struts i18n o mediante la de JSTL: bundle segun la etiqueta que usemos para los mensajes.
Otra opcion es crear un archivo que se llame de forma estandar, y as se localizara automatica-
mente. Si nuestra clase es ActionClass, extiende a BaseClass e implementa un interfaz Interface,
se buscaran estos archivos en el orden indicado:
1. ActionClass.properties
23
2. BaseClass.properties
3. Interface.properties
4. Modelo ModelDriven
5. package.properties
6. Mensajes i18n
Si una key existe en mas de uno de los bundles, se usara la mas alta en la jerarqua (la de
ActionClass.properties si existe y as sucesivamente). Se da mas importancia a la jerarqua que a
la localizacion, de tal forma que se mostrara antes una propiedad en el nivel mas alto aunque no
este en el idioma solicitado.
24
4.3.1. Inyeccion de Objetos con Spring
Spring se configura generalmente mediante el archivo applicationContext.xml. Una de las formas
en las que Spring podra inyectar nuestros objetos es con simples metodos setter.
private PrometeoManager prometeoManager ;
public PrometeoManager getPrometeoManager () {
return prometeoManager ;
}
public void setPrometeoManager ( PrometeoManager prometeoManager ) {
this . prometeoManager = prometeoManager ;
}
Inyeccion automatica
La variable prometeoManager del codigo anterior es nuestro objeto de servicio. Spring buscara por
nuestra aplicacion objetos de servicio con la anotacion Java apropiada si le indicamos en applica-
tionContext.xml:
<! - - Scans for @Repository , @Service and @Component -->
< context : component - scan base - package =" prometeo "/ >
Sin mayor configuracion, cada vez que se necesite emplear prometeoManager y exista el metodo
setter apropiado, Spring se encargara de inyectar el objeto.
Otra forma de que Spring inyecte las dependencias es definir en el fichero applicationContext
directamente los llamados beans. Por ejemplo, creamos un bean para nuestro objeto de acceso
a datos prometeoDao:
< bean id = " prometeoDao " class = " prometeo . dao . preds . prometeo . PrometeoDAOImpl " >
< property name = " sqlMapClient " ref = " sqlMapClient . prometeo " / >
</ bean >
Las propiedades se refieren a dependencias que necesitan los objetos que van a ser inyectados.
En este caso, cuando se necesite una variable prometeoDao, si existe un metodo setPrometeoDao
Spring insertara el objeto apropiadamente inicializado con su sqlMapClient, que en este caso es
otro bean de referencia sqlMapClient.prometeo.
Un parametro importante de un bean es el scope. Si es definido como prototype se le dice que
cree nuevas instancias cada vez en vez de reutilizar los recursos. Esto es util, por ejemplo, para
dependencias de acciones de Struts. Otros scopes pueden crear instancias que duren lo que dura una
sesion HTTP (session), lo que dura una peticion (request), o por el contrario que duren siempre
que viva el container de Spring (singleton).
25
4.4. Framework para la persistencia de Objetos
La Capa de Persistencia de Objetos se situa entre la logica de Negocio y la capa de la Base de
Datos. Se encargan de mapear un modelo relacional (Base de Datos) a uno de objetos (POJOs 2 ).
Son capaces de gestionar conexiones, transacciones y caches, de forma transparente al usuario.
La capa de Persistencia se subdivide habitualmente en:
Es interesante su uso ya que hace mas sencilla el acceso a datos mediante archivos de configuracion
y ademas aisla nuestra aplicacion de la base de datos concreta que empleemos, pudiendo, por
ejemplo, mudar de Mysql a SQL Server sin necesidad de cambiar mas que el driver que empleara el
framework.
Los frameworks de persistencia mas conocidos en el mundo Java son Hibernate e iBATIS. Hiber-
nate abstrae al programador del lenguaje de consultas SQL, empleando su propio lenguaje. iBATIS,
sin embargo, emplea SQL estandar para definir las consultas. Dado que estamos familiarizados con
el uso de SQL emplearemos iBATIS. De esta forma nos ahorraremos aprender una nueva forma de
realizar consultas a la base de datos.
26
< property name = " configLocation " value = " classpath: / es / predictia / prometeo /
dao / ibatis / sql - map - config . xml " / >
</ bean >
sql-map-config.xml: Contiene un listado de los ficheros que albergaran los SQL Maps. Su lo-
calizacion es conocida gracias al bean definido en Spring como sqlMapClient.
En el apartado settings, debemos marcarle la opcion useStatementNamespaces como true pues
lo usara la herramienta de apoyo iBATOR, que veremos mas adelante. El empleo de namespaces
nos permite definir prefijos a los identificadores de las consultas de iBATIS. Nuestro fichero con
un enlace a SqlMap quedara as:
< sqlMapConfig >
< settings enhancementEnabled = " true " maxTransactions = " 5 " maxRequests = " 32 "
maxSessions = " 10 " u seS ta te men tN am esp ac es = " true " / >
< sqlMap resource = " prometeo / dao / ibatis / BBDDs_SqlMap . xml " / >
</ sqlMapConfig >
SQL Maps: Estos ficheros SQL Map contienen un listado de select, donde se le indica el tipo de
parametros, el tipo de resultado que se espera y un SQL para que ataque a la base de datos. El
framework se tendra que encargar de hacer la conversion del resultado devuelto hasta nuestro mo-
delo de datos. Estos parametros que le pasamos se pueden introducir en las querys como variables,
introduciendolas entre almoadillas.
Un ejemplo de select:
< select id = " getVista " parameterClass = " java . lang . String " resultClass = " org .
appfuse . model . Vista " >
select nombre , configuracion_ui
from vista where nombre =# nombre #;
</ select >
27
En esta clase podremos emplear los metodos de ayuda de iBATIS, como getSqlMapClientTempla-
te, que nos permite lanzar querys SQL del fichero de configuracion de iBATIS y devolverlos como
un objeto de modelo o lista de objetos del modelo segun el numero de resultados.
Definido el DAO y su implementacion podemos hacernos de una clase que maneje el modelo.
Siguiendo con el ejemplo haramos VistaManager, que se encargara de acceder al DAO con las
funciones definidas en VistaDao.
Este VistaManager sera al final el que se incluye en nuestras acciones de Struts. As podremos
acceder a los datos de forma elegante en nuestros ficheros de logica del negocio sin mas que emplear
las funciones que hereda el manejador.
4.4.4. iBATOR
Tal y como hemos visto, hemos de definir el modelo, DAO (interface) y DAOImpl (Implementacion
del interface) por cada tabla de la base de datos. iBATOR 3 es un generador de codigo que dispone
de un plug-in para Eclipse que nos ahorrara el trabajo de generar estas clases Java. Se le proporciona
los datos para que pueda efectuar la conexion con la base de datos, gracias a lo cual, en cualquier
momento podremos actualizar esas clases automaticamente si realizamos cambios en la base de
datos.
4.5.1. EHCache
EHCache es un framework de cacheado muy extendido que permite cacheado de proposito general.
Puede realizar almacenamiento tanto en disco como en memoria. Se distribuye bajo la licencia
Apache y es ampliamente desarrollado y soportado.
4.5.2. Creando una cache para almacenar los resultados de los metodos
EHCache nos permite cachear los resultados de metodos existentes, de forma transparente para
el resto de la aplicacion, apoyandonos en Spring. [10]
Para ello, crearemos un nuevo cache en el fichero de configuracion de EHCache, ehcache.xml.
< cache name = " cache . METHOD_CACHE "
m axElementsInMemory = " 300 "
eternal = " false "
timeToIdleSeconds = " 500 "
timeToLiveSeconds = " 500 "
overflowToDisk = " true "
/>
3 http://ibatis.apache.org/ibator.html
28
Crearemos ademas un metodo interceptor llamado MethodCacheInterceptor en el paquete pro-
meteo.interceptor que se encargara de calcular las claves de los objetos de la cache. Cada elemento
tendra una clave que sera calculada a partir del nombre del metodo y los valores de los parame-
tros (tras aplicarles un toString()). De esta forma, cuando el metodo sea llamado con los mismos
parametros que en alguna llamada anterior, la clave coincidira y se enviara el resultado procedente
de la cache.
Por ultimo configuraremos Spring para que integre el la cache de metodos:
< bean id = " cacheManager "
class = " org . springframework . cache . ehcache . E h C a c h e M a n a g e r F a c t o r y Be a n " >
< property name = " configLocation " >
< value > classpath:ehcache . xml </ value >
</ property >
</ bean >
< bean id = " methodCache " class = " org . springframework . cache . ehcache .
EhCacheFactoryBean " >
< property name = " cacheManager " >
< ref local = " cacheManager " / >
</ property >
< property name = " cacheName " >
< value > cache . METHOD_CACHE </ value >
</ property >
</ bean >
< bean id = " m et ho dCa ch eI nte rc ep tor " class = " prometeo . interceptor .
M e th od Cac he In te rce pt or " >
< property name = " cache " >
< ref local = " methodCache " / >
</ property >
</ bean >
Ya solo quedara indicarle (a traves de Spring tambien) que metodos de que clases querremos
cachear.
29
Figura 4.3: Integracion de la seguridad en la aplicacion
Para nuestra aplicacion usaremos una la version 1.0.7. Pese a estar disponible la rama 2.x, esta
aun no se considera igual de estable.
Este framework soporta una gran cantidad de estandares de autenticacion. En nuestro caso com-
probaremos las credenciales simplemente frente a los datos almacenados en nuestra base de datos.
La autorizacion se basara en roles de usuario. En el fichero de configuracion de Spring Security
se definiran que partes de la aplicacion son visibles para que roles.
JSP es una tecnologa Java que permite generar contenido dinamico para web. Fue desarrollado
por Sun Microsystems y se mantuvo cerrada hasta la version 1.2, pero actualmente es una tecnologa
abierta. Debido a su larga tradicion es ampliamente utilizado, y es el modo de visualizacion por
defecto en Struts 2.
Si no definimos un tipo de resultado, Struts asumira que empleara un fichero JSP:
< action name = " login " class = " prometeo . web . UserAction " >
< result name = " success " > acegilogin . jsp </ result >
</ action >
Los codigos JSP pueden contener desde contenido estatico (como HTML), directivas JSP en las
que podemos insertar codigo Java mediante scriptlets, as como tags personalizadas siempre que
empleemos las libreras apropiadas.
30
Tags Los tags son atajos para realizar tareas frecuentes de la parte de vista. Se realiza una
llamada al tag en vez de emplear un mayor numero de lneas de codigo. Por ejemplo, una tarea
frecuente es realizar un desplegable para seleccionar un elemento en un formulario. Habra que ir
recorriendo las diferentes opciones para generar el HTML necesario. Una tag podra hacer este
trabajo por nosotros sin mas que indicarle que variable contiene los elementos.
Desde el punto de vista del modelo MVC no es apropiado introducir logica del modelo de negocio
en la parte de visualizacion, por lo que procuraremos no insertar codigo Java en estos ficheros.
La parte de la vista basicamente debera mostrar la informacion generada por los Action, a la
cual podemos acceder a traves de las variables que tengan definidas sus getters/setters. Mediante
HTML estatico y las tag-libs como ayuda para facilitar la escritura de codigo resolveremos la parte
de la visualizacion.
Las tag-libs basicas que se emplearan son la Standard Tag Library (JSTL) [6] y la de Struts [2]:
< %@ taglib uri =" http :// java . sun . com / jsp / jstl / core " prefix =" c " %>
< %@ taglib uri =" http :// java . sun . com / jsp / jstl / fmt " prefix =" fmt " %>
< %@ taglib uri ="/ struts - tags " prefix =" s " %>
En el codigo JSP utilizaremos frecuentemente estas libreras que contienen las funcionalidades
habituales (control de flujo, mostrado de variables), as como algunas especializadas para mostrar
contenido HTML. En este ejemplo vemos un fragmento de codigo que comprueba si hay una variable
user y, en tal caso, se muestra un mensaje (internacionalizado con Struts) y un enlace para que
pueda desconectarse. Vemos como se emplean las diferentes tag-libs entre el HTML estatico:
< c:if test = " ${ not empty user } " >
< h1 > < s:text name = " usuarios . hi " > </ s:text > </ h1 >
<p >
< s:text name = " usuarios . loggedin " > </ s:text > < strong > < s:property value =
" user . uname " / > </ strong >.
<a class = " logoff " href = " < c:url value = " / j_acegi_logout " / > " > < s:text
name = " usuarios . logout " > </ s:text > </ a >
</ p >
</ c:if >
Freemarker
Freemarker [3] es un motor de plantillas que nacio como una alternativa libre a JSP. Con los anos
se ha ido asentando y hoy en da esta integrado en los Frameworks MVC de Struts y Spring. Al
contrario que JSP, no esta basado en Servlets por lo que puede generar otros tipos de contenido,
ademas del Web.
En Struts podemos usarlo facilmente indicandoselo en el tipo de resultado:
< action name = " comienzo " class = " prometeo . web . HomeAction " >
< result name = " success " type = " freemarker " >
/ template / widget / main . ftl
</ result >
</ action >
31
<# list librerias as libreria >
< script language = " javascript "
type = " text / javascript "
src = " ${ libreria } " >
</ script >
< /# list >
A diferencia de JSP, puede llamar a metodos con parametros, lo cual nos sera de utilidad:
<# list contenidos [ columna_index ] as widget >
< div id = " widget -${ widget . getID () } " >
${ widget . render ( widgetContext ) }
</ div >
< /# list >
Otra ventaja de Freemarker sobre JSP es que los ficheros de plantilla se localizan en la parte
privada de la aplicacion , no siendo accesibles directamente desde WebContents sino a traves del
motor de plantillas.
Sitemesh
Sitemesh 4 es una librera que nos permite definir decoradores. Gracias a los decoradores podremos
dar un aspecto homogeneo al sitio, ya que se le definen ah la parte superior e inferior que se
incluiran en todas las paginas renderizadas.
Funciona interceptando las peticiones HTML, e insertando las decoraciones definidas en su con-
figuracion, generalmente cabeceras y pie de paginas semi constantes, as como menus.
32
< decorator:body / >
Para insertar contenido en los containers de en una pagina los JSP tendran que definir la Tag-lib
de SiteMesh. As, anadiremos al fichero de taglibs.jsp (que incluiremos en todos los JSP) la lnea:
< %@ taglib uri =" http :// www . opensymphony . com / sitemesh / decorator " prefix ="
decorator " %>
AjaxTags
Esta librera pone a nuestra disposicion algunas tags que nos seran muy utiles para dinamizar
nuestras paginas. Necesitaremos incluir tambien un codigo JavaScript generico, as como la li-
brera JavaScript Prototype 7 , de la cual depende. Lo meteremos en todas las paginas a traves del
decorador de cabecera. Le anadiremos la lnea:
< script type = " text / javascript " src = " $ { ctx }/ ajaxtags / js / ajaxtags . js " >
</ script >
Seguidos estos pasos, ya podemos comenzar a usar sus tags. El que nos sera de mayor utilidad es
ajax:select.
ajax:select Esta tag nos generara el JavaScript que se encargara de rellenar selectores de formu-
lario cuando otros elementos del formulario cambien. Los datos que usara para rellenar el formulario
saldran de una peticion con unos parametros que configuraremos.
En la version actual de la Display tag library (1.1.1), se dispone de soporte avanzado para mostrar
tablas HTML. Se le proporciona una lista de objetos, y la librera se encargara del mostrado por
columnas, ordenamiento, paginacion, etc.
Para emplearlo, importamos la taglib en nuestro fichero taglibs.jsp, que sera incluido en todos los
JSP:
5 http://displaytag.sourceforge.net
6 http://ajaxtags.sourceforge.net
7 http://www.prototypejs.org/
33
< %@ taglib
uri =" http :// displaytag . sf . net "
prefix =" display "
%>
Vemos como la librera se encargara de generar la tabla a partir de la lista de objetos usuarios.
Se definen una serie de columnas con la propiedad que tendran que mostrar. En las columnas de
edicion y borrado se generan ademas enlaces que pasan por la URL el parametro uid. La columna
uname permite ser ordenable. Vemos que se definen una serie de atributos mediapara cada
columna. Segun si estamos viendo la tabla desde HTML o se ha realizado una exportacion a otro
formato, se puede variar las columnas que se muestran. As, por ejemplo, puede no interesarnos
que aparezcan ciertas columnas en una version imprimible en PDF.
La librera se ocupara ademas de las labores de paginacion si una tabla resulta demasiado larga.
El resultado sera el siguiente:
Se integra bien con AjaxTags, siendo las tablas generadas con esta librera incrustables en ele-
mentos HTML dinamicos.
34
Captulo 5
Desarrollo
Para el desarrollo, la primera tarea necesaria fue el diseno de un modelo de datos que permitiese
contener la informacion de configuracion que de soporte a la aplicacion. La forma de almacenar los
datos debe lograr una buena velocidad en las tareas frecuentes de acceso y modificacion.
Posteriormente, crearemos el manejador de la aplicacion. Constara de una serie de metodos que
nos facilitaran el acceso a la base de datos siempre que lo necesitemos.
Con la ayuda del manejador, podremos construir el Panel de Control de la aplicacion. Esta pensado
para que los administradores del sitio configuren los diversos parametros a traves de una interfaz
web. Aqu aprovecharemos por primera vez las ayudas de Struts como framework para la Web.
Llegado a este punto, comenzaremos a crear la logica necesaria para el acceso a los datos me-
teorologicos. Describiremos varios tipos de fuentes de datos, as como formas de filtrarlos segun el
usuario que acceda.
Se comentara el aspecto del registro de usuarios, as como la forma en la que se ha implementado
la seguridad.
Por ultimo, llegaremos a la parte donde la informacion se presenta al usuario. Comentaremos
el concepto de Widget, que sera un pequeno panel capaz de mostrar un tipo de informacion,
configurable para el usuario.
35
El diseno fsico parte del esquema logico y da como resultado una descripcion de la imple-
mentacion de la base de datos: las estructuras de almacenamiento.
Nombre1Nombre2 para establecer una relacion N:N entre las tablas Nombre1 y Nombre2.
Al definir las tablas principales se crearan primary keys, de tal forma que el motor de bases
de datos se encargue de la indizacion y resulten mas rapidas las consultas por la clave primaria,
as como las uniones entre varias tablas.
Dado que se realizaran frecuentes operaciones de modificacion, se definiran foreign keys que nos
aseguren la integridad referencial.
BBDDs: Aqu guardaremos los diferentes fuentes de datos que se pueden acceder as como
su localizacion.
Tipos: Cada base de datos puede ser de un tipo. Esta tabla se relacionara con los widgets.
Variables: Informacion relativa a las variables que se manejaran, como por ejemplo sus uni-
dades.
36
Usuarios: Esta tabla contendra los datos propios del registro de usuarios. Campos como
usuario, contrasena, correo electronico, etc.
Perfiles: Los perfiles nos permitiran agrupar a los usuarios segun el tipo de contenidos a los
que pueden acceder.
Roles: Almacenara los tipos de usuarios. Aqu se diferenciara a los administradores del sitio
y a los usuarios normales, as como si existen permisos de personalizacion o por el contrario
se es un usuario anonimo.
FiltrosIds: Permitira limitar las estaciones en concreto en las que el usuario podra acceder a
los datos.
FiltrosVariables: Permitira limitar las variables a las que el usuario puede acceder.
Widgets: Los widgets seran los componentes que un usuario puede utilizar. Pueden ir desde
un mapa a un histograma.
Vistas: Los widgets a los que puede acceder un usuario se agruparan mediante una vista.
Se asociaran a los perfiles.
Layouts: Esta tabla almacenara la configuracion personal de los Widgets de cada diferentes
usuario, as como la distribucion de los widgets en la pagina.
37
layouts widgets tipos
usuarios
iwid INT(10) wid INT(11) tiposwidgets tid INT(11)
uid INT(11)
uid INT(11) wname VARCHAR(255) tid INT(11) tname VARCHAR(255)
uname VARCHAR(255)
wid INT(10) wdesc VARCHAR(255) wid INT(11) tdesc VARCHAR(255)
password VARCHAR(255)
piwid INT(10) wldesc TEXT Indexes tldesc TEXT
mail VARCHAR(255)
lmeta TEXT wclass VARCHAR(255) Indexes
orden INT(11) wenabled TINYINT(1)
vistaswidgets
Indexes wmeta TEXT
Indexes vid INT(11)
usuariosfiltrosids wid INT(11)
uid INT(11) Indexes bbdds
fid INT(11) bid INT(11)
filtrosids
Indexes vistas bname VARCHAR(255)
fid INT(11) vistasperfiles
vid INT(11) bdesc VARCHAR(255)
usuariosperfiles ids LONGTEXT vid INT(11)
vname VARCHAR(255) bldesc TEXT
uid INT(11) bid INT(11) pid INT(11)
vdesc VARCHAR(255) bloc VARCHAR(255)
pid INT(11) fdesc VARCHAR(255) Indexes
vldesc TEXT tid INT(11)
Indexes Indexes
Indexes metadata LONGTEXT
perfiles
pid INT(11) perfilesbbdds
pname VARCHAR(255) pid INT(11)
usuariosfiltrosfechas
pdesc VARCHAR(255) bid INT(11)
uid INT(11)
pldesc TEXT Indexes
fid INT(11)
Indexes
38
Indexes filtrosfechas
fid INT(11)
startdate DATE
enddate DATE
bid INT(11)
Indexes
fdesc VARCHAR(255)
usuariosfiltrosespaciales Indexes
uid INT(11)
fid INT(11) filtrosespaciales
Indexes fid INT(11)
poly LONGTEXT
bid INT(11)
variables
filtrosvariables vid INT(11)
roles usuariosfiltrosvariables fid INT(11) vname VARCHAR(255)
variablesbbdds
rid INT(11) uid INT(11) vid INT(11) vdesc VARCHAR(255)
vid INT(11)
rname VARCHAR(255) fid INT(11) bid INT(11) vunit VARCHAR(255)
bid INT(11)
Indexes Indexes fdesc VARCHAR(255) vmeta VARCHAR(255) Indexes
vnamebbdd VARCHAR(255)
Indexes vformat VARCHAR(255)
Indexes
Indexes
Una vez generados los ficheros, hemos de decirle a iBATIS que use los SqlMaps que se llaman
desde la implementacion del DAO. Para ello le indicamos en sql-map-config donde se encuentran.
< sqlMap resource = " prometeo / dao / ibatis / bbdds_SqlMap . xml " / >
< sqlMap resource = " prometeo / dao / ibatis / perfiles_SqlMap . xml " / >
...
Posteriormente en la implementacion usaremos las funciones del DAO que necesitemos. Muchas
veces nos valdra con las funciones que nos ha generado automaticamente iBATOR, pero para las
consultas multitabla (para las tablas relacionadas) necesitaremos anadir algunas personalizaciones
a los DAO.
As, crearemos un DAO adicional para agrupar todos los metodos asociados a las tablas relacio-
nales. Lo llamaremos TablasRelacionablesDAO. Por ejemplo:
// relaciones tabla PerfilesBBDDs
List selectPerfilesByBid ( Integer bid ) ;
List selectBbbddsByPid ( Integer pid ) ;
...
39
5.3.1. Configuracion de Struts
Definiremos un fichero separado de configuracion de Struts para el panel de control, al que refe-
renciaremos desde el fichero struts.xml principal de tal forma:
< include file =" struts - admin . xml "/ >
El struts-admin.xml definira un paquete de acciones y sus resultados. Todas ellas estaran definidas
en el namespace /admin, de tal forma que para acceder sus acciones, el navegador tendra que indicar
que accedemos a la subcarpeta admin de la web.
< package name =" admin " namespace ="/ admin " extends =" default " >
Llamaremos a la accion adminBBDDs dentro del namespace /admin. La clase de Java que se
ejecutara, y los JSP encargados de mostrar los resultados al usuario se lo definimos dentro del
paquete admin, especificado en struts-admin.xml de esta forma:
< action name =" adminBBDDs " class =" prometeo . web . AdminBBDDsAction " >
< result name =" success " >/ admin / adminBBDDs . jsp </ result >
</ action >
Databases. Desde aqu el administrador podra insertar nuevas BBDD, editar o borrar las
existente. Ademas puede seleccionar el tipo de cada una, as como sus variables asociadas.
Database Types. Se definen los diferentes tipos de BBDD.
Variables. El lugar para definir el conjunto de variables que se manejaran y sus propiedades
fundamentales como, por ejemplo, las unidades.
Web Users. Desde aqu se podran modificar los roles y perfiles de los usuarios, as como
modificarlos o borrarlos. Desde el panel de configuracion de cada usuario se le podran aplicar
filtros.
Web Roles. Los diferentes roles posibles para los usuarios.
Web Profiles. Los perfiles disponibles.
Views. Para configurar las diferentes variables que se ofreceran a los usuarios. Ademas cada
vista se asociara a unos perfiles de usuarios y unos widgets.
Widgets. Los widgets que podran agregarse los usuarios se configuraran aqu. Tambien se
relacionara con los tipos de BBDD que podra usar.
Cada subpanel sera controlado por un Action de Java. Se les llamara siguiendo la notacion de
AdminSubpanelAction.java. A su vez este action debera cumplir varias funciones.
1 Durante el desarrollo de la aplicacion trabajaremos con un servidor web local escuchando en el puerto 8080
40
Arquitectura y clases controladoras
A modo de ejemplo, observemos la parte del fichero de Struts viendo en que momentos se llamara a
la accion AdminBBDDsAction.
< action name = " adminbbdds " class = " prometeo . web . admin . AdminBBDDsAction " >
< result name = " success " >/ admin / adminBBDDs . jsp </ result >
</ action >
< action name = " adminbbddsedit " class = " prometeo . web . admin . AdminBBDDsAction " >
< result name = " success " >/ admin / adminBBDDsEdit . jsp </ result >
</ action >
< action name = " adminbbddssave " class = " prometeo . web . admin . AdminBBDDsAction "
method = " save " >
< result name = " input " >/ admin / adminBBDDsEdit . jsp </ result >
< result name = " success " type = " redirect " >/ admin / adminbbdds . html </ result >
</ action >
< action name = " adminbbddsdelete " class = " prometeo . web . admin . AdminBBDDsAction "
method = " delete " >
< result name = " success " type = " redirect " >/ admin / adminbbdds . html </ result >
</ action >
Vemos que la mencionada accion sera invocada sin aludir a ningun metodo en particular cuando
deseamos listar las bases de datos existentes (adminbbdds) o cuando deseamos editar una en con-
creto (adminbbddsedit). En estos casos la Action Java debera dejar preparado en memoria los datos
necesarios: la lista de bases de datos y, si nos proporcionan un parametro bid, los datos de esa base
de datos en concreto. Esta tarea se llevara a cabo en el metodo prepare() de AdminBBDDsAction,
que es invocado siempre por un interceptor, antes de mostrar los datos.
public void prepare () {
this . setBbdds ( prometeoManager . getBbdds () ) ;
bbddsShow = new ArrayList < BbddsShow >() ;
for ( Iterator it = bbdds . iterator () ; it . hasNext () ;) {
Bbdds origen } ( Bbdds ) it . next () ;
BbddsShow destino = new BbddsShow () ;
BeanUtils . copyProperties ( destino , origen ) ;
// cogemos el nombre de tipo ( tname ) a partir del tid para mostrarlo en
la tabla
destino . setTname ( prometeoManager . getTipo ( origen . getTid () ) . getTname () ) ;
bbddsShow . add ( destino ) ;
}
this . setBbddsShow ( bbddsShow ) ;
this . setTipos ( prometeoManager . getTipos () ) ;
if ( bbdd == null ) {
// check for an add
if ( bid ! null ) bbdd } prometeoManager . getBbdd ( bid ) ;
else bbdd = new Bbdds () ;
}
Borrado de datos Cuando queremos borrar una base de datos, se llamara al metodo delete.
Este cogera el parametro bid y borrara la base de datos solicitada. Como salida iremos nuevamente
al listado de bases de datos.
public String delete () {
prometeoManager . deleteBbdd ( bbdd . getBid () ) ;
return " success " ;
41
}
Insercion y edicion de datos Al enviarse un formulario con datos, antes de intentar almace-
narlo en la base de datos se ejecutara previamente un validador de Struts. Si ha habido un error con
los datos volvemos a mostrar el formulario e informamos de los errores. Si no ha habido problemas
ejecutaremos el metodo que almacena los datos. Esto se lo indicamos en el admin.xml configurador
de Struts. Cuando ha habido un problema validando automaticamente se devuelve el valor input.
< result name = " input " >/ admin / adminBBDDsEdit . jsp </ result >
< result name = " success " type = " redirect " >/ admin / adminbbdds . html </ result >
Cuando enviemos un formulario con, por ejemplo, una base de datos de predicciones (ya estemos
editando una existente o creando una nueva) se encargara de procesarlo el metodo save. Este se
encargara de coger los datos del formulario adminBBDDsEdit.jsp e introducirlos en la BBDD.
public String save () {
if ( bbdd . getBid () ! null ) prometeoManager . updateBbdd ( bbdd . getBid () , bbdd )
;
else {
bbdd . setBid (0) ;
prometeoManager . addBbdd ( bbdd ) ;
}
return " success " ;
}
Por cada subpanel habra dos JSP que se encarguen de la vista. As para las base de datos
tendremos adminBBDDs.jsp y adminBBDDsEdit.jsp. El primero mostrara una lista de todas las
bases de datos disponibles, mediante una tabla que se apoya en la Display Tag Library. El JSP de
edicion mostrara un formulario con todos los campos modificables para una base de datos dada.
Filtros de Usuario
Los filtros seran unas restricciones que se podran aplicar a cada usuario. Nos permitiran limitar los
datos a los que tienen acceso, ya sea por criterios espaciales (ciertas zonas o estaciones), temporales,
o por variable.
Los filtros seran reutilizables, y podran ser aplicables a diversos usuarios. Poseen un campo
descriptivo (fdesc) que permitira que sean identificables desde el panel de control de cada usuario
y de esa forma se puedan elegir en un selector multiple aquellos filtros que se han de aplicar.
Para crear, editar y modificar los filtros se seguira una arquitectura similar a la de otras tablas
de la base de datos. As, por ejemplo, para los filtros temporales tendremos:
< action name = " adminfiltrosfechas " class = " prometeo . web . admin .
A d mi n F i lt r o s Fe c h a sA c t io n " >
< result name = " success " >/ admin / adminFiltros . jsp </ result >
</ action >
< action name = " a dm in fil tr os fec ha se dit " class = " prometeo . web . admin .
A d mi n F i lt r o s Fe c h a sA c t io n " >
< result name = " success " >/ admin / adminFiltrosFechas . jsp </ result >
42
</ action >
< action name = " a dm i n f il t r o sf e c h as d e l et e " class = " prometeo . web . admin .
A d mi n F i lt r o s Fe c h a sA c t io n " method = " delete " >
< result name = " success " >/ admin / adminFiltros . jsp </ result >
</ action >
< action name = " a dm in fil tr os fec ha ss ave " class = " prometeo . web . admin .
A d mi n F i lt r o s Fe c h a sA c t io n " method = " save " >
< result name = " input " >/ admin / adminFiltrosFechas . jsp </ result >
< result name = " success " >/ admin / adminFiltros . jsp </ result >
</ action >
Para listar los filtros temporales existentes se empleara la action adminfiltrosfechas. Se podran
crear nuevas entradas o editar existentes mediante adminfiltrosfechasedit. Si se le introduce un
parametro de fid (Id del filtro) se editara ese filtro en concreto, en caso contrario se mostrara el
formulario vaco para insertar uno nuevo. El action encargado de recoger el formulario sera admin-
filtrosfechassave, que si no hay errores ejecutara el metodo save, o en caso contrario mostrara de
nuevo el formulario para la correccion de dichos errores. Desde el listado de filtros se mostraran
enlaces al action adminfiltrosfechasdelete, que en este caso recibira siempre un parametro fid a
traves de la URL y llamara al metodo delete que se encargara de borrar dicho filtro.
A diferencia de otras partes del panel de control, se usara el mismo archivo .jsp (/admin/ad-
minFiltros.jsp) para mostrar el listado de filtros. Podremos reutilizarlo ya que se asemejan mucho
los datos que hay que mostrar. Todos los tipos de filtros tienen un campo fdesc que contiene la
descripcion del filtro y un fid, con su identificador. En el listado lo unico que hacemos es mostrar
las descripciones y enlaces para su edicion y borrado. Cambiara unicamente pues la action que se
encargara de los procesos de edicion y la que hace el borrado. Por ejemplo, para filtros temporales
la edicion es: adminfiltrosfechasedit y para los filtros espaciales: adminfiltrosespacialesedit. Para
personalizar cada JSP con las acciones correspondientes guardaremos su valor en una variable que
sera rellenada en cada caso por su action. As mostraremos en el lado de vista:
<p > <a href = "
< s:property value = " editaction " / >
. html " >
< fmt:message key = " admin . ufcreate " / > </ a >. </ p >
Que mostrara un enlace para crear un nuevo filtro. En el caso de los filtros temporales el action
de Java rellenara esa variable en su metodo prepare:
editaction = " ad mi nf ilt ro sf ech as ed it " ;
Validacion Todos los formularios de edicion se validan gracias al soporte de validacion de Struts.
As cada action Java tiene en su directorio un fichero XML que indica como ha de ser la validacion.
Para el AdminBBDDsAction que hemos visto como ejemplo, creamos el AdminBBDDsAction-
validation.xml. Struts tiene una serie de validaciones ya implementadas que nos vendran bien, por
ejemplo lo usamos para definir que campos son requeridos. Para los usuarios pedimos como dato el
correo electronico, mediante un validador de Struts comprobaremos que es una direccion correcta
de forma sencilla:
< field name = " usuario . mail " >
< field - validator type = " requiredstring " >
< param name = " trim " > true </ param >
43
< message key = " errors . mail_req " > Mail is required . </ message >
</ field - validator >
< field - validator type = " email " >
< message key = " errors . mail_invalid " > Mail supplied is wrong . </ message >
</ field - validator >
</ field >
Validador personalizado Como vemos, los validadores por defecto nos permiten hacer la mayor
parte de las funciones. Sin embargo, nos gustara incluir un validador que compruebe en la base
de datos que el nombre de usuario que se ha proporcionado no exista previamente para evitar
duplicados. Para ello, tendremos que crear un validador personalizado. Lo usaremos como un
validador mas en AdminUsuariosAction-validation.xml:
< validator type = " userexists " >
< message key = " errors . uname_exists " > Username already exists . </ message >
</ validator >
Ahora tendremos que definir la clase Java que realiza el proceso de validacion que hemos llamado
userexists. Eso lo hacemos desde el validators.xml que se encuentra en la carpeta raz de las clases.
La sintaxis es la siguiente:
< validator name = " userexists " class = " prometeo . validator . UserExistsValidator "
> </ validator >
La clase extendera a ValidatorSupport, y se espera que tenga un metodo llamado validate, al que
se le pasa como parametro un objeto con los datos del formulario. Si se encuentra un error se
llamara al metodo addFieldError.
Validador para fechas Mas adelante veremos que uno de los filtros trabaja con fechas y ha
de interpretar un string proporcionado por el usuario e intentar convertirlo en un objeto de fecha
Date. Para ello crearemos un validador personalizado que nos capture las excepciones de parseado
de tal forma que si no se puede interpretar una fecha anada un campo de error.
try {
date1 = format . parse ( fecha1 ) ;
}
catch ( ParseException e ) {
addFieldError ( fecha1 , object ) ;
e . printStackTrace () ;
}
Como ademas recibira un rango de fechas, analizara que la fecha final sea posterior a la inicial. Los
objetos Date tienen un metodo llamado compareTo que devuelve un valor numerico que depende
de cual es posterior o cero si son iguales. Este metodo nos permitira realizar la validacion del rango.
if ( date1 . compareTo ( date2 ) > 0) addActionError ( object ) ;
44
AJAX con Divs y eventos Dojo En el filtro de variables ofrecemos la posibilidad de seleccionar
que variables son accesibles al usuario. Sin embargo hay diferentes variables por cada base de datos.
Para insertar mas rapido este tipo de filtros creamos un desplegable (un selector HTML) con un
listado de las bases de datos. Cada vez que se elija una base de datos asncronamente (sin recargar
la pagina completamente) se solicitan las variables asociadas a esa base de datos, y el resultado se
muestran al usuario en un elemento div.
En la capa vista tenemos:
< script type =" text / javascript " >
function updateVariables ( bid ) {
document . filtroForm . bid . value } bid ;
dojo . event . topic . publish (" u pd a t e Va r i a bl e s Li s t T op i c ") ;
}
</ script >
<s : select key =" admin - filter . bid " name =" filtro . bid " list =" bbdds " listKey ="
bid " listValue =" bname " multiple =" false " value =" %{ filtro . bid }" id ="
selectorBds " onchange =" javascript : updateVariables ( this . value ) ;"/ >
<s : url action =" variablelistremote " namespace ="../ remote " id =" variablesUrl
" > </ s : url >
<s : div id =" variablesDiv " theme =" ajax " href =" %{ variablesUrl }" listenTopics ="
u p da t e V ar i a b le s L i st T o pi c " formId =" filtroForm " >
La capa div al tener el theme ajax de Struts es capaz de actualizarse asncronamente con los
contenidos que devuelve la URL suministrada. Se define un escuchador que sera llamado cada vez
que cambie el selector, y se le suministraran los datos del formulario filtroForm, entre los que ira el
identificador de la base de datos para que sea capaz de sacar las variables en funcion de dicho
identificador.
En los namespaces que venamos usando hasta ahora tenamos lo que se llaman decoradores, que
se encargaban de darnos consistencia al diseno, anadiendo en la parte de vista automaticamente
un contenido por encima y otro por debajo de todos nuestros JSP. El action que usaremos para
rellenar el div le hemos asociado a un namespace diferente (/remote) ya que, al generar contenido
que va a estar incrustado en mitad de una pagina, no nos interesa que venga con todo el conjunto
de elementos (cabeceras, scripts, elementos de diseno) de una pagina convencional.
En el archivo de configuracion de los decoradores (/WEB-INF/decorators.xml ) excluiremos los
action del namespace remote:
< excludes >
< pattern >/ remote /* </ pattern >
</ excludes >
Utilidades Java
Funciones relacionadas con las fechas Cuando definimos filtros para limitar el rango de
fechas en las que un usuario puede acceder a datos, nos vemos obligados a trabajar con fechas.
Internamente en la base de datos se definieron este tipo de campos como columnas de tipo date.
La capa de persistencia sera capaz de transformarnos tanto objetos de tipo java.util.Date [5] como
java.sql.Timestamp [5] a y desde la base de datos.
Nosotros usaremos internamente elementos Date que nos dan una buena precision y se les asocian
una serie de funciones que nos seran de utilidad. Una librera que interacciona con objetos Date
y que usaremos es la java.text.SimpleDateFormat [5]. Esta librera es capaz de traducir entre un
45
String con un cierto formato y un objeto de fecha. Nos vendra bien para que el usuario pueda
introducir comodamente las fechas.
Concretamente dejaremos al usuario la posibilidad de introducir el formato del string que va a
introducir mediante un campo del formulario. As, si solo le interesa introducir una precision de
anos (ano inicial y final de un cierto filtro de fechas), puede decir que el formato de las fechas
es: yyyy e introduciendo anos con cuatro digitos el sistema sera capaz de interpretar la cadena de
caracteres.
Se implementa en Java definiendo un formato y luego dandole la orden de que interprete el string
fecha1:
SimpleDateFormat format = new SimpleDateFormat ( dateFormat ) ;
Date date1 format . parse ( fecha1 ) ;
Para pasar de un objeto de fecha Date llamado date1 a un string sera similar:
SimpleDateFormat format = new SimpleDateFormat ( dateFormat ) ;
String fecha1 format . format ( date1 ) ;
Clases auxiliares
Hemos comentado previamente que disponemos de algunas tablas relacionales en la base de datos
que nos permitiran, por ejemplo, que un usuario disponga de varios roles. En el panel de control
de usuarios la forma elegida para configurar los roles de cada usuario es un elemento HTML de
select, que muestre los roles posibles y se puedan seleccionar varios.
Struts nos permite de forma sencilla generar el select, ya que con indicarle una lista y el valor que
ha de usar como key y el value nos es suficiente.
< s:select key = " admin - usuario . roles " name = " roles_selected " list = " roles "
listKey = " rid " listValue = " rname " multiple = " true " value = "
roles_prepopulated " > </ s:select >
46
< select name = " roles_selected " id = " a d m i n u s u a r i o s s a v e _ r o l e s _ s e l e c t e d "
multiple = " multiple " >
< option value = " 1 " selected = " selected " > Usuario </ option >
< option value = " 2 " > Administrador </ option >
</ select >
Sin embargo, nos interesa que aparezcan previamente seleccionados los elementos que estan previa-
mente configurados. Eso se lo pasamos mediante la lista roles prepopulated definida para el atributo
value del tag s:select. Sin embargo, esta lista ha de ser obligatoriamente una lista de strings con los
key que han de mostrarse como seleccionados, en vez de valernos una lista de objetos Java bean
como le pasamos en el atributo list.
Crearemos una clase auxiliar Prepopulater, con el metodo ModelListToPrepopulatedList. Sus ar-
gumentos seran una lista de objetos bean y el nombre de la propiedad que se extraera de cada uno,
para generar una lista de strings capaz de ser interpretada por el s:select en su atributo value.
47
5.4.2. Filtrado de estaciones por identificador y filtrado de variables
En ambos casos cada filtro tendra un identificador (en el caso de filtros de variables) o varios (en el
caso de filtros de estaciones por id) permitidos. Recorreremos todos los filtros creando una lista de
identificadores permitidos. Luego recorreremos los elementos de entrada leyendo su identificador.
Si esta en la lista de permitidos aparecera en la salida.
48
5.5. Fuentes de datos de la aplicacion
En la base de datos de la aplicacion hemos definido una tabla llamada bbdds. Esta tabla contiene
informacion sobre diferentes fuentes de datos de las cuales se alimentaran los Widgets para ofrecer
la informacion. Estos datos podran ser tanto predicciones como observaciones, desde diferentes
fuentes y formatos. Cada base de datos de predicciones en principio podra estar en un servidor
diferente.
Configuraremos las fuentes basadas en bases de datos en el fichero jdbc.properties. Seguiremos
la notacion prometeo-nombre base de datos. La base de datos de la aplicacion la llamaremos
prometeo-metadata, y usaremos por el momento una fuente de datos, llamada igualmente pro-
meteo.
prometeo - metadata . jdbc . driverClassName = com . mysql . jdbc . Driver
prometeo - metadata . jdbc . url = jdbc : mysql :// localhost :3306/ prometeo
prometeo - metadata . jdbc . username = root
prometeo - metadata . jdbc . password =*******
Ademas del acceso a la base de datos de predicciones de prometeo, nuestra aplicacion podra leer
otro tipo de datos. Concretamente le suministraremos observaciones de AEMET (antes INM), que
se leeran de un fichero en el formato de observaciones de MeteoLab 2 . Tambien se incorporara un
modulo para leer ficheros de datos cientficos encapsulados en el estandar netCDF, formato que
comentaremos mas adelante.
Tendremos, pues, en total tres fuentes de datos (una de ejemplo por cada tipo), cada una con su
correspondiente entrada en el panel de control.
49
fecha determinadas (Observation). Los DAO que accedan a los datos tendran que procesarlos para
devolverlos en la forma del modelo estandarizado.
Estos modelos contendran una serie de propiedades basicas. As el de estaciones guardara una
serie de datos geograficos as como sus identificadores de estacion.
private Integer id ;
private String name ;
private String sid ;
private String provincia ;
private Double longitud ;
private Double latitud ;
private Double altitud ;
50
< map >
< entry key = " prometeo . dao . preds . prometeo . PrometeoDAO " value - ref = "
prometeoDao " / >
</ map >
</ property >
</ bean >
Contiene una serie de tablas, pero las que utilizaremos seran las de estaciones y predicciones. Es-
taciones contiene datos relativos al lugar al que se le aplican las predicciones que estaran centradas
en la estacion que tomo los datos. Cada estacion tiene asociada un identificador entero, unas coor-
denadas (longitud, latitud, altura), as como un nombre y una provincia. La tabla de predicciones
esta pensada para dar toda la informacion para una variable concreta, as por cada fila tenemos
las predicciones para todas las estaciones dada una variable, una fecha y un step (alcance).
Concretamente los datos se almacenan en un BLOB (objeto binario), el cual contiene pares de
valores: entero para indicar el id de la estacion, y float para indicar el valor de la variable prevista.
Cada fuente de datos puede tener sus propios nombres de variable. Para asociarlos con las varia-
bles que tenemos definidas en el panel de control dentro del subpanel de variables, anadimos las
asignaciones pertinentes.
51
Figura 5.4: Configuracion de los nombres de variable de la fuente de datos de predicciones de
prometeo
Ofreceremos una serie de funciones a los widgets que usen esta base de datos. Definiremos un
interfaz PrometeoDao con todas ellas:
public interface PrometeoDAO {
public List getVariables () ;
public List getFechas () ;
public List getSteps () ;
public List < String > getRegions () ;
public List < Station > getStations () ;
public List < Station > getStationsFiltered ( Double minLat , Double maxLat ,
Double minLon , Double maxLon ) ;
public List < Station > ge tSta tions From Regio n ( String region ) ;
public Date getLastFecha () ;
public Station getStation ( Integer id ) ;
public Float g e t P r e d i c t i o n V a l u e F o r S t a t i o n ( String variable , Date fecha ,
Integer step , Integer stationId ) ;
public List < Prediction > getPredictionValues ( String variable , Date fecha ,
Integer step , List < Station > stations ) ;
}
Vemos que se ha definido un metodo para obtener todas las estaciones posibles (getStations), y
varios metodos para obtener un subconjunto de estaciones (getStationsFromRegion, getStations-
Filtered ). El numero de posibles estaciones es bastante alto y generalmente nos interesara acotar
la busqueda para aumentar el rendimiento.
Vemos que algunos de estos metodos necesitan varios parametros. Sin embargo las funciones de
apoyo de iBATIS estan pensadas para recibir un parametro. Crearemos as contenedores para
albergar los parametros, como el modelo PredictionVariable.
As por ejemplo getStationsFiltered se implementa:
public List getStationsFiltered ( Double minLat , Double maxLat , Double minLon
, Double maxLon ) {
52
EsquinasParameter parametro = new EsquinasParameter () ;
parametro . setMinLat ( minLat ) ;
parametro . setMaxLat ( maxLat ) ;
parametro . setMaxLon ( maxLon ) ;
parametro . setMinLon ( minLon ) ;
return g et Sq l Ma p Cl ie n tT e mp la t e () . queryForList ( " prometeoDAO .
getStationsFiltered " , parametro ) ;
}
Hace uso de la funcion de iBATIS denominada igualmente getStationsFiltered, que tiene este
codigo en el sqlMap correspondiente:
< select id = " getStationsFiltered " parameterClass = " prometeo . model .
EsquinasParameter " resultClass = " prometeo . model . Station " >
select stn_id as id , stn_idinm as sid , stn_name as name , stn_prv as
provincia , stn_lon as longitud , stn_lat as latitud , stn_alt as
altitud
from stn
WHERE
stn_lon BETWEEN # minLon # AND # maxLon # AND
stn_lat BETWEEN # minLat # AND # maxLat #
ORDER BY name ;
</ select >
5.5.4. Observaciones
Las fuentes de datos del tipo Observaciones tendran siempre el mismo Dao y leeran los datos
desde ficheros, en vez de una base de datos. Por ello cuando configuramos la fuente de datos,
almacenamos la ruta base de los ficheros en el campo bloc.
Al igual que ocurra con las predicciones, las observaciones manejan internamente unos nombres
de variables diferentes a los que podemos tener configurados en la aplicacion, por lo que habra que
hacer las asociaciones oportunas.
Definiremos una serie de funciones utiles para acceder a las observaciones, as como obtener las
variables y fechas para las que estan disponibles. Dado que este DAO valdra para diversas fuentes
de datos, le indicamos siempre como parametro la bid que le identifica.
public interface ObservationDao {
public List < VariablesNamed > getVariables ( Integer bid ) ;
public Date getFirstDate ( Integer bid ) ;
public Date getLastDate ( Integer bid ) ;
public List < Station > getStations ( Integer bid ) ;
public Station getStation ( Integer bid , String sid ) ;
public List < Observation > getObservations ( Integer bid , String sid ,
Integer vid , Date date1 , Date date2 ) throws Exception ;
}
Para implementar dicho DAO tendremos que tener en cuenta el formato de los ficheros del di-
rectorio. Habra siempre unos ficheros en formato de texto plano llamados Master.txt, que tiene
informacion sobre las estaciones, y Table.txt, con informacion sobre las variables. Las observacio-
nes en s se encuentran empaquetadas y comprimidas en formato gzip.
53
Fuente de datos de Observaciones del INM
A modo de ejemplo definiremos una fuente de datos de observaciones. Le indicamos la ruta, que
en este caso sera un fichero local, almacenado en el classpath de la aplicacion. Como cada fuente
de datos puede tener sus propios formatos de fechas, as como una frecuencia de datos registrados,
hemos de configurar dichos datos mediante el campo de metadata destinado a almacenar diversas
configuraciones. Por ultimo asociamos sus nombres de variables (del fichero Table.txt) con las de
la aplicacion (tabla Variables de nuestra base de datos).
54
DAO para la generacion de imagenes para mapas desde NetCDF
Definiremos un DAO para los widgets que puedan insertar imagenes en mapas. Tendra una funcion
para obtener las variables disponibles as como otra para generar una imagen con la salida del fichero
NetCDF con las dimensiones solicitadas para el Mapa.
public interface NetCDFDao {
public List < VariablesNamed > getVariables ( Integer bid , Date fecha ) ;
public ImageSerializable getImage ( Integer bid , String variable , Date
fecha , float [] esquinas , int WIDTH , int HEIGHT ) ;
}
Para realizar la implementacion emplearemos las clases de la librera de ncWMS. Por ejemplo
una funcion auxiliar que crearemos para obtener el nombre interno de la variable, dada la ruta del
archivo, hace empleo del metodo getLayers de su clase lectora de ficheros DefaultDataReader.
private String getVariable ( String location ) {
DefaultDataReader dr = new DefaultDataReader () ;
try {
return dr . getLayers ( location ) . get (0) . getId () ;
}
catch ( IOException e ) {
// ...
}
}
Crearemos una fuente de datos que utilice el DAO lector de ficheros NetCDF.
Le indicaremos una ruta donde podra encontrar los ficheros y el DAO se encargara de mostrar
las variables disponibles a los widgets que lo necesiten. Podra haber diversos ficheros, pero para
comprobar su funcionamiento introduciremos uno: MPI ECHAM5 20C3M r1 DM tas.2008.nc.
Dicho fichero contiene la temperatura en superficie global de cambio climatico del modelo ECHAM5
5
, en el ano 2008 con el escenario 20C3M 6 .
55
Figura 5.6: Configuracion de la fuente de datos de ficheros NetCDF
Cuando el usuario entra en registro.html, se dispara la accion registro, que se ejecuta el metodo
execute de UserAction y encarga de mostrarle el formulario registrar.jsp al usuario. Dicho formula-
rio contiene una imagen que sera el CAPTCHA, que apunta a imagen-captcha.html. Al solicitar el
7 Completely Automated Public Turing test to tell Computers and Humans Apart (Prueba de Turing publica y
56
navegador la imagen se creara sobre la marcha gracias a la logica insertada en GraficaCaptchaAc-
tion, que se encargara de generar el grafico e insertar en sesion la palabra codificada de forma que
se pueda validar si el usuario la ha introducido correctamente.
Dicha clase se encarga de leer el texto en sesion que introdujo el GraficaCaptchaAction y compa-
rarlo con el texto del campo captcha del formulario. Si no coinciden, se alerta del error.
public void validate ( Object object ) throws ValidationException {
Map session = ActionContext . getContext () . getSession () ;
String valorcorrecto } ( String ) session . get ( GraficaCaptchaAction .
CAPTCHA_KEY ) ;
String valorIntroducido = ( String ) getFieldValue ( fieldName , object ) ;
if (! valorIntroducido . equals ( valorcorrecto ) ) addFieldError ( fieldName ,
object ) ;
}
57
usuario . setPassword ( DigestUtils . md5Hex ( pass1 ) ) ;
Procesado el password, se almacena junto al nombre de usuario y demas datos en la base de datos
en la tabla de Usuarios. Por ultimo, se asigna a dicho usuario un Rol (publicRolName) y un Perfil
(publicProfileName) pensado para el publico en general, esto es un rol sin permisos de Supervisor
(no podra acceder a la zona de administracion), y un perfil que permita acceder a un conjunto
limitado de bases de datos. El dar al usuario nuevos perfiles o roles lo debera hacer un usuario con
rol de supervisor a traves del panel de control.
Spring Security
Se comento previamente que se haba escogido Spring Security como framework de seguridad.
Dado que ya tenemos la aplicacion configurada para que lo emplee, procederemos a configurar el
framework para que se adapte a nuestras necesidades. Editaremos as el applicationContext-acegi-
security.xml.
Le configuraremos donde ha de permitir dejar entrar al usuario en funcion de su rol. Le indicaremos
que para las acciones del namespace admin hara falta el rol de supervisor. El resto de namespaces
no estaran protegidos:
< property name = " o bj ec tDe fi ni tio nS ou rce " >
< value > <! [ CDATA [
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
P AT TE R N_ T YP E_ A PA C HE _A N T
/ admin /**= ROLE_SUPERVISOR
/**= I S _ A U T H E N T I C A T E D _ A N O N Y M O U S L Y
]] > </ value >
</ property >
Por defecto, Spring Security almacena las credenciales en un fichero de texto. Nosotros le configu-
raremos para que use la base de datos de usuarios de nuestra aplicacion. Habra que tener en cuenta
ademas que la password se almacena codificada en MD5. Cambiamos el bean de userDetailsService
dejandolo as:
< bean id = " userDetailsService " class = " org . acegisecurity . userdetails . jdbc .
JdbcDaoImpl " >
< property name = " dataSource " >
< ref bean = " dataSource " / >
</ property >
58
< property name = " a u t h o r i t i e s B y U s e r n a m e Q u e r y " value = "
SELECT u . uname , r . rname
FROM usuarios u , roles r , usuariosroles ur
WHERE
u . uname = ? AND
u . uid = ur . uid AND
ur . rid = r . rid
"/>
< property name = " usersByUsernameQuery " value = "
SELECT uname , password , true as enabled FROM usuarios WHERE uname = ?
"/>
</ bean >
< bean id = " passwordEncoder " class = " org . acegisecurity . providers . encoding .
Md5PasswordEncoder " / >
Hemos tenido que indicarle las querys que ha de emplear para acceder a la informacion ya que no
se crearon igual que en el estandar de Spring Security. Por ejemplo en el usersByUsernameQuery
se devuelve un tercer parametro que indica si el usuario esta activo. En nuestro caso todos nuestros
usuarios estan activos (si no se borraran), por lo que ese valor le damos siempre como true.
Formulario de autenticacion
Definiremos dos acciones de Struts, una que permita acceder al formulario de entrada, y otro en
caso de que falle la autenticacion.
< action name = " login " class = " prometeo . web . UserAction " >
< result name = " success " > acegilogin . jsp </ result >
</ action >
< action name = " login - error " >
< result name = " success " > accessDenied . jsp </ result >
</ action >
Hemos de configurar el Spring Security para que emplee estas URLs, a traves de su application-
Context, mediante el parametro del bean correspondiente (authenticationProcessingFilter ):
< property name = " a ut h e n ti c a t io n F a il u r e Ur l " value = " / login . html ? login_error =1 "
/>
Dicho formulario no tendra mas que los campos de usuario y contrasena, y el encargado de procesar
dichos datos es el propio framework. Se lo indicamos introduciendo el action en el formulario:
j acegi security check.
El el formulario ademas el usuario podra solicitar que se le recuerde en futuras visitas. Si se
activa, el framework generara una cookie 10 que sera leda en posteriores visitas para realizar
automaticamente el proceso de log-in.
a traves de su navegador, a peticion del servidor de la pagina. Esta informacion puede ser luego recuperada por el
servidor en posteriores visitas.
59
de nuestro modelo, donde conservamos por ejemplo el identificador de usuario, que sera de gran
utilidad para asociarlo con otras partes de la aplicacion. As, por ejemplo, los filtros de usuario
se pueden obtener a traves del identificador de usuario (uid) pero no directamente mediante el
nombre de usuario.
Definiremos un userManager con una funcion que nos devuelva el identificador de usuario, ali-
mentandose de la sesion que ha registrado el framework de seguridad.
public Integer getUserId () {
Authentication auth } Se curit yCon text Holde r . getContext () .
getAuthentication () ;
Object principal = auth . getPrincipal () ;
if ( principal . getClass () . equals ( User . class ) ) {
Usuarios user = prometeoManager . getUsuarioFromUname ((( User ) principal )
. getUsername () ) ;
return user . getUid () ;
}
return null ;
}
Los portales web basados en widgets estan creciendo en numero, lo cual ha creado la necesidad
de definir una especificacion estandar de widget de forma que este sea usable en cualquier portal
que implemente el estandar.
A finales de 2003 Sun publico la especificacion JSR-168 11 en la que se defina el concepto de portlet
(equivalente al concepto de Widget utilizado en esta memoria) como componentes modulares de
11 http://www.jcp.org/en/jsr/detail?id=168
60
interfaz de usuario gestionadas y visualizadas en un portal web. Dicha especificacion permite la
interoperabilidad entre portlets y portales que los contienen a traves de una API que estandariza la
comunicacion, presentacion, personalizacion, empaquetado, seguridad y despliegue de los mismos.
Para la implementacion de una aplicacion basada en portlets es necesario disponer de un con-
tenedor adecuado. Apache Pluto es la implementacion de referencia de JSR168 aunque tambien
existen otras implementaciones comerciales proporcionadas por IBM, Oracle y BEA Systems ente
otros. Existen ademas un gran numero de soluciones de portales open-source soportan JSR168,
tales como Apache Jetspeed-2 Enterprise Portal, JBoss Portal o Liferay Portal.
Una de las caractersticas de los portlets es que no proporcionan un mecanismo para asociar el
contenido independiente de un portlet directamente a una URL. Por lo tanto, la integracion con la
tecnologa AJAX no es directa y necesita del uso extensivo de JavaScript para evitar que la vista
entera del portal se recargue al modificar o introducir un portlet dificultando ademas la integracion
de los portlets en otros portales con diferentes formatos (no solo HTML). Este hecho nos llevo a
descartar el uso de esta tecnologa en la aplicacion y optar por una arquitectura propia mas flexible
no basada en el estandar JSR-168.
La nueva version de esta especificacion, JSR-268 (conocida como Portlet 2.0), fue publicada en
verano de 2008 y ofreca soluciones a las limitaciones anteriormente mencionadas. Sin embargo, al
comienzo de la elaboracion de este trabajo aun muy pocos frameworks soportaban esta especifica-
cion.
El W3C esta trabajando en otra especificacion llamada Widgets 1.0 12 que trata sobre formato
de empaquetado de los widgets, su configuracion y su modelo de procesamiento. Sin embargo, se
encuentra en estado borrador y por ello aun no es lo suficientemente estable como para trabajar
sobre ella.
Iwid : El identificador de la instancia de widget. Como pueden existir varios widgets del
mismo tipo es importante asociar a cada instancia un numero unico, y as las acciones sobre
las diversas instancias de un mismo tipo de widgets podran ser diferenciables. Sera ademas
la primary key de la tabla.
Piwid : Cada instancia podra tener una instancia padre, permitiendo as el acoplamiento de
widgets. Las que no tengan padre (piwid = 0) seran simples contenedores de instancias.
Uid : Identificador de usuario. Cada instancia se refiere a un usuario que se indicara a traves
este campo.
61
5.7.2. La pagina principal. HomeAction.java
HomeAction recogera la mayor parte de las tareas de las que se encarga el servidor. Se compren-
dera mejor mediante este definicion de la arquitectura que se relaciona con este fichero Action:
< action name = " comienzo " class = " prometeo . web . HomeAction " >
< result name = " success " type = " freemarker " >/ template / widget / main . ftl </
result >
</ action >
< action name = " home - change - template " class = " prometeo . web . HomeAction " method =
" changeTemplate " >
< result type = " chain " > comienzo </ result >
</ action >
< action name = " home - widget - remove " class = " prometeo . web . HomeAction " method = "
removeInstance " >
< result >/ simpleMessage . jsp </ result >
</ action >
< action name = " home - widget - add " class = " prometeo . web . HomeAction " method = "
addWidget " >
< result >/ newWidget . jsp </ result >
</ action >
< action name = " home - widget - update " class = " prometeo . web . HomeAction " method = "
updateWidget " >
< result >/ simpleMessage . jsp </ result >
</ action >
El primer action (comienzo) sera el encargado de mostrar la pagina principal. Sacara, en funcion
del usuario, los widgets y los dejara a disposicion de la plantilla freemarker. Para obtener los
widgets lo que se hara sera obtener una lista de las instancias de primer nivel (sin padre) del
usuario. Cada instancia padre el usuario lo percibira como una columna. Para cada columna se
buscaran las instancias hijas y se almacenaran en una variable.
La plantilla freemarker recibe una lista de columnas y una lista de widgets por cada columna.
Creara el layout HTML e ira llamando a cada widget para recibir su codigo HTML.
Se ofrecera desde la pagina principal enlaces que permitan al usuario controlar su pagina. Podra agre-
gar un nuevo widget a alguna columna o cambiar la disposicion de columnas. Si se agrega un nuevo
widget se llamara a home-widget-add, que generara el codigo del nuevo widget, y a una funcion
JavaScript que insertara asncronamente (AJAX) dicho codigo en la pagina.
Si se cambia la disposicion de columnas se llamara al action de home-change-template. Si el numero
de columnas aumenta se crearan las instancias nuevas, y si disminuye se destruiran, recolocando a
sus hijos entre las instancias que aun existan.
Cada widget tendra varias opciones: minimizar, configurar y cerrar. Minimizar y configurar sim-
plemente alteraran el estilo de la capa que muestra la zona de configuracion o el widget, para que
se muestre o no. Si se elige cerrar se llamara mediante AJAX a home-widget-remove que hara el
trabajo en el lado del servidor y a la funcion JavaScript que se encargue de eliminar el codigo
del lado del cliente e informar al usuario sobre si se realizo la operacion correctamente. El ultimo
action, home-widget-update, se llama cuando el usuario pulsa sobre el boton que hay debajo de
las opciones de configuracion del widget. Un usuario podra modificar su widget pero hasta que no
pulsa sobre el boton de guardar cambiosno se intentaran almacenar los cambios.
62
5.7.3. El manejador de Widgets
Para facilitar las labores del procesamiento del lado del servidor, y el acceso a la base de datos (y
la tabla Layouts), crearemos un manejador de widgets. Implementara el siguiente interfaz Java:
public interface WidgetManager {
public void createTopInstances ( Integer uid , Integer numberOfInstances ,
String template ) ;
public List < Layouts > getTopInstances ( Integer uid ) ;
public void removeTopInstance ( Integer uid , Integer iwid ) ;
public String getTemplate ( Integer uid ) ;
public void updateTemplate ( Integer uid , String newTemplate ) ;
public Layouts getInstance ( Integer iwid ) ;
public void removeInstance ( Integer iwid ) ;
public void updateInstance ( Integer iwid , Integer piwid , String
configuration ) ;
public List < Layouts > getChilds ( Integer iwid ) ;
public Layouts getChild ( Integer iwid , Integer orden ) ;
public void insertChild ( Integer iwid , Integer orden , Widgets widget ,
String configuration ) ;
public Layouts getLastChild ( Integer iwid ) ;
public void setOrder ( Integer iwid , Integer orden ) ;
}
El primer grupo de funciones se refiere a las instancias padre. Cuando un usuario elige una
template, esta vendra identificada por un nombre, que representa el numero y tipo de columnas
que contendra. As, el template s s s contendra tres columnas pequenas (short) y el l contendra una
columna grande (long). Cuando vamos a crear por primera vez las columnas del usuario, o vamos
a actualizar su template, analizaremos dicho string para saber cuantas instancias hemos de crear.
Dichas instancias padre almacenaran ademas en su campo de meta-datos el nombre de la template.
Este es el codigo de updateTemplate, que ilustra dicha forma de almacenar la informacion.
public void updateTemplate ( Integer uid , String newTemplate ) {
String oldTemplate = getTemplate ( uid ) ;
if (! oldTemplate . equals ( newTemplate ) ) {
int oldLength } new StringTokenizer ( oldTemplate , " _ " ) . countTokens () -
1;
int newLength = new StringTokenizer ( newTemplate , " _ " ) . countTokens () -
1;
if ( oldLength ! newLength ) {
int diferencia } Math . abs ( oldLength - newLength ) ;
if ( newLength > oldLength ) createTopInstances ( uid , diferencia ,
newTemplate ) ;
else {
List < Layouts > viejos = getTopInstances ( uid ) ;
for ( int cont =0; cont < diferencia ; cont ++) removeTopInstance ( uid ,
viejos . get ( viejos . size () -(1+ cont ) ) . getIwid () ) ;
}
}
// actualizo el metadata ...
}
}
El segundo grupo se encargan basicamente de las funciones CRUD sobre la tabla de Layouts,
permitiendo sacar la informacion sobre una instancia, borrarla o actualizar sus datos. El ultimo
63
grupo nos ayudara a trabajar con las instancias conociendo su instancia padre. Por ejemplo para
obtener todas las instancias de una columna usaramos la funcion:
public List < Layouts > getChilds ( Integer iwid ) {
LayoutsExample example } new LayoutsExample () ;
example . createCriteria () . andPiwidEqualTo ( iwid ) ;
example . setOrderByClause ( " orden " ) ;
return layoutsDao . s e l e ct B y E xa m p l eW i t hB L O B s ( example ) ;
}
ToggleLayer recibe el identificador de una capa y buscara su estilo. Si esta visible lo cambiara por
oculto y viceversa. Esto nos vendra bien para minimizar los widgets o ocultar su formulario de
configuracion, entre otras cosas.
AJAXInteraction realiza peticiones AJAX a la URL solicitada. Es capaz de realizar tanto peti-
ciones GET como POST (enviando los parametros de params, si los hay). Ademas recibe como
parametros dos funciones, doWhile y callback, que permitiran respectivamente definir que acciones
se han de realizar mientras no ha habido una respuesta y al terminar la peticion. Estas funciones
nos vendran bien por ejemplo para mostrar un indicador de progreso de la que realiza la peticion
y al terminar eliminar dicho indicador y cargar los resultados en la pagina.
Estas funciones de la librera las utilizaran una serie de funciones de control de widgets:
function inserta ( piwid , orden , wid ) ;
function saveConfig ( iwid ) ;
function widgetConfig ( iwid ) ;
function widgetMinimize ( iwid ) ;
function widgetClose ( iwid ) ;
64
cada widget guardaremos en la tabla Widgets de la base de datos la clase Java de cada tipo de
widget. Al llegarnos este dato, haremos un instanciamiento dinamico basado en nombre, mediante
un Class.forName.
Cada widget pues tendra su clase y su plantilla freemarker diferente. Sin embargo habra una
serie de elementos comunes de todos los widgets, por lo que crearemos una clase BaseWidget que
sera extendida por las clases ya concretas de cada widget.
Mostramos una funcion auxiliar que se encargara de instanciar dinamicamente el widget segun el
nombre de la clase y le insertara diversas propiedades comunes:
private BaseWidget makeWidget ( Layouts widgetLayout ) {
Widgets widget = prometeoManager . getWidget ( widgetLayout . getWid () ) ;
try {
Class widgetClass = Class . forName ( widget . getWclass () ) ;
try {
BaseWidget cadaWidget = ( BaseWidget ) widgetClass . newInstance () ;
cadaWidget . setTitle ( cadaWidget . getTitle () ) ;
cadaWidget . setID ( widgetLayout . getIwid () ) ;
cadaWidget . s e t C u s t o m F r e e m a r k e r M a n a g e r ( c us to m Fr e em ar k er M an ag e r ) ;
cadaWidget . setPredsManager ( predsManager ) ;
cadaWidget . setPrometeoManager ( prometeoManager ) ;
cadaWidget . setUserManager ( userManager ) ;
cadaWidget . setWidgetManager ( widgetManager ) ;
return cadaWidget ;
}
}
...
}
Clase BaseWidget
La clase BaseWidget definira una serie de propiedades comunes a todos los widgets, as como
metodos que necesitaran todos. Las propiedades seran el identificador, el ttulo, as como los ma-
nejadores que puedan llegar a necesitar. Los metodos seran los siguientes:
65
Extendiendo BaseWidget. Un Widget concreto
Plantilla de un Widget
Cada tipo de Widget necesitara una plantilla Freemarker diferente, pero usaremos una estructura
homogenea en la nomenclatura para encajar los comportamientos comunes en el JavaScript y los
estilos CSS que se puedan aplicar a diversas plantillas.
Se muestra a continuacion la estructura basica de la plantilla de un Widget. Tiene una serie de
elementos comunes como son un container, una zona de controles y otra de configuracion. Esas
capas tendran dos estilos, una que aplique los estilos genericos y otra los del tipo de widget concreto
(en el ejemplo, los del mapa).
< div id = " widget -${ iwid } - container " class = " widget - container widget - mapa -
container " >
<! -- Titulo del widget aqui -- >
<# include " widget - controls . ftl " >
< div id = " widget -${ iwid } - content " class = " widget - content widget - mapa -
content " >
< div id = " widget -${ iwid } - configure " class = " widget - configure widget -
mapa - configure " >
</ div >
<! -- Contenido del widget aqui -- >
</ div >
</ div >
Para que cada capa tenga un identificador unico y se pueda acceder desde el getElementById de
JavaScript, incluimos en su definicion el identificador de instancia, cargado desde loadPorperties
como la variable iwid.
66
5.8.1. Arquitectura
El widget, tendra una serie de caractersticas arquitectonicas que heredara de las clases genericas
de widgets. Tendra un formulario de configuracion donde el usuario podra elegir la localidad, fecha
y variable para las que desea ver las predicciones. En caso de haber mas de una fuente de datos de
predicciones, se podra seleccionar la deseada desde el formulario igualmente.
La tabla que se muestra como resultado de una configuracion se creara desde un Action separado
y se insertara dentro del HTML del widget ya formado al cambiar la configuracion mediante AJAX.
El Action encargado de generar la tabla se llama LocalidadesAction y el metodo que contiene la
logica se llama generateForecast. El resultado calculado se mostrara al usuario a traves del fichero
localidadesTabla.jsp.
< action name = " localidades - tabla " class = " prometeo . web . LocalidadesAction "
method = " generateForecast " >
< result name = " success " >/ localidadesTabla . jsp </ result >
</ action >
Las demas cargas asncronas que se realizan, consisten en obtener las distintas opciones disponibles
del formulario. Se cargan dinamicamente ya que dependeran de la fuente de datos de predicciones
que este seleccionada. Todas ellas se calculan desde una Action auxiliar que hemos creado que se
encarga de llamar al DAO que corresponda, aplicando los filtros que se le aplican al usuario.
El resultado es de tipo XMLResult de forma que se puedan alimentar de estos datos las tags de
la librera AjaxTags.
< action name = " retrievesteps " class = " prometeo . web . AuxAction " method = "
dameSteps " >
< result name = " success " type = " XMLresult " / >
</ action >
< action name = " retrieveregiones " class = " prometeo . web . AuxAction " method = "
dameRegiones " >
< result name = " success " type = " XMLresult " / >
</ action >
< action name = " retrieveestaciones " class = " prometeo . web . AuxAction " method = "
dameEstaciones " >
< result name = " success " type = " XMLresult " / >
</ action >
< action name = " retrievevariables " class = " prometeo . web . AuxAction " method = "
dameVariables " >
< result name = " success " type = " XMLresult " / >
</ action >
67
Figura 5.8: Configuracion del widget de prediccion por Localidades.
Cada vez que se modifique la fecha seleccionada del calendario le pediremos que nos modifique
un campo oculto del formulario para que podamos recibir la fecha de forma sencilla.
YAHOO . Calendario$ { iwid }. cal1 . render () ;
function actualizaFecha ( type , args , obj ) {
var dates = args [0];
var date = dates [0];
var year = date [0] , month = date [1] , day = date [2];
$ ( $ { iwid } _date ) . value = month + "/" + day + "/" + year ;
LoadTabla ( $ { iwid } ) ;
}
YAHOO . Calendario$ { iwid }. cal1 . selectEvent . subscribe ( actualizaFecha , YAHOO .
Calendario$ { iwid }. cal1 , true ) ;
Para escoger la estacion, se muestran dos selectores. Uno primero de las regiones disponibles y otro
con las localidades de una region en concreto. El selector de localidades se modifica dinamicamente
al cambiar la region escogida mediante una tag de la librera Ajaxtags.
Veamos en detalle como, por ejemplo, se podra actualizar el selector de id step cuando cambie el
elemento de id bid usaramos el siguiente tag. Los datos que se insertaran seran los que devuelva
la peticion a retrievesteps.html pasandole como parametro el bid antes mencionado.
68
< @ajax . select
baseUrl = " retrievesteps . html "
source = " bid "
target = " step "
parameters = " bid ={ bid } "
executeOnLoad = " true "
/>
Para interpretar el resultado devuelto por retrievesteps.html, lo mas comodo es usar el interprete
XML por defecto. Para ello tendremos que generar un fichero con unas caractersticas predefinidas,
del tipo:
<? xml version = " 1.0 " encoding = " UTF -8 " ? >
< ajax - response >
< response >
< item >
< name >1 </ name >
< value >1 </ value >
</ item >
< item >
< name >2 </ name >
< value >2 </ value >
</ item >
...
</ response >
</ ajax - response >
Se nos proporciona una clase auxiliar llamada AjaxXmlBuilder que facilita la tarea de construir
este tipo de ficheros XML. Para hacerlo transparente a nuestra aplicacion, construiremos un nuevo
tipo de resultado de Struts. Este tipo de resultado tomara de una variable la lista de objetos a
meter en el XML, los beans que compondran el name y el value del selector, y lo definiremos
implementando la clase Result de Struts.
public class XMLResult implements Result {
public void execute ( ActionInvocation invocation ) throws Exception {
ServletActionContext . getResponse () . setContentType ( " text / xml " ) ;
PrintWriter responseStream = ServletActionContext . getResponse () .
getWriter () ;
AjaxXmlBuilder builder = new AjaxXmlBuilder () ;
ValueStack valueStack = invocation . getStack () ;
List coleccion = ( List ) valueStack . findValue ( " coleccion " ) ;
String nombre = ( String ) valueStack . findValue ( " coleccionNombre " ) ;
String valor } ( String ) valueStack . findValue ( " coleccionValor " ) ;
for ( Iterator it = coleccion . iterator () ; it . hasNext () ;) {
Object cada } it . next () ;
if ( cada instanceof String ) builder . addItem (( String ) cada , true , (
String ) cada ) ;
else builder . addItem ( BeanUtils . getProperty ( cada , nombre ) , true ,
BeanUtils . getProperty ( cada , valor ) ) ;
}
responseStream . println ( builder . toString () ) ;
}
}
Ahora podremos definir esta clase como un nuevo tipo de resultado en el struts.xml:
69
< result - types >
< result - type name = " XMLresult " class = " prometeo . result . XMLResult " / >
</ result - types >
70
5.8.4. Capa de Vista (visualizacion de los datos)
A la capa de vista le llega el ArrayList que hemos creado previamente. Para mostrar los datos
utilizaremos la librera DisplayTag que nos permite generar tablas comodamente.
La primera columna contendra simplemente una descripcion de cada variable. La mostraremos
tal cual. Las demas columnas sin embargo contienen elementos VariableNumber. Para indicarle
como ha de mostrarlas utilizaremos un decorador [7] de columna.
As definimos en el JSP como se ha de generar la tabla:
< display : table name =" tabla " class =" table " requestURI =" prometeo - localidades -
show . html " >
<c : forEach var =" i " items =" $ { columnList }" varStatus =" contador " >
<c : choose >
<c : when test =" $ { contador . count == 1}" >
< display : column property =" $ { i }" sortable =" true "/ >
</ c : when >
<c : otherwise >
< display : column property =" $ { i }" sortable =" true " decorator ="
prometeo . web . displaytag . decorators . Numb erCo lumnD ecor ator "/ >
</ c : otherwise >
</ c : choose >
</ c : forEach >
</ display : table >
En el action de Java creamos una variable llamada columnList que contiene un ArrayList con
los nombres de todas las columnas (los keys de cada LinkedHashMap). Recorremos dicha variable
para definir todas las columnas. Para la primera columna no aplicamos decorador y para las demas
s.
Dicho decorador se encargara de mostrar el numero segun el NumberFormat [5], y anadiendo las
unidades.
VariableNumber valor } ( VariableNumber ) columnValue ;
String patron = valor . getFormat () . equals ( " " ) ? " ### ,##0.00 " : valor .
getFormat () ;
String unidad = valor . getUnit () . equals ( " " ) ? " " : " " + valor . getUnit () ;
NumberFormat nf = NumberFormat . getNumberInstance ( pageContext . getRequest () .
getLocale () ) ;
DecimalFormat df = ( DecimalFormat ) nf ;
df . applyPattern ( patron ) ;
return df . format ( valor . getValue () ) + unidad ;
71
Figura 5.9: Un ejemplo de vista del Widget de Localidades para tres das de step y la estacion de
Santander Centro.
5.9.1. Arquitectura
La API de Google Maps esta pensada para cargar los datos asncronamente en funcion de las ac-
ciones del usuario. As, cada vez que el usuario cambie el zoom o se mueva por el mapa, cargaremos
los datos sobre el dinamicamente.
Tendremos que ofrecer al usuario unos controles sobre la prediccion que se desea mostrar sobre
el mapa, as como de la fecha de las mismas. De la misma forma, cargaremos un listado de todas
las posibles variables (tanto de prediccion como de fichero NetCDF) y las filtraremos de forma que
solo sean visibles aquellas para las cuales el usuario tiene autorizacion.
Los selectores del formulario los rellenara AjaxTags a traves de las salidas que generan los metodos
de AuxAction:
< action name = " retrievesteps " class = " prometeo . web . AuxAction " method = "
dameSteps " >
< result name = " success " type = " XMLresult " / >
</ action >
< action name = " retrievevariables " class = " prometeo . web . AuxAction " method = "
dameVariables " >
< result name = " success " type = " XMLresult " / >
</ action >
< action name = " re tr i ev e va ri a bl e sm od e lo " class = " prometeo . web . AuxAction "
method = " dameVariablesModelo " >
< result name = " success " type = " XMLresult " / >
</ action >
Habra ademas una serie de acciones que generaran los contenidos listos para mostrar sobre el
mapa, que veremos mas adelante.
72
5.9.2. Codigo del lado del cliente
En la plantilla del widget mapa.ftl se cargaran varios scripts JavaScript:
< script language = " javascript " src = " http: // maps . google . com / maps ? file = api & v =2
" type = " text / javascript " > </ script >
< script language = " javascript " type = " text / javascript " src = " scripts /
m arkermanager_packed . js " > </ script >
El primero es API de mapas y el segundo es una librera de codigo abierto llamada MarkerManager
14
, que se encargara de mostrar los marcadores sobre el Mapa. Usaremos dicha librera en vez de
anadir cada marcador independientemente porque vamos a manejar un gran numero de estaciones
y, por tanto, de marcadores, y as se mejora la eficiencia al agregar o eliminar marcadores.
De hecho, para ciertos niveles muy bajos de zoom (cubriendo grandes areas geograficas) podramos
llegar a tener miles de marcadores, que aun con el empleo del MarkerManager resultara en tiem-
pos de carga muy pesados tanto para cliente como para servidor. Por ello optaremos por una
estrategia mixta, a niveles de zoom cercano, con pocos marcadores, los mostraremos mediante el
MarkerManager. Si nos alejamos lo suficiente lo que haremos sera crear una imagen transparente
con todos los marcadores y la superpondremos directamente sobre el mapa. Se perdera parte de
la interactividad (el navegador no sabra cuando el cursor pasa por encima de un marcador), pero
aligerara los tiempos de forma que merezca la pena.
Ademas de las citadas libreras, necesitaremos crear cierto codigo JavaScript que realice las tareas
que necesitemos. Definiremos una serie de funciones:
LoadMap. Recibe de parametros una latitud, una longitud y un nivel de zoom. La idea es que
cada usuario se mueva con sus mapas hasta la zona que le interesa y nosotros guardemos la
informacion de la posicion seleccionada para mostrarselo all la siguiente vez. Esta funcion se
encargara de centrar el mapa en la zona y zoom indicados, as como de anadir los controles
que queremos mostrar en el mapa (botones de acercar/alejar, etc). La funcion se llamara uni-
camente al principio de la carga del mapa para dejar todo preparado. Como ultimo comando
de la funcion se hara una llamada a UpdateMap que sera la realmente encargada de cargar
nuestra informacion en el mapa.
UpdateMap. Es una funcion sin parametros que se encarga de actualizar la informacion que se
muestra en el mapa. Es llamada al principio (mediante LoadMap), cuando el usuario se mueve
en el mapa o cuando se pide visualizar una variable/fecha diferentes mediante el formulario de
mapa.jsp. Esta funcion cada vez que es llamada, se encarga de recoger la informacion necesaria
(lmites del mapa y variable y fecha solicitada) y solicitar dicha informacion. Si estamos a
niveles de zoom cercanos, solicitara los markers y llamara a la funcion process json que se
encargara de anadirlos. Ademas se encargara de limpiar de la memoria aquellos markers que
no esten en la zona visible, para no guardar en la memoria del cliente datos que en principio
ya no son necesarios. A niveles alejados de zoom, solicitaremos una capa en vez de markers.
Esta capa es simplemente un conjunto de imagenes transparentes, que como hemos discutido
anteriormente, seran mas livianas de cargar que un conjunto de miles de markers.
process json. Esta funcion es llamada por UpdateMap, y recibe como parametro un conjunto
de markers que han de ser anadidos del mapa en formato JSON. Estos markers son generados
dinamicamente por la action retrieveMarkers de la que hablaremos mas adelante ya que es
codigo del lado del servidor. La funcion process json recogera los datos de los marcadores y
llamara a la funcion createMarker. Una vez creados todos los nuevos markers, actualizara la
informacion de marcadores que almacena el MarkerManager.
14 http://gmaps-utility-library.googlecode.com/svn/trunk/markermanager/
73
createMarker. Dadas la localizacion en el mapa, la imagen que se usara de icono, y la infor-
macion que se mostrara al pulsarse, createMarker se encargara de usar la API de GMaps para
crear un objeto de tipo Gmarker y anadirlo a un array donde guardamos todos los markers
visibles en el mapa.
Action de retrieveMarkers
Desde el lado del cliente nos van a pedir asncronamente (AJAX) los nuevos marcadores que hay
que mostrar en el mapa una vez cargado o cada vez que se mueva o cambie la variable. El action
de Java que se encargara de generar dicha informacion es el retrieveMarkers, que tiene la siguiente
estructura:
< action name = " retrievemarkers " class = " prometeo . web . MarkersAction " >
< result name = " success " type = " JSONresult " >
< param name = " classAlias " > Marker </ param >
< param name = " classAliasClass " > prometeo . model . Marker </ param >
</ result >
</ action >
Dicha clase de Java implementa el interfaz de Struts2 Result, generando un objeto de tipo Print-
Writer llamado responseStream, que contendra la respuesta a mostrar al usuario. Para realizar la
conversion de Lista de objetos a archivo JSON emplearemos la librera XStream 15 .
Mediante dicha librera es muy sencilla la transformacion, basicamente se realiza mediante:
XStream xstream = new XStream ( new J et ti s on M ap pe d Xm l Dr iv e r () ) ;
xstream . toXML ( jsonModel )
74
paquete donde se encuentra el modelo de Marker. Para ello Xstream nos ofrece la posibilidad de
crear alias (mediante el metodo xstream.alias). Para usarlo le pasamos a JSONresult los elementos
que queremos que sean sustituidos mediante los parametros classAlias y classAliasClass. As se
mostrara la lista de objetos como Marker en vez de prometeo.model.Marker.
El action de Struts que hemos definido como MarkersAction sera el encargado de consultar los
datos en la base de datos de predicciones y agruparlos en una lista de objetos de tipo Marker.
Dichos objetos contendran informacion que luego desde el lado del cliente se podra convertir con
facilidad en un icono de Google Maps Gmarker, como por ejemplo la latitud, longitud, la URL del
icono que le representa o el HTML que se mostrara una vez pulsado.
Recibe dos parametros: q y f. El primero es la query, y agrupara los parametros de variable y
fecha consultados. F se refiere a filter, y contendra las coordenadas de los lmites del mapa a la vista
del usuario. Gracias a f podremos sacar de la base de datos un conjunto reducido de estaciones
(las que son visibles al usuario) y de esta forma agilizar el proceso de calculo de marcadores. Con
la query podremos realizar la consulta de las predicciones para una variable y fecha concretas.
Ambos parametros son recogidos en el metodo prepare(), que se encarga de dejarnos listos dos
variables: variableData y prediccion. La primera contiene la informacion relativa a la variable
solicitada presente en la base de datos. Prediccion contiene la prediccion para estaciones en la
variable y fecha solicitadas.
Se podra dar la circunstancia de que el usuario mueva el mapa, pero no deje fuera de las region
visible todas las estaciones antes visibles. Para no volver a mandar las estaciones que el usuario
ya tiene presentes en el mapa (no han salido fuera de los lmites), desde el lado del servidor lo
que haremos sera guardar en sesion todas las variables enviadas. Con cada peticion, se descartan
aquellas que han quedado fuera de la zona visible. Antes de enviar un nuevo Marker, se comprueba
que no este presente en la sesion.
De esta forma, a costa de un mayor gasto en memoria en el lado del servidor (ha de recordar que
Markers tiene en pantalla cada usuario), se transfiere solo la informacion realmente necesaria (los
markers nuevos), y en casos de pequenos movimientos en el mapa, sera mas rapido para el cliente.
Generacion de iconos de los Markers En vez de usar la visualizacion estandar de los GMar-
kers de Google Maps nos interesara usar una personalizada.
Crearemos imagenes al vuelo con forma circular, con un cierto texto y un cierto color que indi-
cara el valor de la prediccion.
Colores segun el valor de la Variable Habra dos posibles formas para configurar el color
en una cierta variable. Cada una podra tener definido entre sus meta-datos un valor maximo
y mnimo, as como los colores que se aplicaran a los extremos maximos y mnimos. La otra
posibilidad sera definir lista de umbrales y una lista de colores. Sea cual fuere la forma en la que
se ha configurado la variable, cada valor se asociara a un color.
75
Para conseguir esta asociacion usaremos dos funciones auxiliares que crearemos dentro de la clase
Coloreador el paquete en prometeo.util.
public static Color ColoreaContinuo ( float valorMin , float valorMax , float
value , Color colorMin , Color colorMax ) ;
public static Color ColoreaDiscreto ( float value , List < Float > umbrales , List
< Color > colores ) ;
El metodo para colorear de forma contnua permitira, dados los extremos, obtener un color en
cualquier punto del rango proporcional a la distancia de los extremos de color. El metodo de
coloreado discreto, observa la lista de umbrales y si el valor es menor a alguno de ellos aplica el
color de la lista correspondiente. En caso de no haber ningun umbral menor se aplica el ultimo
color de la lista.
Creacion de la imagen Los parametros del color y texto se pasaran por URL, y de esta forma
dos imagenes que muestren el mismo texto y color tendran la misma direccion y por tanto podran
ser cacheadas por el navegador. Para poder ofrecer un resultado que no sea textual (en nuestro
caso es una tipo imagen, pero podra valernos tambien para otros ficheros) en Struts hemos de usar
un tipo de resultado llamado stream [2].
La arquitectura queda:
< action name = " marker - mapa -* -* " class = " prometeo . web . Gr afica Loca tionA ctio n " >
< param name = " texto " > {1} </ param >
< param name = " colorText " > {2} </ param >
< result name = " success " type = " stream " >
< param name = " contentType " > image / png </ param >
< param name = " inputName " > imageStream </ param >
< param name = " bufferSize " > 1024 </ param >
</ result >
</ action >
Java ofrece una librera de dibujo bastante potente llamada AWT [5]. La emplearemos para
generar dinamicamente las imagenes segun el color y texto que se necesiten mostrar.
Para hacerlo con la misma nomenclatura de HTML le pasaremos los colores como un unico numero
en formato hexadecimal donde los dos primeros bytes corresponden al rojo, los siguientes al verde
y los ultimos al azul. Luego emplearemos el constructor correspondiente de la clase Color del Java
AWT [5].
El color se pasara en forma de String de tal forma que sea interpretable por el metodo decode de
la clase Long. Hemos de usar Long en vez de Integer debido a que emplearemos los 32 bits posibles
de un Integer pero sin considerar signo y en Java no hay Unsigned Integer.
De esta manera podremos crear el color a partir del parametro con la siguiente instruccion:
Color color = new Color ( Long . decode ( colorText ) . intValue () ) ;
Creamos una imagen de tipo BufferedImage e insertamos nuestro circulo y texto mediante la clase
abstracta Graphics2D.
BufferedImage image = new BufferedImage ( anchura , altura , BufferedImage .
TYPE_INT_ARGB ) ;
Graphics2D g = ( Graphics2D ) image . getGraphics () ;
76
Una vez rellenado el circulo , su borde y el texto, hemos de convertir la imagen a un InputStream
[5] (necesario para el tipo de resultado stream de Struts). Para ello nos valemos del metodo write
de ImageIO. Dicho metodo nos rellena un ByteArray, que puede construirnos un ByteArrayInputS-
tream [5] que ya es un tipo InputStream.
B y t e Arr ayOut putS tream output = new Byt eArr ayOut putS tream () ;
ImageIO . write ( image , " png " , output ) ;
byte [] bytes = output . toByteArray () ;
output . close () ;
this . imageStream = new ByteArrayInputStream ( bytes ) ;
Vision de conjunto
El mapa, en caso de estar en un nivel de zoom cercano, solicita informacion al servidor sobre las
estaciones que hay entre los lmites del mapa. El servidor le enva un archivo JSON con los datos
suficientes para que la API de Google Maps pueda crear los markers, definiendose cada uno con
una imagen propia. Al insertarse los markers, se intenta resolver la imagen, que se generara en el
servidor sobre la marcha con los valores de color y texto apropiados.
Figura 5.12: Vista del Widget de Mapa mostrando marcadores en un zoom cercano al suelo.
77
Detalles al pulsar en el Marker
Hemos comentado previamente que, al generar los Markers, parte de la informacion que se le
suministraba a la API era un HTML que sera mostrado al pulsar sobre el icono del marcador. En
nuestro queremos mostrar una grafica con la evolucion en unos pocos das de la variable seleccionada
para la estacion pulsada.
En el codigo HTML le indicaremos que muestre una imagen que sera generada sobre la marcha.
Los parametros de la grafica se le indicaran a traves de la propia URL, siendo por ejemplo para la
grafica de precipitacion esperada el 7 de Octubre de 2008, para la estacion 918:
evolucion -6 -918 - precip_75 -20081007. html
Configuraremos Struts para que pase esos parametros de la URL al action, as como otros que le
indicaremos directamente en la configuracion como los das a mostrar antes y despues del solicitado.
Dicho action sacara la informacion de la variable para los das solicitados y generara una grafica
apoyandose en JFreeChart.
< action name = " evolucion -* -* -* -* " class = " prometeo . web . G ra fic aE vo luc io nA ct i o n
">
< param name = " bid " > {1} </ param >
< param name = " estacion " > {2} </ param >
< param name = " variable " > {3} </ param >
< param name = " fecha " > {4} </ param >
< param name = " stepAntes " >2 </ param >
< param name = " stepDespues " >3 </ param >
< result name = " success " type = " stream " >
< param name = " contentType " > image / png </ param >
< param name = " inputName " > imageStream </ param >
< param name = " bufferSize " > 1024 </ param >
</ result >
< result name = " error " type = " redirect " > error - graph . html </ result >
</ action >
Figura 5.13: Detalles en la precipitacion esperada para la estacion del Aeropuerto de Parayas.
78
5.9.4. Generacion de la capa de marcadores
Como se ha comentado, para zooms alejados habra numerosas estaciones que mostrar. Crear los
marcadores uno a uno sera pesado para el cliente, consumiendole mucha memoria. Optaremos por
crear imagenes transparentes que superpondremos en el mapa.
Transformacion de datos
La API, al igual que ofreca ayudas para insertar marcadores, nos ofrece una metodologa pa-
ra insertar capas transparentes. Estas capas (llamadas tiles), han de tener unas dimensiones de
256x256 pixels, y se numeran mediante coordenadas X e Y para cada nivel de zoom (Z).
Para localizar las estaciones que quedan dentro de un tile, necesitaremos una metodo que trans-
forme la coordenada de la tile en un rectangulo de coordenadas latitud/longitud geograficas.
public static Rectangle2D . Double getTileRect ( int x , int y , int zoom ) {
int tilesAtThisZoom = 1 << zoom ;
double lngWidth = 360.0 / tilesAtThisZoom ; // anchura de longitud en
grados
double lng = -180 + ( x * lngWidth ) ; // longitud del borde izquiedo ( en
grados )
double latHeightMerc = 1.0 / tilesAtThisZoom ; // altura " normalizada "
para mercator 0 ,0 arriba a la izquierda
double topLatMerc = y * latHeightMerc ; // borde superior " normalizado "
para mercator 0 ,0 arriba a la izquierda
double bottomLatMerc = topLatMerc + latHeightMerc ;
// convertimos latitudes superior e inferior a mercator
// nota : las coordenadas entre ( -85 - +85) no ( -90 - 90)
double bottomLat = (180 / Math . PI ) * ((2 * Math . atan ( Math . exp ( Math . PI *
(1 - (2 * bottomLatMerc ) ) ) ) ) - ( Math . PI / 2) ) ;
double topLat = (180 / Math . PI ) * ((2 * Math . atan ( Math . exp ( Math . PI * (1
- (2 * topLatMerc ) ) ) ) ) - ( Math . PI / 2) ) ;
double latHeight = topLat - bottomLat ;
return new Rectangle2D . Double ( lng , bottomLat , lngWidth , latHeight ) ;
}
De cada estacion conocemos sus coordenadas. Necesitaremos otro metodo que nos permita calcular
a que punto de la tile corresponden dichas coordenadas segun la proyeccion del mapa que emplea
Google: Mercator 16 .
public static Point l a t L n g T o A b s o l u t e P i x e l C o o r d s A t Z o o m ( double lat , double
lon , int zoom , int tileSize ) {
// convertimos lat / lon a la proyeccion Mercator
if ( lon > 180) lon -= 360;
lon /= 360;
lon += 0.5;
lat = 0.5 - (( Math . log ( Math . tan (( Math . PI / 4) + ((0.5 * Math . PI * lat ) /
180) ) ) / Math . PI ) / 2.0) ;
// ahora que tenemos las coordenadas normalizadas
double scale = (1 << zoom ) * tileSize ;
return new Point (( int ) ( lon * scale ) , ( int ) ( lat * scale ) ) ;
}
16 Proyeccion geografica tipo cilndrica, inventada por Gerardus Mercator en 1569. Se basa en un modelo ideal
que trata a la tierra como un globo que se introduce en un cilindro. Este cilindro cortado longitudinalmente y ya
desplegado sera el mapa con proyeccion de Mercator.
79
Generando la imagen
Definiremos una accion de Struts con la clase que se encargara de generar la imagen.
< action name = " capa - markers " class = " prometeo . web . CapaMarkersAction " >
< result name = " success " type = " stream " >
< param name = " contentType " > image / png </ param >
< param name = " inputName " > imageStream </ param >
< param name = " bufferSize " > 1024 </ param >
< param name = " allowCaching " > true </ param >
</ result >
</ action >
Dicha clase recogera unos parametros que seran las coordenadas de la tile (X, Y, zoom), as como
la variable y la fecha que hay que representar. Transformara las coordenadas en una region gracias
a la funcion getTileRect, y obtendra las estaciones comprendidas en su interior.
Para cada estacion efectuara la transformacion de coordenadas latitud/longitud a pixel en el
mapa, y dibujara un crculo con el color apropiado, apoyandose en las funciones coloreadoras que
vimos en la generacion de marcadores individuales.
Esta forma de generar las imagenes tiene la contrapartida de la zona de las fronteras entre las
tiles. Si una estacion esta muy cerca del borde, puede que el crculo no entre por completo en la
tile. Para solventar este tipo de situaciones lo que haremos sera crear una imagen un poco mas
grande, con los datos de las estaciones de una region algo mayor (un 10 %). En ultima instancia
recortamos la imagen al tamano apropiado.
De esta forma la imagen de cada tile muestra los marcadores de tiles adyacentes que sobresalen,
pasando desapercibido para el usuario la existencia de las tiles.
Crearemos una funcion JavaScript para cargar la visualizacion de capas transparentes de forma
sencilla cuando estemos a un zoom lejano. Basicamente le indicaremos la direccion desde la que
puede conseguir las imagenes para unas coordenadas dadas y la transparencia con la que mostrara el
resultado sobre el mapa.
function loadCapaMarkers ( iwid ) {
// cargamos variables necesarias ( bid , variable , fecha )
// ...
var myCopyright = new GCopyrightCollection ("") ;
myCopyright . addCopyright ( new GCopyright ( Mapa ,
new GLatLngBounds ( new GLatLng ( -90 , -180) , new GLatLng (90 ,180) ) ,0 , 2009
Prometeo ) ) ;
80
var tilelayer = new GTileLayer ( myCopyright ) ;
tilelayer . getTileUrl = function ( point , zoom ) {
return " capa - markers . html ? fecha ="+ date +"& step ="+ step +"& bid ="+ bid +"&
variable ="+ variable +"& x ="+ point . x +"& y ="+ point . y +"& z ="+ zoom ;
};
tilelayer . isPng = function () { return true ;};
tilelayer . getOpacity = function () { return 1.0; }
myTileLayer = new GTileLayerOverlay ( tilelayer ) ;
}
La funcion creara una variable global myTileLayer, que podremos agregar al objeto mapa mediante
la instruccion:
map . addOverlay ( myTileLayer ) ;
Figura 5.15: Vision alejada de precipitacion en el mapa. Las diferentes imagenes transparentes
encajan perfectamente.
81
Crearemos una clase llamada CapaModeloAction que sera la encargada de solicitar al DAO la
imagen correspondiente para una region determinada. Disponemos de las coordenadas de la tile,
por lo que transformaremos previamente a un rectangulo de coordenadas geograficas gracias a la
funcion getTileRect, tal y como hicimos al generar los marcadores.
La llamada al DAO simplemente le proporciona los parametros necesarios:
netCDFDao . getImage ( bid , variable , fecha , esquinas , WIDTH , HEIGHT ) ;
Figura 5.16: Mapa mostrando una capa con marcadores de predicciones de precipitacion y otra
con la temperatura de superficie simulada leda desde un fichero NetCDF.
Los ficheros NetCDF empleados suelen tener un tamano de varios Megabytes por lo que realizar
estas imagenes lleva su tiempo, pudiendo elevar mucho la carga del servidor. Para mejorar el
rendimiento emplearemos la cache de metodos que definimos gracias a EHCache y Spring. Le
indicaremos en el applicationContext.xml de Spring que queremos cachear el metodo de getImage
de la siguiente forma:
< bean id = " methodCachePointCut " class = " org . springframework . aop . support .
RegexpMethodPointcutAdvisor ">
< property name = " advice " >
< ref local = " m et ho dCa ch eI nte rc ep tor " / >
</ property >
< property name = " patterns " >
< list >
< value > .* getImage </ value >
</ list >
</ property >
</ bean >
< bean class = " org . springframework . aop . framework . ProxyFactoryBean " >
< property name = " target " >
< bean class = " prometeo . dao . preds . netcdf . NetCDFDaoImpl " / >
</ property >
< property name = " interceptorNames " >
< list >
82
< value > methodCachePointCut </ value >
</ list >
</ property >
</ bean >
83
YYYY - MM - DD valor
Crearemos un tipo de resultado Struts que automaticamente nos pase de una lista de observaciones
al formato necesario para la librera. Obtiene el listado del ValueStack y lo procesa con los formatos
de fecha y numerico configurados.
public class TimelineResult implements Result {
public void execute ( ActionInvocation invocation ) throws Exception {
ServletActionContext . getResponse () . setContentType ( " text / plain " ) ;
PrintWriter responseStream = ServletActionContext . getResponse () .
getWriter () ;
}
}
Esas seran las propiedades que almacenemos en el modelo VariablesData. El resultado sera con-
vertido a JSON gracias al tipo de resultado JSONresult.
84
< action name = " retrievevarinfo " class = " prometeo . web . AuxAction " method = "
dameVariablesInfo " >
< result name = " success " type = " JSONresult " >
< param name = " classAlias " > VarData </ param >
< param name = " classAliasClass " > prometeo . model . VariablesData </ param >
</ result >
</ action >
La funcion que llamaremos sera LoadTimeline. Tendra un parametro (iwid) que permitira que
haya varios widgets con timeline simultaneamente funcionando.
function LoadTimeline ( iwid ) {
// primero saco parametros de configuracion ( date1 , date2 , bid ,
variables , region , estacion )
// tambien inserto en la variable query todos ellos para construir la
peticion
// ...
Una vez recibida la informacion de las variables, se llamara a la funcion que realmente construye
la grafica, pasandole todos los datos necesarios.
85
drawTimeline = function ( iwid , variableData , variables , query ) {
var timeline_var = new Object () ;
timeline_var [ gridColor ] = new Timeplot . Color ( #333333 ) ;
timeline_var [ colors ] = new Array (
new Timeplot . Color ( # FFD176 ) ,
new Timeplot . Color ( #964 D1D ) ,
new Timeplot . Color ( # EB800F )
);
var timeGeometry = new Timeplot . DefaultTimeGeometry ({
gridColor : timeline_var [ gridColor ] ,
}) ;
5.10.3. Visualizacion
La parte de visualizacion del widget sera bastante simple. La plantilla freemarker tendra los
contenidos estandar del formulario de configuracion as como un contenedor div donde el JavaScript
se encargara de insertar la grafica mediante la funcion correspondiente.
Cada instancia de widget tendra su contenedor con un nombre dependiente del iwid de la siguiente
forma: widget-timeline-iwid-grafica-container.
86
5.11. Otros Widgets
La aplicacion se ha planteado de forma que sea relativamente sencilla la creacion de nuevos
widgets. Para explorar estas posibilidades creamos un par de widgets sencillos, que acceden a
fuentes de datos que ya se han empleado en alguno de los anteriores widgets.
87
Figura 5.20: Ejemplo de visualizacion del widget de histograma.
88
Figura 5.21: Aspecto final de la interfaz de usuario, con ejemplos de todos los widgets desarrollados.
89
Captulo 6
Conclusiones
90
a problemas de rendimiento en caso de darse un uso simultaneo por parte de muchos usuarios.
Quedara pendiente optimizar el rendimiento de los widgets realizados, incorporando por ejemplo
mas caches en los datos de uso frecuente.
Sera interesante agregar a los widgets la posibilidad de exportar su informacion en diversos
formatos como, por ejemplo, el RSS que permitira que pudiese ser accesible desde agregadores de
noticias.
Otra lnea de mejora de la aplicacion sera anadir nuevos widgets. Siguiendo el esquema de plantilla
freemarker y clase Java para el Widget, y definiendo el nuevo widget en el panel de control, quedara
listo para uso.
Gracias a esta estructura modular de Widgets la aplicacion queda preparada para anadir opciones
en el futuro y as se pueda seguir brindando al usuario una aplicacion potente desde la sencilla
interfaz de su Navegador Web.
91
Bibliografa
92
[13] Angermeier, Markus Mapa meme de Web 2.0
31 Oct 2006
2009 http://nww.nerdwideweb.com/web20/es.png
[14] Real Time MM5 Wheather Forecasts
2009 http://mm5forecasts.uib.es/
[15] Agencia Estatal de Meteorologa
2009 http://www.aemet.es
[16] Meteo Galicia
2009 http://www.meteogalicia.es
[17] Fox Weather News Forecast
2009 http://weather.fox.com/
[18] Weather Underground
2009 http://www.wunderground.com/
[19] Zeckoski, Aaron. Install Eclipse WTP
30 May. 2008 WG: Programmers Cafe
2009 http://bugs.sakaiproject.org/confluence/display/BOOT/Install+Eclipse+WTP
93
Captulo 7
Apendices
94
7.1.3. Instalacion de Tomcat
Durante el desarrollo, necesitaremos un servidor web local para ir comprobando que todo funciona
segun lo esperado. Necesitaremos un servidor que soporte la implementacion servlets de Java.
Emplearemos Tomcat, que es uno de los mas utilizados en su campo.
Tomcat es desarrollado bajo el proyecto Jakarta en la Apache Software Foundation. Tomcat
implementa las especificaciones de los servlets y de Java Server Pages (JSP) de Sun Microsystems.
El Eclipse con WTP esta preparado para ejecutar ejecutar proyectos web y subirlos a un servidor.
Si bien tenemos la opcion de instalar el servidor desde el propio asistente de ejecucion del Eclipse,
instalaremos manualmente Tomcat. La version elegida es la 6.0, y se puede descargar el archivo
comprimido desde: http://tomcat.apache.org/download-60.cgi
Una vez descargado y descomprimido ya estara listo para su ejecucion. En el eclipse simplemente
indicaremos la carpeta donde esta descomprimido.
JAVA HOME apuntando al raz de la instalacion de la JVM (en nuestro caso /usr/lib/jv-
m/jdk1.6.0 06)
M2 HOME apuntando al directorio donde hayas descomprimido Maven (/home/[nombre
usuario Linux]/apache-maven-2.0.9)
Dichas variables se configuran en Ubuntu 8.04 editando como administrador el fichero /etc/-
bash.bashrc
Se anaden las lineas:
export M2_HOME ="/ home /[ nombre usuario Linux ]/ apache - maven -2.0.9"
export JAVA_HOME ="/ usr / lib / jvm / jdk1 .6.0 _06 "
Tambien tendremos que incluir $JAVA HOME/bin y $M2 HOME/bin en la variable ya existente
PATH para poder ejecutarlos desde cualquier directorio. Para ello, en el fichero bashrc incorporamos
la linea:
export PATH = $JAVA_HOME / bin : $M2_HOME / bin : $PATH
3 http://maven.apache.org/
95
7.1.5. Appfuse Light para integrar Struts2 y Spring
Appfuse 4 es una aplicacion web base creada por Matt Raible donde el trabajo de conectar
todos los frameworks ya esta hecho. Sin embargo, el pegamento usado en esta aplicacion base es
demasiado fuerte, y es difcil modificar el comportamiento de lo que se conoce como el core de
Appfuse. Para controlar mas el proceso de desarrollo, existe Appfuse Light 5 .
Hemos de conseguir la version de Appfuse Light que contiene los frameworks que nos interesa
utilizar, en nuestro caso el que contiene Struts2 + Spring + iBATIS. Se puede descargar entre las
diferentes posiblidades en la siguiente URL:
https :// appfuse - light . dev . java . net / servlets / ProjectDocumentList
Se descomprime el fichero. Podemos hacerlo dentro de la carpeta workspace del Eclipse y luego
cambiar el nombre del directorio appfuse-light-struts2-ibatis-1.8.2 al nombre de tu futuro proyecto.
Desde la lnea de comandos, situado dentro del directorio de tu proyecto, ejecutamos:
mvn eclipse : eclipse
Bastara con incluir la librera correspondiente, y anadir los filtros necesarios al web.xml.
< filter >
< filter - name > sitemesh </ filter - name >
< filter - class >
com . opensymphony . module . sitemesh . filter . PageFilter
</ filter - class >
</ filter >
< filter - mapping >
< filter - name > sitemesh </ filter - name >
<url - pattern > /* </ url - pattern >
</ filter - mapping >
Como SiteMesh viene ya integrado por defecto en AppFuse, lo unico que habra que hacer es
configurarlo.
Spring Security
96
Instalamos el jar del Spring Security en la carpeta de libreras de nuestra aplicacion.
En el web.xml, agregamos los filtros que se encargaran de recoger los metodos propios del
Spring Security
97