Вы находитесь на странице: 1из 99

ESCUELA TCNICA SUPERIOR DE INGENIEROS

INDUSTRIALES Y DE TELECOMUNICACIN

UNIVERSIDAD DE CANTABRIA

Proyecto Fin de Carrera

Sistema basado en J2EE para la


visualizacin de informacin
meteorolgica con tecnologas Web 2.0

(J2EE-based system for displaying weather


information with Web 2.0 technologies )

Para acceder al Titulo de

INGENIERO DE TELECOMUNICACIN

Autor: Max Tuni San Martn

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

3. Estado del arte 16

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

El proyecto Prometeo fue desarrollado por el Grupo de Meteorologa y Minera de Datos de la


Universidad de Cantabria (http://www.meteo.unican.es) el colaboracion con la Agencia Estatal de
Meteorologa (AEMET). Se define como una aplicacion operativa de downscaling estadstico para
la prediccion de fenomenos locales.
Los servicios meteorologicos utilizan modelos atmosfericos (ACMs) que simulan la dinamica de la
atmosfera a gran escala, con una resolucion equivalente a 50 o 100 km. Estos modelos poseen menor
pericia en la prediccion de fenomenos que dependen de procesos fsicos complejos que ocurren a
escalas menores que la resolucion de los modelos.
Una de las tecnicas mas habituales para abordar este problema es el downscaling estadstico
(aumento de resolucion con metodos estadsticos) que consiste en interpolar las predicciones de
un modelo numerico dadas en una cierta rejilla a puntos subrejilla de mayor resolucion donde se
dispone de registros historicos de la variable que se desea predecir.
Se realizo hace anos una interfaz web que mostraba los datos de las predicciones en un mapa
basado en Applet de Java, as como un listado de predicciones por localidades. La idea de volver
a crear la Web surgio como necesidad de adaptarse a las nuevas tendencias en desarrollo Web,
personalizable para el usuario, y con las ultimas tecnologas como AJAX, prescindiendo de los
pesados applet Java.

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

2.1. La Programacion de Sitios Web


La World Wide Web (o la Web) es un sistema de documentos con texto y otros medios mul-
timedia entrelazados y accesibles a traves de Internet. Un usuario puede acceder a esta red de
contenidos sin mas que un programa instalado en su computadora conocido como navegador.
El concepto de La Web fue creado alrededor de 1989 por el ingles Tim Berners-Lee y el belga
Robert Cailliau mientras trabajaban en el CERN en Ginebra, Suiza, siendo publicado en 1992.
Desde entonces, ha ido creciendo en popularidad, siendo actualmente el servicio de Internet que
genera mayor trafico 1 .
La programacion de contenidos para la Web igualmente ha evolucionado con el tiempo. En los
orgenes, la mayor parte de los sitios web estaban hechos manualmente en el lenguaje HTML -
HyperText Markup Languaje - mediante un editor de texto. No contenan ninguna logica adicional.
Antes de la adopcion masiva de la banda ancha, el acceso a los datos llevaba su tiempo, por lo
que resultaba interesante poder hacer calculos desde el propio navegador del cliente, sin necesidad
de contactar con el servidor para tareas sencillas. Con esa idea surgio el lenguaje JavaScript,
fruto de la colaboracion de Netscape y Sun, que no fue estandarizado hasta 1997, cuando se
envio la especificacion JavaScript 1.1 al organismo ECMA (European Computer Manufacturers
Association).
Hoy en da, con la necesidad de aplicaciones complejas y que sean capaces de mostrar informacion
siempre actualizada, toma mayor relevancia la programacion desde el lado del servidor. El servidor,
en vez de enviar al usuario unos documentos estaticos, lo que hace es recoger la peticion, realiza
una serie de calculos, y genera dinamicamente un resultado HTML.
Un ejemplo tpico sera un servidor que se alimenta de una base de datos de noticias. El servidor
automaticamente podra acceder a la base de datos, coger la ultima noticia disponible y mostrarla
a los usuarios. Sin la programacion del lado de servidor, alguien tendra que manualmente cambiar
la web cuando hubiese nuevas noticias.

2.1.1. Alternativas para la programacion desde el lado del servidor


Existen diferentes plataformas sobre las que podemos realizar la programacion desde el lado del
servidor. Los lenguajes mas empleados en el desarrollo web actual son:
1 Excluyendo los protocolos de intercambio de archivos entre pares P2P

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.

ASP.net. Es la implementacion de Microsoft, abierta recientemente (2009) y bastante popu-


lar. Sin embargo es dependiente de la plataforma (necesitaremos emplear su Sistema Operati-
vo en el servidor), con lo cual no nos parece lo suficientemente flexible. Ademas su comunidad
de usuarios tiene a ser algo mas cerrada, sobre todo orientada a profesionales.
Java. Al contrario que los anteriores, no es un lenguaje interpretado sino compilado, siendo
la ejecucion algo mas veloz. Es un lenguaje de proposito general orientado a objetos y muy
potente, siendo idoneo para proyectos de gran envergadura. Posee una activa comunidad de
desarrolladores, que ha creado diversos frameworks as como una gran coleccion de libreras.

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.

2.2. Programacion Web en Java


El protocolo de transferencia de la Web es el HTTP, que esta basado en texto. Trabajaremos del
lado del servidor con Java que es orientado a objetos. Necesitaremos pues un intermediario que nos
sirva de traductor entre nuestros objetos Java, y los textos HTTP que pueda entender el navegador
del usuario. Este intermediario sera la API Java Servlet.
La citada API ademas nos proporcionara la funcionalidad del seguimiento de sesiones. Una se-
sion relaciona las diversas peticiones que ha realizado el usuario con el tiempo, caracterstica no
disponible en el protocolo HTTP.

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.

Eleccion del Entorno de Desarrollo

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.

2.3. Patrones de diseno


Un patron de diseno es una solucion a un problema de diseno con ciertas caractersticas, como la
efectividad resolviendo problemas similares o la de ser reusable.
Una forma basica de afrontar el proyecto sera realizarlo en Java Server Pages (JSP) 4 . Mediante
JSP podemos construir servlets, que son los objetos Java disenados para ofrecer contenido web
dinamico. Veamos un ejemplo de codigo JSP que accede a la base de datos e imprime un documento
HTML con una lista de usuarios.
< %@ page import = " java . sql .* " %>
<%
String login = " root " ;
String password = " " ;
String url = " jdbc : mysql :// localhost :3306/ mysql " ;
%>
< html > < body >
<%
Class . forName ( " com . mysql . jdbc . Driver " ) . newInstance () ;
Connection conn = DriverManager . getConnection ( url , login , password ) ;
Statement statement = conn . createStatement () ;
ResultSet rs = statement . executeQuery ( " SELECT * FROM user " ) ;
while ( rs . next () ) {
out . println ( rs . getString ( " user " ) + " <br > " ) ;
}
rs . close () ;
%>
</ body > </ html >

Tenemos en el mismo fragmento de codigo el acceso a datos, la logica de la aplicacion as como la


visualizacion de los datos. Empleando esta metodologa, a medida que crezca la complejidad de la
aplicacion complicara el mantenimiento, ademas de ser muy dificultoso reutilizar partes del codigo
comunes para futuros servlets.
En las aplicaciones web es relativamente frecuente realizar cambios en la interfaz. Con esta me-
todologa, en el momento que necesitemos realizar dichos cambios, tendremos que modificar tra-
bajosamente los componentes de negocio. Mayor trabajo y mas riesgo de error.
4 http://java.sun.com/products/jsp/

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.

Figura 2.1: Patron Modelo-Vista-Controlador

La logica de negocio dependera fuertemente del objetivo de la aplicacion concreta, pero el resto
del codigo sera posible reutilizarlo con mayor facilidad.

2.3.2. Arquitectura de capas (Layers)


Este patron procura dividir la logica del negocio en una serie de capas, con ciertas ventajas:

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.

2.4. Web 2.0


El concepto de Web 2.0 surgio en una sesion de brainstorming realizada entre OReilly y Media-
Live International. Dale Dougherty, pionero de la web y vicepresidente de OReilly, observaron que,
tras el estallido de la burbuja tecnologica en 2001, lejos de estrellarse, la Web era mas importante

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.

Figura 2.3: Conceptos que engloba la Web 2.0 [13]

As, unas caractersticas claves de este tipo de sitios son

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 >

Ejemplo equivalente de datos empaquetados en JSON.


{" menu ": {
" id ": " file " ,
" value ": " File " ,
" popup ": {
" menuitem ": [
{" value ": " New " , " onclick ": " CreateNewDoc () "} ,
{" value ": " Open " , " onclick ": " OpenDoc () "} ,
{" value ": " Close " , " onclick ": " CloseDoc () "}
]
}
}}

15
Captulo 3

Estado del arte

Para poder contextualizar mejor el proyecto, analizaremos algunos servicios web de informacion
meteorologica caractersticos tanto nacionales como internacionales.

Agencia Estatal de Meteorologa La AEMET [15] ofrece en su pagina principal datos de


observaciones y predicciones. Las observaciones son de datos recogidos de diversas variables (como
temperaturas) o imagenes de satelite. Las predicciones estan disponibles tanto por localidades
(donde se muestran sus predicciones numericas en una tabla) o a traves de un mapa de Espana
donde se ha de seleccionar la provincia.
Las predicciones de distintas fuentes no se ofrecen sobre el mismo formato. Esto resulta algo
incomodo y de hecho han aparecido diversas paginas que acceden a esta informacion para ofrecerla
de forma mas atractiva, como por ejemplo QueTiempo 1 , que muestra las predicciones locales de
AEMET sobre el servicio de mapas de Google Maps.

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.

Weather Underground La pagina de Weather Underground [18] ofrece la representacion grafi-


ca de los modelos as como datos relativos a estaciones meteorologicas (estado actual y prediccion)
1 http://www.quetiempo.es

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

Un framework es un conjunto de abstracciones disenado con el proposito de facilitar el desarrollo


de software, permitiendo a los disenadores y programadores pasar mas tiempo identificando reque-
rimientos de software que tratando con los tediosos detalles de bajo nivel de proveer un sistema
funcional. Es una buena idea usar un framework para proyectos de cierta envergadura.
En este captulo daremos una vision general de la arquitectura escogida y posteriormente justifi-
caremos los frameworks elegidos, introduciendo brevemente sus caractersticas mas importantes.

4.1. Arquitectura de la Aplicacion


La arquitectura sigue el patron J2EE de tres capas:

1. Capa de presentacion (VIEW), se corresponde con el interface de usuario de la aplicacion y va


a ser la encargada de recoger las acciones de entrada de los usuarios y mostrar la informacion
resultante de salida.
2. Capa de negocio (SERVICE), se corresponde con la operativa propia de la aplicacion y va a
ser la encargada de recoger las acciones de la capa de presentacion y ejecutarlas utilizando
la informacion aportada por la capa de acceso a datos.
3. Capa de acceso a datos (DAO), se corresponde con la gestion de la informacion de la aplicacion
y va a ser la encargada de aportar los datos necesarios a la capa de negocio.

Para la capa de presentacion se va a utilizar el componente Struts.


Para la capa de negocio se va a utilizar el componente Spring.
Para la capa de acceso a datos se va a utilizar el componente iBATIS cuando haya que conectar
con bases de datos.
Spring nos va a permitir, ademas, configurar y comunicar las diferentes capas de la aplicacion,
as como integrar los servicios de seguridad y cacheado en la aplicacion.

4.2. Framework para el desarrollo Web


Existen diversos frameworks para el desarrollo Web Java que implementan el patron MVC. Los
principales son: Struts, Beehive, Spring, Tapestry, Aurora y JavaServerFaces. Todos ellos se distri-
buyen con la licencia libre de Apache. El que emplearemos en nuestro proyecto sera Struts 2.

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.

4.2.2. MVC en Struts 2


En Struts 2 los conceptos de Modelo Vista Controlador son implementados respectivamente por
los elementos action, result y FilterDispacher. Action pues se encargara del modelo de datos y
la logica de negocio. Result contendra la presentacion de resultados, generalmente se tratara de
paginas JSP que generaran el HTML. FilterDispacher se encarga de mapear las peticiones del
usuario con las acciones que deberan ser invocadas.

Figura 4.2: Arquitectura de Struts [2]

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.

4.2.3. Introduccion al uso


Ya hemos visto brevemente los aspectos teoricos de Struts 2 y como implementa el modelo MVC.
Ahora se hara un pequeno repaso al funcionamiento basico de Struts 2, olvidando por un momento
los detalles mas avanzados. [1]
Cuando se inserta una URL 1 en el navegador, y accedemos a un servidor preparado con Struts
2, intervienen siempre tres ficheros:

struts.xml

Este fichero contiene la arquitectura de la aplicacion. Observa la URL que se ha introducida y si


corresponde con algo que entienda asociara los demas ficheros involucrados.
< action name = " HelloWorld " class = " prometeo . HelloWorld " >
< result > HelloWorld . jsp </ result >
</ action >

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:

La etiqueta de Struts: <s:text name=admin.title/>


La etiqueta de JSTL: <fmt:message key=admin.title/>

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

7. Bundle de mensajes globales

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.

4.3. Framework para Inyeccion de Dependencias


Una buena practica en el software es la llamada Inversion de Control (IoC), que se basa en la
idea de No nos llames, ya te llamamos nosotros.
Tradicionalmente, el programador especifica la secuencia de decisiones y procedimientos que pue-
den darse durante el ciclo de vida de un programa mediante llamadas a funciones. En su lugar, en
la inversion de control se especifican respuestas deseadas a sucesos o solicitudes de datos concretas,
dejando que algun tipo de entidad o arquitectura externa lleve a cabo las acciones de control que
se requieran en el orden necesario y para el conjunto de sucesos que tengan que ocurrir.
La forma tradicional de crear objetos es instanciarlos mediante el operador new, siendo esta una
manera poco eficiente de trabajar, ya que de cambiar el tipo de objeto tendramos que retocar
el codigo en muchas partes de la aplicacion, ademas de usar el poco flexible operador new. En
ocasiones nos interesara obtener las referencias a nuestros objetos de formas alternativas, ya sea
mediante factoras o localizadores de servicios.
Al reducirse el acoplamiento entre los objetos, podremos obtener otras ventajas, como el empleo
de mocks (objetos simulados) para realizar pruebas con mayor rapidez.
Disponer de un framework encargado de la inyeccion de objetos nos permite resolver las depen-
dencias del codigo, mediante la simple configuracion del framework.
Este patron de programacion se ha vuelto popular recientemente en la comunidad J2EE. Los fra-
meworks de IoC mas empleados son Spring y PicoContainer. Spring es un Framework de proposito
mas general (tiene otras capacidades ademas de la de IoC), pero emplearemos su parte de inyeccion
de dependencias ya que esta ampliamente documentado y es el mas utilizado.
Los primeros componentes de lo que se ha convertido en Spring Framework fueron escritos por
Rod Johnson en el ano 2000, mientras trabajaba como consultor independiente en Londres. El
framework fue lanzado bajo Apache 2.0 License en el 2003, apareciendo la version 1.0 ya en 2004.
En nuestra aplicacion emplearemos la version 2.5, datada en 2007, y tiene algunas importantes
mejoras:

Permite definir la configuracion tanto por XML como por anotaciones.


Esta preparado para la version 6 de Java EE.

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 "/ >

As encontrara la clase que implementa a PrometeoManager:


@Service ( value = " prometeoManager " )
public class PrometeoManagerImpl implements PrometeoManager {
// ...
}

Sin mayor configuracion, cada vez que se necesite emplear prometeoManager y exista el metodo
setter apropiado, Spring se encargara de inyectar el objeto.

Definicion de dependencias en applicationContext.xml

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:

La capa de Abstraccion. Es la interfaz con la capa de la logica de negocio. Se implementa de


forma general mediante el patron Data Access Object (DAO).
Interfaz con el gestor de Base de Datos ocupandose de la gestion de los datos mediante un
API.
La capa de Driver se ocupa de la comunicacion con la propia Base de Datos utilizando un
Driver especfico para la misma.

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.

4.4.1. Persistencia con iBATIS


Su mision es asociar objetos de modelo (JavaBeans) con sentencias SQL o procedimientos alma-
cenados mediante ficheros descriptores XML, simplificando la utilizacion de bases de datos.
Lo integraremos en la aplicacion gracias a Spring. Se define un bean llamado sqlMapClient, que
sera inyectada a los objetos DAO y que contiene la informacion sobre el driver y los parametros
de conexion.
< bean id = " dataSource " class = " org . apache . commons . dbcp . BasicDataSource "
destroy - method = " close " >
< property name = " driverClassName " value = " ${ prometeo - metadata . jdbc .
driverClassName } " / >
< property name = " url " value = " ${ prometeo - metadata . jdbc . url } " / >
< property name = " username " value = " ${ prometeo - metadata . jdbc . username } " / >
< property name = " password " value = " ${ prometeo - metadata . jdbc . password } " / >
< property name = " maxActive " value = " 30 " / >
< property name = " maxIdle " value = " 10 " / >
< property name = " maxWait " value = " 1000 " / >
< property name = " defaultAutoCommit " value = " true " / >
</ bean >

<! -- SqlMap setup for iBATIS Database Layer -- >


< bean id = " sqlMapClient " class = " org . springframework . orm . ibatis .
S ql Ma p Cl i en tF a ct o ry Be a n " >
< property name = " dataSource " ref = " dataSource " / >
2 Plain Old Java Object, clases Java simples

26
< property name = " configLocation " value = " classpath: / es / predictia / prometeo /
dao / ibatis / sql - map - config . xml " / >
</ bean >

4.4.2. Configuracion de iBATIS


iBATIS se configura mediante los ficheros sqlMap:

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 >

4.4.3. DAOs de nuestra aplicacion


Configuraremos una serie de interfaces e implementaciones Java para acceder a nuestros datos.
Lo primero es definir nuestro modelo de datos, que sera una clase publica con las caractersticas
estandar de JavaBeans. Tendra pues una serie de variables internas y los getters/setters que nos
permiten manejarlas.
Ahora definiremos el DAO como un interfaz con las funciones que ofreceremos al que quiera
acceder a los datos. La implementacion del DAO se realizara mediante iBATIS, extendiendo su
clase SqlMapClientDaoSupport. Por ejemplo si nuestro modelo es Vista, el DAO es VistaDao, lo
implementamos mediante VistaDaoiBatis as:
public class VistaDaoiBatis extends Sq lM ap Cli en tD aoS up po rt implements
VistaDao

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. Framework de cacheado


Ciertas tareas requieren un elevado tiempo de computo y han de ser realizadas frecuentemente.
Para estos casos resulta interesante almacenar los resultados la primera vez que se solicitan, sin
necesidad de recalcularlos en sucesivas peticiones.
Los frameworks de cacheado mas importantes son EHCache, OSCache y Java Caching System
(JCS). Nos decantaremos por EHCache ya que es ligero, versatil, sencillo de usar y ademas se
puede integrar con facilidad con Spring.

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.

4.6. Framework de seguridad


Los Frameworks de Seguridad proporcionan caractersticas como la autenticacion y la autoriza-
cion. La autenticacion es el proceso de decidir si el usuario es quien afirma ser. La autorizacion se
refiere al proceso de decidir si el usuario esta autorizado a realizar una accion.
Dado que la seguridad es aspecto crtico en cualquier aplicacion, sera interesante confiar estos
aspectos a un framework de forma que se sigan unos estandares de seguridad que protejan apro-
piadamente la aplicacion de usuarios malintencionados. Ademas nos proporcionaran una manera
comoda de manejar el acceso de usuarios.
Emplearemos para esta funcion Spring Security ya que es un framework potente y flexible.
Ademas, se adaptara perfectamente a nuestra aplicacion ya que esta integrada con Spring, su
proyecto padre.

4.6.1. Spring Security como Framework de Seguridad


Spring Security [4] es un proyecto que comenzo en 2003 bajo el nombre de Acegi Security. Tras ser
liberado bajo la licencia Apache fue adoptado por Spring como uno de sus sub-proyectos oficiales.

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.

4.7. Capa Visualizacion


4.7.1. JSP, Freemarker, Sitemesh
Para la parte de visualizacion de la aplicacion usaremos basicamente tres elementos: JSP, Free-
marker y Sitemesh.

Java Server Pages

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 >

Su lenguaje de plantillas es bastante potente, con posiblidad de programacion de macros. Ademas


soporta al igual que JSP el empleo de Taglibs. La taglib de Struts viene incluida por defecto.
Veamos por ejemplo como emplear un ArrayList. Si tenemos en la variable librerias un elemento
por cada JavaScript necesario, podramos incluirlos todos mediante:

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.

Figura 4.4: Funcionamiento de Sitemesh [11]

El fichero de configuracion esta en /WEB-INF/decorators.xml. Ah se han definido las peticiones


que no necesitan decoracion (es la directiva excludesy se la aplicaremos a los directorios donde
haya imagenes, scripts...). Tambien se ha definido el fichero /decorators/default.jsp que sera donde
incluiremos la decoracion que se insertara en las paginas.
Dicho fichero contendra el HTML base y en el se definiran una serie de contenedores que recibiran
el contenido diferencial de cada pagina, mediante la tag decorator. Dichos containers se rellenaran
automaticamente con el HTML si coincide su nombre con la tag. As, el contenido HTML en la
etiqueta body de una pagina sera incluida en donde el decorador tenga definido:
4 http://www.opensymphony.com/sitemesh/

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 " %>

4.7.2. Tag-Libs Utiles


Ademas de las libreras de tags basicas (JSTL y de Struts), emplearemos otras que nos ayudaran
a agilizar diversas tareas recurrentes de la parte de vista. Emplearemos Displaytag 5 para dibujar
las tablas as como AjaxTags 6 para definir el JavaScript necesario para dinamizar nuestras paginas
mediante AJAX.

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 >

Tambien anadiremos a nuestro fichero de taglibs.jsp:


< %@ taglib uri =" http :// ajaxtags . sourceforge . net / tags / ajaxtags " prefix =" ajax
" %>

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.

Display tag library

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 "
%>

La mejor forma de comprender su uso es con un ejemplo.


< display:table name = " usuarios " >
< display:column property = " uid " titleKey = " admin - usuario . uid " media = " html
csv excel xml pdf " / >
< display:column property = " uname " titleKey = " admin - usuario . uname " media = "
csv excel xml pdf " sortable = " true " / >
< display:column property = " uname " media = " html " sortable = " true " href = "
adminusuariosedit . html " paramId = " uid " paramProperty = " uid " titleKey =
" admin - usuario . uname " / >
< display:column property = " mail " titleKey = " admin - usuario . mail " media = "
html csv excel xml pdf " / >
< display:column href = " adminusuariosdelete . html " media = " html " paramId = "
uid " paramProperty = " uid " titleKey = " column . delete " >
< fmt:message key = " column . delete " / >
</ display:column >
</ display:table >

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.

4.7.3. Librera de creacion de graficos JFreeChart


En ocasiones querremos representar informacion de forma grafica. Para ello nos valdremos de la
librera JFreeChart, que es una de las mas potentes en su ambito. Esta programada en Java y se
incluye en la Java Class Library estandar.

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.

5.1. Definicion del Modelo de Datos


Dado que estaremos creando una web dinamica, nos interesara disponer de una base de datos.
As, se podra acceder a la informacion de forma sistematica y a mayor velocidad gracias a las
facilidades que nos aportan este tipo de aplicaciones.
El diseno de una base de datos es un proceso complejo que abarca decisiones a muy distintos
niveles. La complejidad se controla mejor si se descompone el problema en subproblemas indepen-
dientes. As, el diseno de una base de datos se descompone en diseno conceptual, diseno logico y
diseno fsico.

El diseno conceptual parte de las especificaciones de requisitos de usuario y su resultado es el


esquema conceptual de la base de datos. Un esquema conceptual es una descripcion de alto
nivel de la estructura de la base de datos.
El diseno logico parte del esquema de alto nivel y resulta en un esquema logico en terminos
de las estructuras de datos que puede procesar un gestor de bases de datos.

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.

Como gestor de bases de datos relacionales emplearemos MySQL. Es un sistema multihilo y


multiusuario bastante popular desarrollada por MySQL AB, filial de Oracle, como software libre
en un esquema de licenciamiento dual.

5.1.1. Analizando los requerimientos


Se dara soporte para varios tipos de usuarios, pudiendo acceder cada uno a diferentes datos.
As pues, necesitaremos definir usuarios y roles que agrupen estos tipos de usuarios. Tambien
almacenaremos en la base de datos los recursos a los que puede acceder cada usuario segun su rol
y las configuraciones propias de cada usuario.
Esta base de datos se manejara en la aplicacion web con las caractersticas tpicas CRUD (Create,
Read, Update y Delete). Los datos en s que se ofreceran a estos usuarios estaran alojadas en otras
bases de dato o contenedores de datos diferentes, y consultadas simplemente en modo lectura.

5.1.2. Base de datos de la Aplicacion


La nomenclatura de las tablas de la BBDD que se usara sera del tipo:

Nombre1 para una tabla de datos.


Nombre2 para otra tabla de datos

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.

5.1.3. Tablas de datos


A continuacion se listaran las tablas definidas para la aplicacion. Algunas de ellas soportaran
la informacion de los usuarios (usuarios, roles y perfiles) mientras otras estaran enfocadas a las
fuentes de datos (BBDDs y Tipos). Habra otras que almacenaran la configuracion del usuario,
as como sus limitaciones (Widgets, Vistas, Layouts y Filtros).
Crearemos tambien una tabla de variables ya que estas en principio pueden ser comunes entre
diferentes fuentes de datos.

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.

FiltrosEspaciales: Permitira limitar la zona geografica en la que el usuario puede acceder a


los datos.
FiltrosFechas: Permitira limitar la fecha de las observaciones disponibles al usuario.

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.

5.2. Manejador de la Aplicacion


Para poder acceder a la base de datos con mayor comodidad, nos vendra bien crear una clase
que nos proporcione dicho servicio. Ofrecera una serie de metodos que puedan realizar las tareas
necesarias, desde anadir un nuevo usuario hasta actualizar la configuracion de una fuente de datos.

5.2.1. Generacion de los modelos y DAO de la Aplicacion


La herramienta iBATOR nos ahorrara el proceso de crear los archivos del Modelo Java as como
los ficheros sqlMap. El modelo no es mas que una clase de Java con diferentes propiedades (una
por cada campo) y los getters/setters para acceder a ellas. Automaticamente leera los campos de
cada tabla y creara el modelo correspondiente.
Tambien nos creara los DAO por defecto. Consisten en una lista de funciones que podremos utilizar
para el acceso a la base de datos. Concretamente tendremos funciones de lectura, modificacion o
borrado de datos, ya sea por clave primaria o por expresion. Ademas, se incluye el SqlMap de
iBATIS que implementa todas estas consultas.
Ejemplo de funciones del DAO generado.
public interface BbddsDAO {
void insert ( Bbdds record ) ;
int u p d a t e B y P r i m a r y K e y W i t h o u t B L O B s ( Bbdds record ) ;
...

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)

Figura 5.1: Diagrama de la base de datos relacional


usuariosroles fdesc VARCHAR(255)
uid INT(11) Indexes
rid INT(11)
Indexes

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 " / >
...

5.2.2. Manejador de la Aplicacion


Ahora que ya disponemos de los DAO que nos permiten interactuar con la base de datos, podemos
construir el manejador de la aplicacion.
Crearemos una serie de metodos por cada tabla de la base de datos. Para las tablas normales(las
que contienen conjuntos de nuestros objetos modelados) crearemos las clasicas funciones CRUD,
de lectura, insercion, borrado y actualizacion de datos. Para las tablas que relacionan otras tablas,
crearemos funciones de insercion y borrado, as como otras funciones que nos permitan obtener los
objetos en funcion de una clave con la que se relaciona.
Veamos unos ejemplos de funciones definidas en la interfaz del manejador.
// funciones CRUD para la tabla BBDDs
public Bbdds getBbdd ( Integer bid ) ;
public List < Bbdds > getBbdds () ;
public void addBbdd ( Bbdds bbdd ) ;
public void updateBbdd ( Integer bid , Bbdds bbdd ) ;
public void deleteBbdd ( Integer bid ) ;
// funciones CRUD para la tabla PerfilesBBDDs
public List < Perfiles > getPerfilesFromBbdd ( Integer bid ) ;
public List < Bbdds > getBbddsFromPerfil ( Integer pid ) ;
public void addPerfilBbdd ( Perfilesbbdds perfilbbdd ) ;
public void deletePerfilBbdd ( Perfilesbbdds perfilbbdd ) ;

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 ) ;
...

5.3. Panel de Control


Crearemos un panel de control para que los administradores puedan manejar la configuracion
de la aplicacion Web comodamente. El interfaz sera accesible a traves del navegador, pero lo
situaremos en una ruta separada, con la idea de restringir su acceso de tal forma que solo los
usuarios identificados que tengan el Rol Administrador puedan acceder.

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 " >

Si por ejemplo entramos en la ruta del servidor 1 :


http :// localhost :8080/ prometeo2 / admin / adminBBDDs . html

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 >

5.3.2. El Panel de Control (CP)


El panel de control tendra una serie de subpaneles, para controlar las diferentes partes de la
aplicacion. Las enlazaremos todas desde la pagina principal del CP.

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 " ;
}

La parte de Vista (los JSP)

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 " ;

Funcionalidades apoyadas en Struts

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 ) ;

Internacionalizacion Para nuestro panel de control agruparemos todos los mensajes en un


ResourceBundle llamado package.properties, que es uno de los nombres que Struts buscara por de-
fecto. Indicaremos manualmente donde se encuentra el bundle mediante un fmt:bundle y usaremos
la etiqueta para mensajes fmt:message. Se eligio usar esta etiqueta en vez de la de Struts ya que
es la que usa la Display Tag Library que nos proporciona el soporte para dibujar tablas.

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.

Figura 5.2: Captura del panel de control de Usuarios.

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 >

Cogera los elementos indicados de la lista y generara el HTML

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.

5.4. Filtrado de datos


Desde el panel de control hemos visto que se pueden crear varios tipos de filtros que limitan los
datos a los que puede acceder el usuario. Cuando desarrollemos widgets tendremos que tener en
cuenta dichos filtros antes de mostrar los datos al usuario, por lo que sera una tarea frecuente
aplicar estos filtros.
Una forma comoda de aplicar los filtros sera disponer una serie de funciones en el manejador de
la aplicacion que reciba un conjunto de valores y un conjunto de filtros y devuelva el conjunto
filtrado.
Si no hay filtro tendremos que devolver los datos tal cual. Si hay varios filtros, con que alguno de
ellos permita la variable, la variable se considera disponible.
Las funciones que definiremos en prometeoManager seran:
public List < Date > filtraFechas ( List < Date > fechas , List < Filtrosfechas >
filtros ) ;
public List < Station > filtraStationsById ( List < Station > estaciones , List <
Filtrosids > filtros ) ;
public List < Station > f il t r a St a t i on s B yL o c a ti o n ( List < Station > estaciones ,
List < Filtrosespaciales > filtros ) ;
public List filtraVariables ( List variables , List < Filtrosvariables > filtros )
;

5.4.1. Filtrado de fechas


Se recibe una lista de fechas y una lista de filtros. En cada filtro se ha definido una fecha inicial
y otra final permitidas. Lo que haremos sera recorrer todos los filtros, definiendo una fecha de
comienzo global con la menor de las fechas de comienzo de cada filtro, y una fecha final global con
la mayor de las fechas finales de cada filtro.
Calculadas estas dos fechas, recorreremos la lista de fechas de entrada y si esta en el intervalo
(comienzo global, fecha final global) la fecha estara disponible a la salida. Para ello nos ayudamos
de los metodos asociados a los metodos del objeto Date [5] after y before.

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.

5.4.3. Filtrado de estaciones por localizacion


De cada estacion conocemos sus coordenadas espaciales (longitud, latitud y altitud). Los filtros
definen un polgono de puntos 2D (longitudes y latitudes) dentro de los cuales se permite el acceso.
Para saber si un punto esta dentro del area definida por un polgono nos ayudaremos de la librera
java.awt.Polygon [5]. Podemos crear un polgono a partir de un conjunto de puntos, y dispone de
un metodo contains que nos dira si el punto esta dentro de sus lmites.
Cada filtro contiene el listado de puntos del polgono almacenado como un string. Los puntos se
guardaran separados por puntos y comas, y las coordenadas por comas. Para dividir los strings
utilizaremos la funcion StringTokenizer [5]. Recibe como parametros la cadena a dividir y el sepa-
rador, y mediante el metodo nextToken vamos obteniendo los elementos.
Como unica contrapartida, el polgono ha de recibir puntos enteros. Las coordenadas sin embargo
son decimales. Para solventar esto con suficiente precision lo que haremos sera redondear al entero
la coordenada multiplicada por 100, as obtenemos una precision suficiente.
El codigo completo de la funcion filtradora quedara:
public List < Station > f il t r a St a t i on s B yL o c a ti o n ( List < Station > estaciones ,
List < Filtrosespaciales > filtros ) {
if ( filtros . size () ==0) return estaciones ;
List < Station > estacionesPermitidas } new ArrayList () ;
for ( Iterator < Station > it = estaciones . iterator () ; it . hasNext () ;) {
Station estacion = it . next () ;
for ( Iterator < Filtrosespaciales > fit = filtros . iterator () ; fit . hasNext ()
;) {
Filtrosespaciales filtro = fit . next () ;
String polyStr = filtro . getPoly () ;
Polygon poly = new Polygon () ;
StringTokenizer st = new StringTokenizer ( polyStr , " ; " ) ;
while ( st . hasMoreTokens () ) {
String punto } st . nextToken () ;
StringTokenizer st2 = new StringTokenizer ( punto , " ," ) ;
Float lat = Float . valueOf ( st2 . nextToken () ) ;
Float lon = Float . valueOf ( st2 . nextToken () ) ;
poly . addPoint ( Math . round ( lat *100) , Math . round ( lon *100) ) ;
}
if ( poly . contains ( estacion . getLatitud () *100 , estacion . getLongitud ()
*100) ) {
// si una estacion esta permitida por algun filtro la agrego y
no miro otros filtros
estacionesPermitidas . add ( estacion ) ;
break ;
}
}
}
return estacionesPermitidas ;
}

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 =*******

prometeo - prometeo . jdbc . driverClassName = com . mysql . jdbc . Driver


prometeo - prometeo . jdbc . url = jdbc : mysql :// localhost :3306/ oprinm
prometeo - prometeo . jdbc . username = root
prometeo - prometeo . 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.

Figura 5.3: Listado de fuentes de datos configurados en el panel de control

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.

5.5.1. Modelos genericos de estaciones, predicciones y observaciones


Nos vendra bien hacer un modelo de datos que sea independiente de la forma de almacenaje
concreto de la informacion de cada fuente de datos. Haremos as tres modelos JBean, uno pa-
ra estaciones (Station), otro para el valor concreto de una prediccion en una estacion concreta
(Prediction) y por ultimo otro para almacenar el valor de una observacion para una estacion y
2 http://grupos.unican.es/ai/meteo/MeteoLab.html

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 ;

El modelo de predicciones simplemente almacenara un identificador de estacion y un valor de


prediccion.
private Station station ;
private float value ;

El modelo para observaciones almacenara ademas datos sobre la variable y la fecha.


private Station station ;
private Date fecha ;
private Variables variable ;
private float value ;

5.5.2. Manejador de Predicciones


Para los widgets que accedan a diferentes fuentes de datos de predicciones, sera util definir un
manejador desde el cual se facilite la tarea de conseguir el DAO apropiado. En la tabla de bases de
datos se guardara un String (bloc) que identificara cada una con su clase de DAO que proporciona
el acceso.
Definiremos por tanto un manejador con un metodo getDao(String dataSource) que nos consiga
el DAO a partir de la cadena bloc. Dicho DAO se buscara en una lista asociativa que se inyec-
tara mediante Spring.
Implementacion de PredsManager:
@Service ( value = " predsManager " )
public class PredsManagerImpl implements PredsManager {
@Autowired
private DataSourceCollection dataSources ;
public Object getDao ( String dataSource ) {
return dataSources . getCollection () . get ( dataSource ) ;
}
public void setDataSources ( DataSourceCollection dataSources ) {
this . dataSources = dataSources ;
}
}

Definicion del conjunto posible de DataSources en el actionContext.xml:


< bean id = " dataSources " class = " prometeo . dao . preds . DataSourceCollection " >
< property name = " collection " >

50
< map >
< entry key = " prometeo . dao . preds . prometeo . PrometeoDAO " value - ref = "
prometeoDao " / >
</ map >
</ property >
</ bean >

5.5.3. Predicciones de Prometeo


Esta fuente de datos nos proporcionara predicciones diarias y con alcance (step) de varios das.
Se generaran de forma transparente para nosotros.
Para acceder a esta fuente de datos, crearemos un DAO.
En el fichero de configuracion de Spring (applicationContext-ibatis.xml ), definiremos un bean que
emplee el conector JDBC apropiado y sera inyectado automaticamente en un DAO que definiremos
como prometeoDao:
< bean id = " prometeo " class = " org . apache . commons . dbcp . BasicDataSource " destroy
- method = " close " >
< property name = " driverClassName " value = " ${ prometeo - prometeo . jdbc .
driverClassName } " / >
< property name = " url " value = " ${ prometeo - prometeo . jdbc . url } " / >
< property name = " username " value = " ${ prometeo - prometeo . jdbc . username } " / >
< property name = " password " value = " ${ prometeo - prometeo . jdbc . password } " / >
< property name = " maxActive " value = " 30 " / >
< property name = " maxIdle " value = " 10 " / >
< property name = " maxWait " value = " 1000 " / >
< property name = " defaultAutoCommit " value = " true " / >
</ bean >
< bean id = " sqlMapClient . prometeo " class = " org . springframework . orm . ibatis .
S ql Ma p Cl i en tF a ct o ry Be a n " >
< property name = " dataSource " ref = " prometeo " / >
< property name = " configLocation " value = " classpath: / prometeo / dao / ibatis /
sql - map - config - prometeo . xml " / >
</ bean >
< bean id = " prometeoDao " class = " prometeo . dao . preds . prometeo . PrometeoDAOImpl " >
< property name = " sqlMapClient " ref = " sqlMapClient . prometeo " / >
</ bean >

Contenido de la Base de datos de Predicciones Prometeo

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

DAO de la base de datos de predicciones 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.

Dao generico de lectura de Observaciones

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).

Figura 5.5: Configuracion de la fuente de Observaciones del INM en el panel de control

5.5.5. Lectura de datos desde ficheros NetCDF


Un formato estandar muy empleado para el almacenaje de datos cientficos es el NetCDF 3 . Esta
pensado para almacenar arrays de datos geoespaciales de forma independiente de la maquina y
auto explicativa: los ficheros contienen una cabecera que indica el contenido del propio fichero.
La lectura de este tipo de ficheros sobrepasa los objetivos del proyecto. En su lugar emplearemos
una librera de codigo abierto capaz de generar salidas para mapas a partir de datos en NetCDF,
llamada ncWMS [12], que a su vez se apoya en la librera NetCDF API de Unidata 4 .
3 Network Common Data Form, http://www.unidata.ucar.edu/software/netcdf
4 http://www.unidata.ucar.edu/software/netcdf-java/

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 ) {
// ...
}
}

Ejemplo de fuente NetCDF

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 .

5.6. Registro de usuarios. Permisos


En la pagina puede entrar cualquier persona mediante su navegador. No toda la informacion que
podra mostrarse es de dominio publico, por lo que sera necesario un control de usuarios, de forma
que algunos tendran acceso a mas o menos datos segun su perfil.
Ademas, los usuarios registrados tendran la posibilidad de guardar en la base de datos la confi-
guracion que hayan aplicado, de forma que se cargara automaticamente cuando vuelvan al sitio e
introduzca sus credenciales.

5.6.1. Registro de usuarios


El formulario de registro pedira los datos que se usaran posteriormente como credenciales: usuario
y contrasena. De igual forma se solicitaran algunos datos adicionales que podran ser de utilidad
5 http://www.mpimet.mpg.de/en/wissenschaft/modelle/echam/echam5.html
6 http://www.ipcc-data.org/ar4/scenario-20C3M.html

55
Figura 5.6: Configuracion de la fuente de datos de ficheros NetCDF

mas adelante, como el correo electronico.


Los datos que introduce el usuario han de ser validados, y para ello emplearemos un validador
de Struts. Gracias a ello, de forma similar a como se realizaba en el panel de control de usuarios,
se comprueba que el nombre de usuario no este ya utilizado, que el correo electronico sea valido y
que las contrasenas coincidan y no sean nulas.
Para evitar registros automatizados se colocara ademas un CAPTCHA 7 que mostrara una imagen
con un texto al usuario y el usuario tendra acertar dicho texto.

Arquitectura del registro de usuarios

La arquitectura elegida, se configuro en el fichero struts.xml de la siguiente forma:


< action name = " registro " class = " prometeo . web . UserAction " >
< result name = " success " > registrar . jsp </ result >
</ action >
< action name = " registro - salvar " class = " prometeo . web . UserAction " method = "
register " >
< result name = " input " > registrar . jsp </ result >
< result name = " success " type = " redirect " > login . html </ result >
</ action >
< action name = " imagen - captcha " class = " prometeo . web . GraficaCaptchaAction " >
< result name = " success " type = " stream " >
< param name = " contentType " > image / png </ param >
< param name = " inputName " > imageStream </ param >
< param name = " bufferSize " > 1024 </ param >
< param name = " allowCaching " > false </ param >
</ result >
</ action >

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

automatica para diferenciar a maquinas y humanos).

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.

Figura 5.7: Ejemplo de CAPTCHA.

Hemos creado una logica de validacion almacenada en un fichero llamado UserAction-validation.xml,


de forma que cuando el usuario enve el formulario se aplicaran sus reglas. En caso de que algun va-
lidador detecte un error, el usuario sera enviado de nuevo al formulario. Tendremos validadores de
Struts que se encargan de comprobar los campos obligatorios y, ademas, construiremos un validador
personalizado que verifique el texto que ha introducido el usuario al descifrar el CAPTCHA.
La configuracion del validador es la siguiente:
< field name = " captcha " >
< field - validator type = " captchavalidator " >
< param name = " fieldName " > captcha </ param >
< message key = " errors . captcha " > Wrong captcha text . </ message >
</ field - validator >
</ field >

El validador captchavalidator no es de los implementados por Struts por lo que le diremos en


validators.xml cual es su clase Java:
< validator name = " captchavalidator " class = " prometeo . validator .
CaptchaValidator " > </ validator >

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 ) ;
}

Si tras enviar el formulario, el proceso de validacion no ha devuelto error alguno, se dispara la


clase UserAction, mediante su metodo register.
Las contrasenas no se guardaran en la base de datos en claro, por seguridad conviene codificarlos
de alguna forma. Utilizaremos para ello el algoritmo MD5 (Message-Digest Algorithm 5) 8 . Este
algoritmo es capaz de generar un codigo de 128 bits (resumen) para textos de cualquier longitud,
de forma practicamente unvoca.
Para codificar el password con MD5 no tenemos mas que aplicarle el metodo correspondiente de
la librera de Apache DigestUtils 9 :
8 RFC del MD5: http://www.ietf.org/rfc/rfc1321.txt
9 http://commons.apache.org/codec/apidocs/org/apache/commons/codec/digest/DigestUtils.html

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.

5.6.2. Control de Permisos. Acceso de usuarios


Una vez que el sistema tiene en la base de datos las credenciales de un usuario, este debera
ser capaz de autentificarse en la aplicacion. Se ofrecera al usuario un nuevo formulario donde se
le pida su nombre de usuario y contrasena y el sistema debera comprobar dichos datos con las
credenciales almacenadas. Si son correctas, se le cargara en memoria su Rol y el usuario pasara a
estar autentificado.
Dicho mecanismo es recurrente en casi todas las aplicaciones web personalizables, debido a lo
cual hay Frameworks que nos permiten implementar dicho funcionamiento de forma mas o menos
sencilla segun el nivel de complejidad de permisos que pretendamos aplicar. En nuestro caso, nos
bastara con dos roles, los supervisores que pueden acceder al panel de control, y el resto de usuarios
(autentificados o no) que pueden acceder a la web.

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.

5.6.3. Manejador de Usuarios


El framework de seguridad introduce un objeto en sesion cuando un usuario ha insertado correc-
tamente sus credenciales. Sin embargo nos interesara disponer con facilidad del objeto Usuarios
10 Una cookie es un fragmento de informacion que se almacena en el disco duro del visitante de una pagina web

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 ;
}

5.7. Representacion de datos. Widgets


La informacion se mostrara al usuario a traves de los widgets. La palabra widget se suele emplear
en el ambito informatico para referirse a mini-aplicaciones que son facilmente controlables por el
usuario. Se comentaran detenidamente tres widgets creados para la aplicacion: mapa, tabla y lnea
temporal; as como brevemente otros dos realizados aprovechando los anteriores: termometro e
histograma.
Un usuario accedera a una pagina desde la cual podra ver sus widgets. Puede haber varios widgets
del mismo tipo, y cada widget se configura por separado. Si el usuario esta registrado en la aplica-
cion se le ofrecera la posibilidad de guardar su disposicion de widgets as como sus configuraciones
particulares. Si el usuario entra anonimamente podra personalizarse igualmente la vista, pero los
cambios que realice a la vista por defecto no se almacenaran.
Realizar la programacion que permita esta serie de funcionalidades conlleva tener en cuenta dos
aspectos separados: la programacion del lado de servidor y la del lado del cliente. La programacion
del lado del cliente (realizada en JavaScript) se tendra que encargar de recoger todas las peticiones
del usuario, hacer las peticiones necesarias al servidor, y configurar la pagina en consecuencia. Por
ejemplo, si un usuario decide cerrar un widget, tendra que eliminar del codigo HTML su contenido
y enviar al servidor la peticion para que se almacene que no ha de mostrarse en futuras visitas el
citado widget en la configuracion del usuario. La programacion del lado de servidor recibira las
peticiones generadas con JavaScript, hara los cambios pertinentes en la base de datos y generara el
codigo de nuevos widgets cuando sea necesario crear uno nuevo.

Estandares para widgets

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.

5.7.1. Persistencia en la Base de Datos: la tabla Layouts


En la tabla Layouts almacenaremos la informacion necesaria para sobre la vista por defecto,
as como las configuraciones particulares de cada usuario.
Tendra una serie de campos:

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.

Wid : Indica el tipo de widget que contiene la instancia.


Lmeta: Campo de meta-datos. Su uso primordial sera almacenar la configuracion personali-
zada de la instancia.
Orden: Almacena un entero que permitira mostrar las instancias ordenadas de la forma que
desee el usuario.
12 http://www.w3.org/TR/widgets/

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 ) ;
}

5.7.4. JavaScript para controlar los Widgets


El codigo JavaScript que permitira el control interactivo de los widgets se incluira desde la plantilla
freemarker main.ftl. Las funciones que se incluyan en ocasiones realizaran peticiones (tanto POST
como GET) al servidor asncronamente. Para reutilizar el maximo codigo posible incluiremos una
librera de funciones utiles.
Dicha librera estara en el fichero util-ajax.js dentro del directorio de scripts. Las funciones mas
importantes seran toggleLayer y AJAXInteraction.
function toggleLayer ( whichLayer ) ;
function AJAXInteraction ( url , params , doWhile , callback ) ;

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 ) ;

widgetMinimize y widgetConfig operan unicamente en el lado del cliente, mostrando u ocultando


las capas apoyandose en la funcion toggleLayer. El resto de funciones realizaran una peticion
AJAX al servidor y en el lado del cliente mostraran el resultado. Al insertar un widget se recibe
el codigo del nuevo widget, por lo que habra que insertarlo en la pagina. En los casos de guardar
la configuracion, o cerrar un widget con insertar un mensaje informando sobre el resultado de la
operacion.

5.7.5. Un Widget generico


Los widgets pueden llegar a ser muy diferentes entre s, por lo que cada tipo de widget tendra una
Clase de Java propia. Cuando se carga la lista de widgets de un usuario, lo unico que sabemos
son los tipos de widgets que hay en cada instancia. Para poder instanciar la clase concreta de

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:

applyFreemarkerTemplate. Cada widget definira su plantilla de freemarker mediante un String.


Esta funcion cargara todas las propiedades que se definan en loadProperties y obtendra la
salida HTML.
loadProperties. Sera la funcion que se emplee para pasar todas las variables que necesite la
plantilla. En el BaseWidget se cargaran las variables comunes y las clases que extiendan a
BaseWidget podran extender esta clase y pasar as otras.

getLocalizedString. Dado que nuestra clase generica de widget no extiende el ActionSupport


de Struts, no disponemos de la funcion que nos devuelve textos internacionalizados. Esta
funcion lo suplira. Funciona comprobando que la clase que instancia un widget extienda
ActionSupport. En ese caso devolvera el texto correctamente.
stripHTMLTags. Elimina las etiquetas de cabecera de la pagina, dejando solo el contenido de
de la tag body. Ya que insertaremos el HTML de los widgets en una pagina correctamente
formada, no necesitamos las cabeceras de la pagina HTML que nos genera Freemarker por
defecto.

65
Extendiendo BaseWidget. Un Widget concreto

Cuando se instancia la clase de un Widget concreto tal y como se ha propuesto mediante la


funcion makeWidget, este dispone de unas propiedades ya inyectadas, as como de los manejadores
necesarios para obtener sus datos. Por ejemplo, todo widget tiene su identificador de instancia
y el widgetManager. Podremos sacar la configuracion concreta de la instancia mediante esos dos
objetos.
De forma similar, se cargaran a traves de la funcion loadProperties todas las variables que necesite
el Widget en concreto.
Todos los widgets definiran una clase render. Dicha funcion usara el metodo comun applyFree-
markerTemplate, para generar el HTML del Widget segun la plantilla que se le indique en FREE-
MARKER FILE. Como se ha comentado dicha funcion llama previamente a loadProperties para
disponer de todas las variables que necesite.
public String render ( WidgetContext widgetContext ) {
renderCache } a p pl yF r ee m ar ke r Te m pl at e ( widgetContext , FREEMARKER_FILE ) ;
return renderCache ;
}

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.

5.8. Widget de prediccion por Localidades


Un primer widget en el que podemos pensar para mostrar los datos de predicciones sera uno
que nos permitiese mostrar todas las variables que se han calculado para una estacion y fecha
concretas.

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 >

5.8.2. Entrada de datos


El formulario de configuracion dispondra de un calendario. La fecha por defecto (y maxima)
sera la de la ultima prediccion disponible, pudiendose escoger otras anteriores.
El calendario mostrado sera el de la librera de componentes de usuario de Yahoo 13 . Esta li-
brera permite insertar un calendario configurable de forma sencilla mediante JavaScript. Podemos
internacionalizarlo, especificarle unas fechas maximas y mnimas disponibles.
En la plantilla Freemarker del widget configuramos el calendario as:
13 Yahoo User Interface Library http://developer.yahoo.com/yui/

67
Figura 5.8: Configuracion del widget de prediccion por Localidades.

YAHOO . namespace (" Calendario$ { iwid }") ;


YAHOO . Calendario$ { iwid }. init = function () {
YAHOO . Calendario$ { iwid }. cal1 = new YAHOO . widget . Calendar (
" cal1 " ,
" widget - $ { iwid } - calendar - container " ,
{
maxdate : " $ { config_date }" ,
today : " $ { config_date }" ,
selected : " $ { config_date }" ,
pagedate : " $ { config_datePage }"
}
);
}

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.

Actualizando los selectores mediante 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 >

5.8.3. Generacion de la tabla de resultados


Con los datos del formulario, pasamos a buscar los datos que necesitamos para construir el re-
sultado. Este se mostraran en una tabla en la que cada columna es un da y en cada fila hay una
variable.
Para almacenar los datos de forma que podamos en la parte de vista facilmente construir una
tabla, generaremos un ArrayList [5] que contendra los resultados. Cada elemento del ArrayList
sera una fila de la tabla, concretamente sera un LinkedHashMap [5]. El LinkedHashMap contiene
pares de elementos, uno representa una clave y otro un valor. Se diferencia del HashMap en que el
Linked mantiene el orden de insercion de los elementos.
Las claves las utilizaremos para guardar el nombre de las columnas, en el primer caso sera la
cabecera de la columna de variables y en el resto la fecha para la que se ha calculado su posible
valor. Los valores contendran Objetos del tipo prometeo.model.VariableNumber, que contendra el
valor concreto de la prediccion, as como las unidades y el formato que se aplicara al mostrar dicho
numero.
Habra dos bucles anidados. Se recorreran las variables mediante un ciclo for y, para cada va-
riable, se recorreran los diferentes steps solicitados. Para cada combinacion de variable/step se
sacara la prediccion asociada. Para ello emplearemos el metodo apropiado del DAO de prediccio-
nes: getPredictionValueForStation. Si el valor devuelto es null, significara que dicha estacion no
contiene informacion para esa variable. En caso contrario, lo recogemos en la tabla con su formato
y unidades asociadas.
List < String > variables = prometeoDao . getVariables () ;
for ( Iterator it = variablesPosibles . iterator () ; it . hasNext () ;) {
VariablesNamed cadaVariable } ( VariablesNamed ) it . next () ;
LinkedHashMap tablaFila = new LinkedHashMap () ;
Boolean insertarFila = false ;
for ( Integer stepCont =0; stepCont < Integer . valueOf ( step ) ; stepCont ++) {
String stepContString = String . valueOf (( stepCont + 1) ) ;
Float value } prometeoDao . 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 ( cadaVariable .
getVnamebbdd () , fechaPred , stepCont + 1 , Integer . valueOf ( station ) ) ;
if ( value == null ) break ;
else {
VariableNumber vn = new VariableNumber () ;
vn . setValue ( value ) ;
vn . setFormat ( cadaVariable . getVformat () ) ;
vn . setUnit ( cadaVariable . getVunit () ) ;
tablaFila . put ( columnList . get ( stepCont + 1) , vn ) ;
insertarFila = true ;
}
if ( insertarFila ) tabla . add ( tablaFila ) ;
}

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 ;

5.9. Widget de Mapa


Dado que tenemos informacion sobre la localizacion geografica de las estaciones, podemos ofrecer
al usuario la informacion de las predicciones en un mapa. En el mismo mapa podremos ademas
mostrar informacion proveniente de ficheros NetCDF, gracias a la librera ncWMS.
Hay diversos servidores de mapas, pero, por su potencia y comunidad de usuarios, elegiremos los
de Google Maps.

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.

5.9.3. Creacion de los Marcadores


Los marcadores se mostraran en el mapa cuando se este relativamente cerca del suelo. Describire-
mos la logica necesaria en el servidor para generar los datos y visualizaciones de forma apropiada
para la API de Google Maps.

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 >

Conversion a resultado JSON

Primero se ha de resaltar que se ha empleado un tipo de resultado personalizado el JSONresult.


As, de forma transparente para retrieveMarkers, transformamos una lista de objetos de modelo
Marker, al formato JSON (JavaScript Object Notation).
Para ello, hemos definido en el struts.xml el tipo de resultado JSONresult y la clase a la que se
refiere:
< result - types >
< result - type name = " JSONresult " class = " prometeo . result . JSONResult " / >
</ result - types >

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 )

Siendo jsonModel el objeto donde guardamos la lista de Markers.


Xstream por defecto asigna a las colecciones un nombre segun el tipo de objeto que tengan sus ele-
mentos. En nuestro caso, el nombre del listado de markers resultante sera: prometeo.model.Marker.
Esto no es muy estetico y nos interesara que el JSON no mostrase informacion sobre el nombre del
15 http://xstream.codehaus.org/index.html

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.

Generacion de los Markers

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.

Figura 5.10: Marcador estandar de Google Maps.

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 ) ;

Figura 5.11: Ejemplo de marcador generado.

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.

Figura 5.14: Capa de marcadores de precipitacion para estaciones de la zona de Cataluna.

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.

Indicando a la API la forma de encontrar las imagenes

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.

5.9.5. Mostrando capas salida de ficheros NetCDF


La API de Google Maps nos permite mostrar varias capas. De forma similar a como se anadio la
informacion de predicciones a zooms lejanos, ofreceremos al usuario las salidas de ficheros NetCDF
ledas a traves de la librera ncWMS.
< action name = " capa - modelo " class = " prometeo . web . CapaModeloAction " method = "
getLayer " >
< 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 >

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 >

5.9.6. Generacion de las barras de colores


Siempre que se muestra informacion codificada en colores, conviene hacer notar cual ha sido
el criterio empleado. Mediante las barras de colores se informara de la correspondencia entre
magnitudes de variable y colores.
La barra de colores de cada variable sera una imagen que se generara mediante Java, de forma
similar a como creamos los iconos de los marcadores. Su parametro sera el nombre de la variable.
Recogera los datos de la variable (donde se indicaran los colores maximos y mnimos o el listado
de valores de umbrales y colores) y lo mostrara en una imagen.
Si el coloreado es continuo (los marcadores se colorean mediante ColoreaContinuo), mostraremos
un gradiente. Mostraremos una serie de textos ademas mostrar una serie de valores ademas del
valor maximo y mnimo de la variable. El gradiente lo creamos de forma sencilla indicando los dos
colores lmite as como los puntos, en nuestro caso los extremos del rectangulo.
Con coloreado discreto, mostraremos un pequeno cuadro de color solido por cada elemento de la
lista de colores, as como su umbral asociado.

Figura 5.17: Colorbars de ejemplo resultado de coloreados continuo y discreto.

5.10. Widget de lnea temporal de Observaciones


Definiremos finalmente un widget que pueda mostrarnos una representacion de datos a lo largo
del tiempo. Concretamente lo emplearemos para mostrar observaciones.
De forma similar al widget de prediccion por localidades, habra un formulario de configuracion
dinamico con las variables y estaciones segun la fuente de datos. Tambien dispondra de un calen-
dario de la YUI para seleccionar el rango de tiempos.
Este widget se caracteriza por usar una librera JavaScript para la visualizacion de graficas tem-
porales, llamada Simile Timeplot 17 . La mayora de las libreras para visualizacion de graficas
estan implementadas sobre Adobe Flash. La de Simile es especialmente interesante porque emplea
unicamente JavaScript.

5.10.1. Arquitectura particular


La librera empleada necesita recibir informacion en formato de texto plano, teniendo en cada
lnea un punto de la grafica. El formato de la lnea es:
17 http://www.simile-widgets.org/timeplot/

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 () ;

SimpleDateFormat sdf = new SimpleDateFormat ( formatoFecha ) ;


DecimalFormat df = new DecimalFormat () ;
df . applyPattern ( formatoValor ) ;

ValueStack valueStack = invocation . getStack () ;


List < Observation > observations = ( List < Observation >) valueStack .
findValue ( " observations " ) ;
if ( observations == null ) return ;
for ( Iterator < Observation > it = observations . iterator () ; it . hasNext ()
;) {
Observation obs = it . next () ;
String value = " " ;
if (! Float . isNaN ( obs . getValue () ) ) value = df . format ( obs . getValue () )
;
else continue ;
responseStream . println ( sdf . format ( obs . getFecha () ) + " " + value ) ;
}

}
}

A continuacion se muestra el formato necesario para mostrar los datos de observaciones:


< action name = " retrieveobservations " class = " prometeo . web . ObservationsAction "
>
< result name = " success " type = " TimelineResult " >
< param name = " formatoFecha " > yyyy - MM - dd </ param >
< param name = " formatoValor " > ### ,##0.00 </ param >
</ result >
</ action >

Ademas es necesario recibir asncronamente la informacion sobre las variables seleccionadas de


cara a construir la grafica. En concreto se pasaran el nombre a modo de identificador, as como
valores maximos y mnimos.
private String key ;
private String valor_max ;
private String valor_min ;

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 >

5.10.2. Cargando las variables dinamicamente mediante JavaScript


Queremos que cada vez que se seleccione una o varias variables del selector de variables del
formulario aparezcan en la grafica. Haremos una funcion JavaScript que sera llamada cada vez que
haya algun cambio en el formulario de configuracion que se encargara de realizar las peticiones
necesarias a retrieveobservations y configurar la librera para que muestre dichos datos.

Figura 5.18: Ejemplo de variable visualizada mediante Simile Timeplot.

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
// ...

// sacar info de variables solicitadas ( AJAX )


var url = " retrievevarinfo . html ? bid ="+ parametros [ bid ]+"& variables ="+
variablesStr ;
var ajax = new AJAXInteraction ( url , null , null ,
sacaInfo = function ( doc ) {
var jsonData = eval ( ( + doc + ) ) ; // Parse the JSON document
this . drawTimeline ( iwid , jsonData . list . VarData , variables , query ) ;
}
);
ajax . doGet () ;
}

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 ] ,
}) ;

// crear variable dinamicamente plotInfo


var i , geometry1 , eventSource1 , plotInfo1 , timeplot ;
var eventSource = new Array () ;
var plotInfo = new Array () ;
geometry1 = new Timeplot . DefaultValueGeometry ({
gridColor : "#000000" ,
axisLabelsPlacement : " left "
}) ;
for ( i =0; i < variables . length ; i ++) {
eventSource1 = new Timeplot . DefaultEventSource () ;
dataSource1 = new Timeplot . ColumnSource ( eventSource1 ,1) ;
plotInfo1 = Timeplot . createPlotInfo ({
id : variableData [ i ][ key ] ,
dataSource : dataSource1 ,
timeGeometry : timeGeometry ,
valueGeometry : geometry1 ,
lineColor : timeline_var [ colors ][ color_index ] ,
showValues : true
})
eventSource . push ( eventSource1 ) ;
plotInfo . push ( plotInfo1 ) ;
}

// creo timeline y cargo los datos de observaciones


timeplot = Timeplot . create ( $ (" widget - timeline -"+ iwid +" - grafica - container
") , plotInfo ) ;
for ( var i =0; i < variables . length ; i ++) {
timeplot . loadText (" retrieveobservations . html ? variable ="+ variables [ i
]+"&"+ query , " " , eventSource [ i ]) ;
}
}

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.

5.11.1. Widget de Termometro


Su mision sera mostrar predicciones de temperatura para una estacion dada en una grafica en
forma de termometro. Al acceder a datos de predicciones para una estacion concreta, su diseno es
muy similar al widget de prediccion por localidades.
A diferencia del widget de localidades no recorreremos todas las variables disponibles para la
estacion, sino solo aquellas que muestran informacion de temperatura. Por ejemplo, podra haber
datos de temperatura mnima y temperatura maxima. Para cada variable encontrada se realizara un
dibujo de un termometro, empleando la clase ThermometerPlot proporcionado por JFreeChart.

Figura 5.19: Ejemplo de visualizacion del widget de termometro.

5.11.2. Widget de Histograma


Este widget sera muy similar al widget de lnea temporal, pues accede al mismo tipo de fuentes
de datos: tipo de observaciones. Se encargara de crear una representacion en forma de histograma.
JFreechart es capaz de crear este tipo de graficos gracias al metodo ChartFactory.createHistogram.
Se le pasa como argumento una serie de datos as como el numero de grupos (bins) en los que los
agrupara.

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

Se decidio realizar la interfaz de la aplicacion basandose en la Web ya que es una tendencia


bastante extendida en la actualidad. Se confa en dejar del lado del Servidor buena parte de la logica,
y que los clientes puedan acceder a ella facilmente, sin necesidad de instalar programas adicionales,
mediante el empleo de un simple navegador. De esta forma es indiferente el Sistema Operativo
que use el cliente y no necesita demasiados conocimientos. Los navegadores modernos permiten
desplegar contenidos dinamicos e interactivos, lo que, sumado a la posibilidad de personalizacion
introducida por nuestra aplicacion, nos lleva a pensar en la aplicacion Web como una forma de
brindar una grata experiencia para el usuario.
La solucion alcanzada cumple las expectativas propuestas y llega un poco mas alla de las soluciones
similares existentes. Al plantear una interfaz basada en widgets, disponemos de una gran flexibilidad
para mostrar datos muy diferentes entre s, con diversidad de herramientas. Los widgets creados
ilustran la potencia del sistema, y fueron realizados empleando herramientas gratuitas como Google
Maps o JFreeChart. La aplicacion es ademas facilmente extensible en caso de que aparezcan nuevas
necesidades: nuevas fuentes de datos y nuevas formas de representacion.
Se ha centrado la interfaz en el usuario, procurando que sea simple de usar, siguiendo los canones
de la Web 2.0. Los widgets pueden ser dispuestos en la pagina a voluntad del visitante, adaptandose
a sus necesidades. Si el usuario esta registrado podra ademas salvar el layout escogido as como la
configuracion de cada widget en la base de datos, de forma que no pierda tiempo en futuras visitas.
Se ha tenido en cuenta la seguridad de forma que los administradores del sitio puedan facilmente
controlar la informacion disponible. Podran determinar que perfiles de usuario tienen acceso a
que fuentes de datos, o a que widgets.
A lo largo del proyecto se ha hecho hincapie en las herramientas empleadas para el desarrollo, la
mayora software libre, ya que han permitido realizar el proyecto en un tiempo razonable. Gracias
a la extensa comunidad de desarrolladores en el ambito de Java, hemos podido encontrar diversos
frameworks que se adaptaban perfectamente a las necesidades de la aplicacion.

6.1. Lneas futuras


La forma de definir la estructura de la aplicacion, basada en manejadores genericos brinda la opor-
tunidad de incluir mas fuentes de datos de forma mas o menos sencilla, introduciendo las clases de
acceso (DAOs) que necesiten. De esta forma, se podran anadir nuevas predicciones meteorologicas
sin necesidad de cambiar la estructura de la aplicacion.
Algunos de los widgets introducidos hacen uso de extensas fuentes de datos, que pueden llevar

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

[1] Donald Brown, Chad Michael Davis, and Scott Stanlick.


Struts 2 in Action
Manning, 2008.
[2] Struts 2 Documentation
2009 http://struts.apache.org/2.x/docs/home.html
[3] Freemarker Documentation
2009 http://freemarker.org/docs/index.html
[4] Spring Security Documentation
2009 http://static.springsource.org/spring-security/site/reference/html/springsecurity.html
[5] JavaTM 2 Platform, Standard Edition, API Specification
2009 http://java.sun.com/j2se/1.4.2/docs/api/index.html
[6] JSTL Documentation
2009 http://java.sun.com/products/jsp/jstl/reference/docs/index.html
[7] Display Tag Library. Tag Reference
2009 http://displaytag.sourceforge.net/10/tagreference-displaytag-12.html
[8] Trail: Internationalization
2009 http://java.sun.com/docs/books/tutorial/i18n/index.html
[9] Gaurav Patel Struts2 framwork
1 Oct. 2008. Knol. A unit of knowledge
2009 http://knol.google.com/k/gaurav-patel/struts2-framwork
[10] Caching the result of methods using Spring and EHCache
11 Nov 2004 Spring Discuss
2009 http://opensource.atlassian.com/confluence/spring/x/WgI
[11] Iverson, Will Introduction to SiteMesh
3 Nov 2004 Java Today
2009 http://today.java.net/pub/a/today/2004/03/11/sitemesh.html
[12] ncWMS
2009 http://www.resc.rdg.ac.uk/trac/ncWMS/

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

7.1. Instalacion de aplicaciones


Se relatara a continuacion el proceso de instalacion de los programas fundamentales empleados
en el desarrollo del proyecto. El sistema operativo empleado fue Ubuntu Linux 8.10.

7.1.1. Instalacion de Eclipse con Web Tools Platform (WTP)


Nos descargamos un paquete que contenga Eclipse con los plugins para desarrollo web ya incor-
porados desde http://www.eclipse.org/downloads/ , concretamente desde Eclipse IDE for Java EE
Developers.
En nuestro caso emplearemos el llamado Eclipse Ganymede (Version de la plataforma: 3.4.0) y
pasaremos a descargar la version apropiada para nuestro SO: eclipse-jee-ganymede-linux-gtk.tar.
Descomprimido, ya se puede ejecutar y la primera vez tendremos que asignarle un directorio de
trabajo (workspace).
Tambien se pueden descargar los plugins WTP independientemente del Eclipse y aprovechar una
instalacion anterior. [19]
Se define un nuevo proyecto como New - Other - Web - Dynamic Web Proyect. Le configuramos
para que emplee Apache Tomcat 6.
Configuramos el proyecto para que use como JRE (alternate JRE): la version JDK de java
jdk1.6.0 06, que se encuentra en el directorio /usr/lib/jvm/jdk1.6.0 06 tras haberse descargado
desde la web oficial de java el archivo binario jdk-6u6-linux-i586.bin. 1

7.1.2. Plug-In para control de versiones Subversion


Subclipse es un Plug-in de Eclipse que nos permite anadir el controlador de versiones SVN en
nuestros proyectos. Para instalarlo accedemos al menu Help - Software Updates. En la pestana
available software anadimos el sitio correspondiente 2 y tras seleccionarlo se procede a la instalacion.
1 Descarga del JDK: http://java.sun.com/javase/downloads/index.jsp y pulsar en Get the JDK download
2 http://subclipse.tigris.org/update 1.4.x

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.

7.1.4. Instalacion de Maven y configuracion de las variables de entorno


Maven 3 es una herramienta para la gestion y automatizacion de proyectos Java. Su funcionalidad
es parecida a Apache Ant de manera que permite compilar, ejecutar test o realizar distribuciones
pero con la diferencia que trata de forma automatica las dependencias del proyecto. Una de las
mas importantes caractersticas es su actualizacion en lnea mediante servidores con repositorios
de libreras (jars).
Para configurar por primera vez la aplicacion con todas sus libreras actualizadas nos vendra bien
Maven. Ademas emplearemos como aplicacion base Appfuse Light, que igualmente lo emplea.
Descargaremos la ultima version de Maven y la descomprimiremos de forma que quede lista para
su uso.
Para el correcto funcionamiento de Tomcat y Maven hemos de definir las variables de entorno:

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

Maven descargara automaticamente todas las dependencias y configurara un proyecto Eclipse.


Tras ello ya podremos importar al eclipse dicho directorio.
Ahora hemos de configurar en Eclipse la variable M2 REPO que apunte al repositorio Maven local.
Lo hacemos a traves de Window - Preferences - Java - Build path - Classpath variables y anade una
de nombre M2 REPO y de contenido (por defecto) /home/[nombre usuario Linux]/.m2/repository

7.1.6. Integracion de los demas frameworks en la aplicacion


Sitemesh

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

Siguiendo los pasos recomendados, nos descargaremos la aplicacion de ejemplo y la adaptaremos


a nuestras necesidades:
4 http://appfuse.org
5 https://appfuse-light.dev.java.net/

96
Instalamos el jar del Spring Security en la carpeta de libreras de nuestra aplicacion.

Copiamos el applicationContext-acegi-security.xml a nuestra carpeta WEB-INF. Se cargara au-


tomaticamente al arrancar la aplicacion ya que el web.xml de WEB-INF esta configurado
para cargar los archivos que comiencen de esa forma:

< context - param >


< param - name > co ntex tConf igLo cati on </ param - name >
< param - value >/ WEB - INF / applicationContext *. xml </ param - value >
</ context - param >

En el web.xml, agregamos los filtros que se encargaran de recoger los metodos propios del
Spring Security

< filter >


< filter - name > Acegi Filter Chain Proxy </ filter - name >
< filter - class > org . acegisecurity . util . FilterToBeanProxy </ filter - class >
< init - param >
< param - name > targetClass </ param - name >
< param - value > org . acegisecurity . util . FilterChainProxy </ param - value >
</ init - param >
</ filter >
< filter - mapping >
< filter - name > Acegi Filter Chain Proxy </ filter - name >
<url - pattern > /* </ url - pattern >
</ filter - mapping >

97

Вам также может понравиться