Download Optimización - Read the Docs

Document related concepts
no text concepts found
Transcript
curso_desarrollo Documentation
Release 1
Fernando González
January 07, 2016
Contents
1
Publicación de versiones
1
2
Prerrequisitos
2
3
JQuery
2
4
OpenLayers
2
5
RequireJS
2
6
Arquitectura
6.1 Estructura del repositorio . . . . . . . . .
Cliente ligero . . . . . . . . . . . . . . .
Plugins, cargador de plugins, aplicaciones
Estructura del código fuente . . . . . . .
Cargador de plugins . . . . . . . . . . . .
Despliegue . . . . . . . . . . . . . . . .
Optimización . . . . . . . . . . . . . . .
Programación de servicios . . . . . . . .
6.2 error-management . . . . . . . . . . . . .
6.3 context-attributes . . . . . . . . . . . . .
6.4 request-attributes . . . . . . . . . . . . .
6.5 RequireJS . . . . . . . . . . . . . . . . .
Módulos RequireJS . . . . . . . . . . . .
Árbol de dependencias . . . . . . . . . .
Dependencias de módulos no-RequireJS .
Valores de retorno . . . . . . . . . . . . .
Funciones públicas . . . . . . . . . . . .
Funciones privadas . . . . . . . . . . . .
Plantilla módulo . . . . . . . . . . . . . .
6.6 Patrón de diseño message-bus . . . .
6.7 Arquitectura servidor . . . . . . . . . . .
Introducción a servlet 3.0 . . . . . . . . .
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
3
3
3
4
6
6
6
8
9
9
9
9
9
9
10
11
11
12
12
13
15
15
Configuración de los plugins
7.1 Modificación de la configuración en tiempo de ejecución . . . . . . . . . . . . . . . . . . . . . . . .
7.2 Modificación de la configuración por programación . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
16
17
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8
Módulos importantes
8.1 Message-bus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
17
9
Referencia mensajes
9.1 modules-loaded . . . . . . . . .
9.2 before-adding-layers . . . . . .
9.3 layers-loaded . . . . . . . . . .
9.4 ajax . . . . . . . . . . . . . . .
9.5 error . . . . . . . . . . . . . . .
9.6 info-features . . . . . . . . . . .
9.7 zoom-in . . . . . . . . . . . . .
9.8 zoom-out . . . . . . . . . . . .
9.9 zoom-to . . . . . . . . . . . . .
9.10 initial-zoom . . . . . . . . . . .
9.11 set-default-exclusive-control . .
9.12 activate-default-exclusive-control
9.13 activate-exclusive-control . . . .
9.14 highlight-feature . . . . . . . .
9.15 clear-highlighted-features . . . .
9.16 add-group . . . . . . . . . . . .
9.17 add-layer . . . . . . . . . . . .
9.18 layer-visibility . . . . . . . . . .
9.19 time-slider.selection . . . . . . .
9.20 layer-time-slider.selection . . . .
9.21 layer-timestamp-selected . . . .
9.22 toggle-legend . . . . . . . . . .
9.23 register-layer-action . . . . . . .
9.24 register-group-action . . . . . .
9.25 show-layer-panel . . . . . . . .
9.26 show-info . . . . . . . . . . . .
9.27 show-layer-info . . . . . . . . .
9.28 show-group-info . . . . . . . . .
9.29 show-wait-mask . . . . . . . . .
9.30 hide-wait-mask . . . . . . . . .
9.31 activate-feedback . . . . . . . .
9.32 deactivate-feedback . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
18
18
18
18
18
19
19
19
20
20
20
20
21
21
21
21
22
22
23
23
23
24
24
24
25
25
25
26
26
26
26
26
27
10 Temas avanzados
10.1 Secuencia de inicio de la aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
27
11 Compilación del proyecto
11.1 Obtención de los fuentes . . . . . . . . . . . .
11.2 Instalación de los fuentes en eclipse . . . . . .
11.3 Ejecución del portal en tomcat desde Eclipse . .
11.4 Generación del unredd-portal.war . . . . . . .
11.5 Generación del unredd-portal.war desde eclipse
11.6 Particularidades de la construcción del proyecto
Optimización . . . . . . . . . . . . . . . . . .
Tests de integración . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
27
27
28
29
29
29
30
30
31
12 Ejecución de los tests de integración
12.1 Servicio de base de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12.2 Configuración del plugin Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12.3 Ejecución de los tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
31
31
31
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
13 Manuales
13.1 Hello World . . . . . . . . . . . . . . . . . . . . . . .
13.2 Añadir elementos a la interfaz . . . . . . . . . . . . .
13.3 Añadir una botonera . . . . . . . . . . . . . . . . . .
13.4 Manejo de eventos . . . . . . . . . . . . . . . . . . .
1. Captura de eventos y recogida de información . . .
2. Ejecución del módulo . . . . . . . . . . . . . . . .
Patrón habitual . . . . . . . . . . . . . . . . . . . . .
13.5 Posición del mapa . . . . . . . . . . . . . . . . . . . .
13.6 Lectura de parámetros URL . . . . . . . . . . . . . . .
13.7 Servicio hola mundo . . . . . . . . . . . . . . . . . .
13.8 Servicio configuración (1 de 2) . . . . . . . . . . . . .
13.9 Servicio configuración (2 de 2) . . . . . . . . . . . . .
Comunicación con el cliente . . . . . . . . . . . . . .
Decodificación en el cliente . . . . . . . . . . . . . . .
13.10Conexión a base de datos . . . . . . . . . . . . . . . .
Conexión a base de datos en Java . . . . . . . . . . . .
Conexión a base de datos desde una aplicación web . .
Configuración conexión en el descriptor de despliegue
La clase DBUtils . . . . . . . . . . . . . . . . . . . .
Conexiones existentes . . . . . . . . . . . . . . . . . .
13.11Cómo crear un nuevo plugin . . . . . . . . . . . . . .
Creación del proyecto para el plugin . . . . . . . . . .
Configuración en Eclipse . . . . . . . . . . . . . . . .
Desarrollo de un módulo . . . . . . . . . . . . . . . .
Reutilización del módulo . . . . . . . . . . . . . . . .
13.12Cómo crear una nueva aplicación . . . . . . . . . . . .
Creación del proyecto . . . . . . . . . . . . . . . . . .
Selección de los plugins que componen la aplicación .
Ejecución de la aplicación desde eclipse . . . . . . . .
Empaquetado . . . . . . . . . . . . . . . . . . . . . .
Empaquetado con optimización . . . . . . . . . . . . .
13.13Cómo dar licencia libre a un plugin . . . . . . . . . . .
Elegir una licencia libre . . . . . . . . . . . . . . . . .
Aplicar la licencia a nuestro proyecto . . . . . . . . .
13.14Publicación de un plugin en un repositorio Maven . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
32
32
32
33
34
34
34
35
35
36
36
38
39
42
43
44
44
44
45
47
47
48
49
50
51
52
53
53
53
55
56
58
60
60
61
66
Contents:
1 Publicación de versiones
Este proceso se lanzará cada tres meses.
1. Avisar un par de semanas antes de la publicación. Aunque la fecha esté acordada es una buena práctica avisar.
2. Creación de la rama para la versión en el repositorio:
git checkout -b release-<VERSION>
mvn versions:set
mvn versions:commit
git commit -am "Bumped version numbers"
Donde la versión tiene la forma x.y.z-SNAPSHOT:
• x se incrementa si se rompe la API o el formato del directorio de configuración
• y se incrementa cuando se incorporan nuevas funcionalidades
• x se incrementa para parches menores
La rama se sacará de la última revisión “estable” en la rama principal.
3. Añadimos lista de mejoras a un fichero CHANGELOG. Esto se hace repasando commits desde una fecha determinada, poniendo especial cuidado en inventariar los cambios de API si es una versión mayor.
4. Cuando la rama está lista se elimina el SNAPSHOT de los pom:
mvn versions:set
mvn versions:commit
git commit -am "Bumped version numbers"
Se genera un .war y se sube al redmine.
En la rama principal se deberá actualizar el número de versión también. De la forma habitual:
mvn versions:set
mvn versions:commit
git commit -am "Bumped version numbers"
5. Se anuncia en la lista incluyendo el changelog y un link al nuevo war.
2 Prerrequisitos
La documentación tecnica parte de la base de que hay conocimiento sobre:
• maquetación web con HTML y CSS
• los lenguajes Java y Javascript
• desarrollo de Java Servlets
• jQuery básico
• OpenLayers
3 JQuery
JQuery se puede definir como una librería de utilidad que nos ayuda a realizar las siguientes tareas:
• Manipulación del modelo de objetos del documento HTML
• Communicación AJAX con el servidor
• Communicación entre módulos mediante eventos
Ejercicios
4 OpenLayers
OpenLayers es una librería que permite la visualizacion e interacción con mapas en navegadores web.
Ejercicios
5 RequireJS
RequireJS es un cargador de módulos asíncrono. En el contexto del portal se utiliza para cargar los distintos módulos
que componen el mismo.
Ejercicios
6 Arquitectura
El portal es una aplicación cliente/servidor, en la que el cliente está programado mediante módulos Javascript que realizan distintas operaciones. Para realizar estas operaciones, el cliente accede a distintos servicios realizando llamadas
asíncronas a servicios que devuelven documentos XML, JSON, etc. con la información necesaria.
El lado del servidor se basa en la especificación Servlets 3 de Java. A través de dicha especificación se pueden
implementar los servicios que devuelven los documentos XML, JSON, etc. que permitirán a los módulos Javascript
desarrollar su funcionalidad.
Warning: Aunque la parte servidora se implementa en Java, desde el punto de vista técnico no es estrictamente
necesario utilizar Java para implementar los servicios que nos interesen. Los módulos Javascript consumen los
servicios a través del protocolo HTTP, que es transparente a los detalles de implementación de los servicios consumidos. En efecto, es posible desplegar una aplicación con la parte cliente accediendo a un conjunto de servicios
en PHP, por ejemplo.
Sin embargo, aunque la parte cliente acceda a servicios implementados en otro idioma, el sistema de plugins está
implementado en Java, como veremos a continuación. Por lo que Java sigue siendo un requisito en el sistema
donde se despliegue la aplicación.
A continuación se presenta el caso particular del portal (Estructura del repositorio). Se termina presentando la tecnología y patrones usados en el cliente (RequireJS, Patrón de diseño message-bus) así como la especificación Servlet
3 de Java usada en el cliente (Arquitectura servidor).
6.1 Estructura del repositorio
Cliente ligero
El cliente Javascript es una aplicación modular que se comunica mediante llamadas asíncronas con los servicios web.
Desde el punto de vista del navegador, la aplicación tiene la siguiente estructura:
unredd-portal
|- modules/
|- jslib/
|- styles/
|- indicators
|- ...
\- index.html
->
->
->
->
->
->
RequireJS modulos y sus estilos
Librerías Javascript usadas por los módulos: OpenLayers, RequireJS, etc.
Hojas CSS generales (de JQuery UI, etc.)
Devuelve una lista con información de los indicadores de un objeto en una capa
Otros servicios
Documento HTML de la aplicación
Así, si tubiéramos que añadir una nueva funcionalidad en el cliente, tendríamos que meter los módulos en el directorio
modules, las hojas de estilos en styles o en modules y las librerías que se utilicen en jslib.
Sin embargo, repartir todos estos ficheros en distintos directorios complicaría el proceso de añadir y quitar plugins a
la aplicación, por lo que el código fuente está organizado tratando de agrupar todos esos ficheros por funcionalidad en
“plugins”.
Plugins, cargador de plugins, aplicaciones
Así, para la parte cliente agruparemos los HTML, CSS y Javascript necesarios para implementar las funcionalidades
que nos interesen en artefactos que llamaremos plugins.
Para la aplicación que queremos desarrollar, habrá que utilizar uno o más plugins, que tendrán que ser cargados
mediante el cargador de plugins.
Por último, tendremos que desplegar una aplicación, diciéndole al cargador de plugins qué plugins queremos incluir
en el resultado final
Así, una aplicación incluirá uno o más plugins que serán cargados mediante el cargador de plugins.
Estructura del código fuente
¿Cómo están estructurados los plugins, el cargador de plugins y las aplicaciones en el código fuente?
La aplicación es implementada por varios proyectos Java estructurados de la siguiente manera:
portal
|- core/
|- base/
|- feedback/
|- layer-time-sliders/
|- time-slider/
|- language-buttons/
|- geoexplorer-reader/
\- demo/
->
->
->
->
->
->
->
->
Librería que contiene el cargador de plugins y algunas funcionalidades bás
Plugin que contiene la funcionalidad básica de la aplicación: árbol de cap
Plugin que contiene la funcionalidad de feedback
Plugin que muestra una barra temporal por cada capa, para cambiar la insta
Plugin que muestra la barra temporal que se instala en la barra de herrami
Plugin que muestra los botones para cambiar de idioma.
Plugin que lee una base de datos de GeoExplorer para añadir las capas al m
Aplicación incluye todos los plugins anteriores.
El proyecto base contendrá los módulos RequireJS, librerías Javascript y estilos CSS necesarios para tener todas las
funcionalidades del portal, mientras que el proyecto demo especificará de alguna manera que quiere incluir base.
Estructura por defecto Maven
Todos los proyectos del portal utilizan Maven como herramienta de compilación, adaptándose a la estructura por
defecto que por convención tienen los proyectos Maven:
• src/main/java: Código fuente Java
• src/main/resources: Recursos usados por el código Java, componentes de la parte cliente (CSS, módulos y
librerías Javascript, etc.)
• src/test/java y src/test/resources: Tests automatizados
• pom.xml: Descriptor del proyecto Maven, donde se definen las dependencias entre proyectos.
Adicionalmente a estos directorios, las aplicaciones como demo incorporan un directorio adicional:
• src/main/webapp: Raíz de la aplicación web
Todos los recursos que se sitúen aquí se ofrecerán via HTTP en la raíz de la aplicación por lo que es el lugar ideal
para los contenidos estáticos y específicos de la aplicación. Además, incluye el directorio WEB-INF específico de
aplicaciones Java, con el descriptor de despliegue web.xml
Estructura proyectos plugin
Los proyectos plugin constan de los siguientes artefactos:
Desarrollos parte cliente Si los proyectos de los que estamos hablando son Java ¿cómo se incluyen artefactos del
cliente (módulos RequireJS, CSS, etc.)?
Las funcionalidades para la parte cliente se encuentran en el directorio nfms dentro de src/main/resources.
Estas funcionalidades consisten en módulos RequireJS, hojas de estilo CSS, librerías Javascript, etc. organizados
siguiendo esta estructura:
nfms
|- xxx-conf.json (descriptor del plugin. xxx es el nombre del plugin. E.g.: base-conf.json)
|- modules/ (módulos RequireJS)
|- jslib/ (librerías Javascript utilizadas)
\- styles/ (hojas de estilo CSS)
Este directorio se encuentra en src/main/resources porque Maven por defecto incluirá todo lo que haya ahí en
el JAR que genere al empaquetar.
Por ejemplo, en el proyecto base existe el directorio src/main/resources que contiene
nfms/modules/layer-list.js y nfms/jslib/OpenLayers/OpenLayers.unredd.js, entre
otros. Cuando Maven genere el JAR, el directorio nfms aparecerá en la raíz de los contenidos del JAR.
Descriptor parte cliente Es un fichero compuesto por el nombre del plugin y “-conf.json” que reside en la raíz del
directorio nfms y que contiene información descriptiva sobre el plugin, como la configuracion por defecto de los
plugins (ver Configuración de los plugins) o las librerías de terceros que utiliza el plugin y sus dependencias. Esta
última información es necesaria para que RequireJS cargue las librerías en el orden correcto.
El formato del fichero es el siguiente:
{
"default-conf" : {
"<nombre-modulo>" : <configuracion-por-defecto-modulo>
...
},
"requirejs": {
"paths" : {
"<id-libreria>" : "<ruta relativa a 'modules'>",
...
},
"shim" : {
"<id-libreria>" : [ "<id-dependencia1>", "<id-dependencia2>", ... ],
...
},
}
}
Ejemplo:
{
"default-conf" : {
"banner" : {
"hide" : false
}
},
"requirejs": {
"paths" : {
"jquery-ui" : "../jslib/jquery-ui-1.10.4.custom",
"fancy-box": "../jslib/jquery.fancybox.pack",
"openlayers": "../jslib/OpenLayers/OpenLayers.unredd",
"mustache": "../jslib/jquery.mustache"
},
"shim" : {
"fancy-box": [ "jquery" ],
"mustache": [ "jquery" ]
},
}
}
Parte servidora El descriptor de la parte servidora es META-INF/web-fragment.xml y se encuentra en
src/main/resources. Sigue el estándar Servlet3 de Java y contiene referencia a las clases Java que implementan
los servicios en él declarados.
La implementación de los servicios estará en src/main/java.
Estructura proyectos aplicación
Los proyectos aplicación constan de los siguientes artefactos.
TODO
Cargador de plugins
Para desplegar la aplicación se genera un WAR (Web application ARchive) que contendrá los ficheros JAR
pertenecientes a los plugins y sus dependencias.
Cuando este WAR se despliega y se inicia la aplicación, se analizan todos los JARs existentes dentro del WAR en
busca de módulos RequireJS, estilos y librerías externas.
• los paquetes modules y styles son escaneados en busca de módulos javascript y estilos:
nfms
|- xxx-conf.json
|- modules/ (escaneado en busca de .js y .css)
|- jslib/
\- styles/ (escaneado en busca de .css)
De esta manera, cualquier fichero .css existente en cualquier de los dos paquetes será importado al cargar la
aplicación. Igualmente, todo fichero .js existente en modules será cargado inicialmente por RequireJS al
iniciar la aplicación.
• el descriptor del plugin es analizado.
Tras este proceso, todos estos recursos encontrados serán accesibles via HTTP.
Despliegue
Como visto en el punto Cargador de plugins, todos los JARs incluídos en la aplicación son analizados en busca de
módulos, librerías, estilos, etc. Así, para componer una aplicación que incluya los plugins que nos interesan basta con
especificar en el pom.xml la dependencia al proyecto del plugin.
Cuando este proyecto es incluido como dependencia en un proyecto, por ejemplo demo, aparecerá como JAR dentro
del WAR y sus contenidos serán analizados y accesibles via HTTP.
Optimización
Durante el proceso de empaquetado de una aplicación como fichero WAR se realiza un proceso de optimización de las
hojas de estilos CSS y el código Javascript.
Este proceso consiste en la generación de dos recursos optimizados para estilos CSS y código Javascript en el directorio
optimized del espacio web de dicha aplicación, es decir, en src/main/webapp/.
Estos dos ficheros contienen respectivamente todos los estilos CSS y todo el código Javascript proporcionado por todos
los plugins incluidos en la aplicación. Además el contenido está comprimido para que la descarga desde el navegador
sea más ligera.
Así, cuando desplegamos el fichero WAR de la aplicación, éste contiene tanto las hojas de estilo y módulos Javascript
individuales como los dos ficheros optimizados. Para seleccionar el modo optimizado basta con poner la variable de
entorno MINIFIED_JS=true.
A continuación podemos observar lo que nos arroja el fichero index.html en cada caso. Primero sin optimizar:
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="icon" type="image/png" href="static/img/favicon.png">
<link
<link
<link
<link
<link
<link
<link
<link
<link
<link
<link
<link
rel="stylesheet"
rel="stylesheet"
rel="stylesheet"
rel="stylesheet"
rel="stylesheet"
rel="stylesheet"
rel="stylesheet"
rel="stylesheet"
rel="stylesheet"
rel="stylesheet"
rel="stylesheet"
rel="stylesheet"
href="modules/banner.css">
href="modules/info-dialog.css">
href="modules/layer-list.css">
href="modules/layout.css">
href="modules/legend-button.css">
href="modules/legend-panel.css">
href="modules/scale.css">
href="modules/time-slider.css">
href="modules/toolbar.css">
href="modules/zoom-bar.css">
href="styles/jquery-ui-1.10.3.custom.css">
href="styles/jquery.fancybox.css">
<script src="config.js"></script>
<!--<script src="js/require.js" data-main="modules/main"></script>-->
<script src="jslib/require.js"></script>
<script>
require.config({
paths: {
"main": "modules/main"
}
});
require(["main"]);
</script>
<link rel="stylesheet" href="static/overrides.css"/>
</head>
<body>
</body>
</html>
Y ahora con la variable MINIFIED_JS = true:
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="icon" type="image/png" href="static/img/favicon.png">
<link rel="stylesheet" href="optimized/portal-style.css">
<script src="config.js"></script>
<!--<script src="js/require.js" data-main="modules/main"></script>-->
<script src="jslib/require.js"></script>
<script>
require.config({
paths: {
"main": "optimized/portal"
}
});
require(["main"]);
</script>
<link rel="stylesheet" href="static/overrides.css"/>
</head>
<body>
</body>
</html>
Podemos observar cómo en lugar de cargarse todos los CSS de forma separada, se carga un único CSS en
optimized/portal y que el modulo main se mapea a optimized/portal.js
Programación de servicios
El código en los módulos RequireJS puede realizar peticiones a los servicios de la aplicación. De igual modo que en
la parte cliente, un plugin puede contribuir con servicios a la aplicación final.
La implementación de estos servicios se basa en la especificación Java Servlet 3.0 y consistirá en la implementación
de uno o más Servlets definidos en el descriptor de despliegue. Este puede encontrarse en dos ficheros.
El primero es WEB-INF/web.xml del espacio web, es decir en src/main/webapp/WEB-INF/web.xml en
la estructura por defecto de Maven. Este fichero es el descriptor de despliegue propiamente dicho, y en él se pueden
definir todos los servlets necesarios en las aplicaciones, como demo.
Sin embargo, en los plugins no es posible utilizar el descriptor de despliegue (web-xml) ya que no se genera ningún
fichero WAR sino un JAR (que se incluirá en un WAR). En este caso, la especificación Servlet 3.0 define que las
librerías JAR usadas por una aplicación WAR pueden contribuir al descriptor de despliegue mediante un fichero
META-INF/web-fragment. Es el caso por ejemplo del plugin base que incluye distintos servicios para acceder
a indicadores sobre objetos de algunas capas del mapa:
<?xml version="1.0" encoding="UTF-8"?>
<web-fragment version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-f
<!-- indicators -->
<servlet>
<servlet-name>indicator-list-servlet</servlet-name>
<servlet-class>org.fao.unredd.indicators.IndicatorListServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>indicator-list-servlet</servlet-name>
<url-pattern>/indicators</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>indicator-data-servlet</servlet-name>
<servlet-class>org.fao.unredd.indicators.IndicatorDataServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>indicator-data-servlet</servlet-name>
<url-pattern>/indicator</url-pattern>
</servlet-mapping>
</web-fragment>
6.2 error-management
6.3 context-attributes
6.4 request-attributes
6.5 RequireJS
RequireJS es una librería que permite encapsular en módulos nuestro código JavaScript y cargarlos bajo demanda
(llamada require).
Para ello es necesario cargar la librería RequireJS en el código HTML de la página en la que se va a utilizar:
<html>
<head>
<script src="require.js" type="text/javascript"
data-main="modules/main"></script>
</head>
<body>
</body>
</html>
En el tag de carga se especifica el módulo inicial que RequireJS cargará y ejecutará. En el ejemplo “modules/main.js”.
Módulos RequireJS
Un módulo RequireJS consiste en:
• Unas dependencias
• Un código de inicialización que devuelve opcionalmente un valor y que se ejecuta cuando las dependencias han
sido a su vez inicializadas
Árbol de dependencias
Típicamente los módulos consisten en una llamada a define, que toma una lista de los módulos y una función de
inicialización que se ejecuta cuando las dependencias están satisfechas:
define([ "map" ], function(map) {
alert("este módulo depende del módulo"
+ "map y en este punto map ha sido"
+ "cargado e inicializado");
});
La función obtiene recibe como parámetros referencias a los módulos que se definen como dependencias, en el mismo
orden:
define([ "layer-list", "map" ], function(layerList, map) {
// layerList apunta al módulo "layer-list"
// map apunta al módulo "map"
});
pero el orden de carga puede variar, ya que por ejemplo, en el caso anterior otro módulo puede haber cargado ya
layer-list. RequireJS analiza las llamadas define y crea un árbol de dependencias que se cargan en cualquier
orden que garantice que las dependencias de un módulo estén todas cargadas e inicializadas cuando el módulo que
depende de ellas se inicializa.
De forma más visual:
para cargar A requireJS realiza una secuencia similar a esta:
• Se detecta la dependencia de A en B y C por lo que pasa a intentar cargarlos.
• Se intentar cargar B pero B depende de E, por lo que se intenta cargar E.
• Como E no tiene ninguna dependencia se cargar directamente y se llama a su código de inicialización.
• Ahora que B tiene todas sus dependencias satisfechas se ejecuta su código de incialización.
• RequireJS pasa a cargar C, pero detecta la dependencia en D y en E y, como E ya ha sido cargada, pasa a cargar
D.
• Como D no tiene ninguna dependencia se cargar directamente y se llama a su código de inicialización.
• Ahora C tiene sus dos dependencias ya cargadas, por lo que se llama a su código de incialización.
• Con B y C ya cargados se pasa a ejecutar el código de inicialización de A.
En la secuencia anterior se ilustra por una parte el hecho de que los módulos en los que depende un módulo son
cargados e inicializados previamente. Por otra parte, se muestra que el orden de carga de las dependencias de un
módulo no corresponde con el orden en el que están definidas en el código. Por ejemplo, en el caso de C primero se
cargó E antes que D porque la dependencia fue cargada por B previamente.
Dependencias de módulos no-RequireJS
En el caso de un módulo que dependa de librerías que no son módulos RequireJS (jquery, openlayers, etc.) es todavía necesario especificar las dependencias, ya que de lo contrario RequireJS puede cargarlas arbitrariamente antes
o después. En caso de hacerlo después, el módulo intentará utilizar una librería que no está cargada y se producirá un
error.
La forma de especificar la dependencia un modulo tal es idéntica a la de los modulos RequireJS:
define([ "jquery" ], function($) {
alert("este módulo depende de jQuery"
+ "y en este punto jQuery ha sido"
+ "cargado e inicializado");
});
pero ¿de dónde saca RequireJS el código de jQuery? No lo puede obtener de ningún sitio, por lo que hay que
especificárselo. El módulo main, que es el módulo de entrada, es el encargado de realizar esta configuración:
require.config({
baseUrl : "modules",
paths : {
"jquery" : "http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min",
"jquery-ui" : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min",
"fancy-box" : "../js/jquery.fancybox.pack",
"openlayers" : "../js/OpenLayers/OpenLayers.debug"
},
shim : {
"fancy-box" : [ "jquery" ]
}
});
En el código de main se puede observar cómo hay una llamada al método require.config que incluye un
elemento cuya propiedad paths incluye las direcciones de las cuales puede descargar RequireJS cada librería.
También se puede observar cómo en la propiedad shim se establecen las dependencias entre módulos no-RequireJS.
Valores de retorno
Se ha mostrado anteriormente cómo las dependencias de un módulo son recibidas como parámetro en la función de
inicialización:
define([ "layer-list", "map" ], function(layerList, map) {
// layerList apunta al módulo "layer-list"
// map apunta al módulo "map"
});
pero, ¿qué contienen exactamente estas referencias?
Contienen el valor que retorna la función de inicialización del módulo dependencia. Por ejemplo, el siguiente módulo
map en su función de inicialización crea un mapa OpenLayers, añade una capa y la devuelve:
define([ "openlayers" ], function() {
var mimapa = new OpenLayers.Map("map");
mimapa.addLayer(new OpenLayers.Layer.WMS("OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/
layers : "basic"
}));
return mimapa;
});
Por tanto, cuando el siguiente módulo establezca la dependencia:
define([ "map" ], function(map) {
map.setCenter(new OpenLayers.LonLat(-84, 0), 6);
});
el valor de la variable map que se pasará a la función de inicialización de éste será el mapa OpenLayers que se creó
en el módulo map anterior.
Funciones públicas
A veces un módulo debe permitir que los módulos que dependen de él realicen algunas operaciones sobre el mismo.
Más adelante se verá que existe otra manera que frecuentemente es mejor, pero de momento mostramos otra manera
que también puede ser útil en un momento dado.
Por ejemplo, si queremos hacer un módulo error para manejar los errores de la aplicación mostrando un diálogo al
usuario y escribiendo el problema en la consola tendríamos una función como ésta:
function(errorMsg) {
console.log(errorMsg);
alert(errorMsg);
});
Para poder utilizar este módulo tendremos que importarlo en las dependencias de nuestro módulo y cuando se produzca
la condición del error hacer una llamada a dicho módulo:
define([ "error" ], function(error) {
if (condicion) {
error.showMessage("Se ha cumplido una condición de error");
}
});
Pero para esto tendríamos que devolver un objeto en el módulo error que tenga una función showMessage. Esto
podría hacerse teniendo un módulo error como el siguiente:
define([], function() {
return {
showMessage : function(errorMsg) {
console.log(errorMsg);
alert(errorMsg);
}
};
});
cuyo valor de retorno tiene una propiedad showMessage que es la función de error.
Funciones privadas
Es posible definir funciones privadas dentro del módulo. Para ello basta con definir las funciones antes del valor de
retorno del módulo:
define([ "openlayers" ], function() {
function createLayer(name, url, wmsName) {
return new OpenLayers.Layer.WMS(name, url, {
layers : wmsName,
transparent : true
});
}
var mimapa = new OpenLayers.Map("map");
mimapa.addLayer(createLayer("Basic", "http://vmap0.tiles.osgeo.org/wms/vmap0?", "basic");
mimapa.addLayer(createLayer("Costa", "http://vmap0.tiles.osgeo.org/wms/vmap0?", "coastline_01
return mimapa;
});
En el ejemplo anterior se define una función createLayer y a continuación se le invoca un par de veces para
instanciar las capas WMS que se añaden al mapa.
Plantilla módulo
A continuación se presenta una plantilla que puede ser útil para la creación de nuevos módulos:
define([ "dependencia1", "dependencia2" ], function(modulo1, modulo2) {
// // variables privadas
// var miVariablePrivada = ...;
// // funciones privadas
// function miFuncionPrivada() {
// }
// // inicialización
// // valor de retorno
// return {
// propiedades públicas
miPropiedadPublica : ...,
// Funciones públicas
miFuncionPublica : function() {
}
};
});
6.6 Patrón de diseño message-bus
El patrón de diseño Message Bus permite desacoplar los componentes que forman una aplicación. En una aplicación
modular, los distintos componentes necesitan interactuar entre sí. Si el acoplamiento es directo, la aplicación deja
de ser modular ya que aparecen dependencias, con frecuencia recíprocas, entre los distintos módulos y no es posible
realizar cambios a un módulo sin que otros se vean afectados.
En cambio, si los objetos se acoplan a través de un objeto intermediario (Message Bus), casi todas las dependencias
desaparecen, dejando sólo aquellas que hay entre el Message Bus y los distintos módulos.
En el siguiente ejemplo vemos una hipotética aplicación modular que consta de tres componentes con representación
gráfica y que están dispuestos de la siguiente manera:
• Map: En la parte central (verde) hay un mapa que muestra cartografía de España.
• LayerList: En la parte izquierda (rojo) hay una lista de temas. Vemos que sólo hay un tema de catastro, que es
el que se visualiza en el mapa.
• NewLayer: En la parte superior (azul) existe un control que permite añadir temas a los otros dos componentes.
Un posible diseño de dicha página consistiría en un módulo Layout que maqueta la página HTML y que inicializa
los otros tres objetos. En respuesta a la acción del usuario, el objeto NewLayer mandaría un mensaje a LayerList y
Map para añadir el tema en ambos componentes. De la misma manera, LayerList podría mandar un mensaje a Map en
caso de que se permitiera la eliminación de capas desde aquél. El siguiente grafo muestra los mensajes que se pasarían
los distintos objetos:
Es posible observar como en el caso de que se quisiera quitar el módulo LayerList, sería necesario modificar el objeto
Layout así como el objeto NewLayer, ya que están directamente acoplados. Sin embargo, con el uso del Message Bus,
sería posible hacer que los distintos objetos no se referenciaran entre sí directamente sino a través del Message Bus:
Así, el módulo NewLayer mandaría un mensaje al Message Bus con los datos de la nueva capa y Map y LayerList
símplemente escucharían el mensaje y reaccionarían convenientemente. Sería trivial quitar de la página LayerList ya
que no hay ninguna referencia directa al mismo (salvo tal vez en Layout).
Y al contrario: sería posible incluir un nuevo módulo, por ejemplo un mapa adicional, y que ambos escuchasen el
evento “add-layer” de forma que se añadirían los temas a ambos mapas.
De esta manera la aplicación es totalmente modular: es posible reemplazar módulos sin que los otros módulos se vean
afectados, se pueden realizar contribuciones bien definidas que sólo deben entender los mensajes existentes para poder
integrarse en la aplicación, etc.
TODO referenciar los ejemplos del taller de desarrollo.
6.7 Arquitectura servidor
Introducción a servlet 3.0
La parte servidor del portal está construida sobre la especificación Servlet 3.0. En este contexto se pueden crear
principalmente tres tipos de objetos: filtros, servlets y escuchadores de contexto o ContextListeners.
Servlets
Los servlets son classes que heredan de javax.servlet.http.HttpServlet:
package org.fao.unredd;
import javax.servlet.http.HttpServlet;
public class HolaMundoServlet extends HttpServlet {
}
El objetivo principal del servlet es proporcionar una respuesta a una petición HTTP. Si por ejemplo queremos retornar
una petición GET deberemos de implementar el método doGet:
package org.fao.unredd;
import java.io.IOException;
import
import
import
import
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
public class HolaMundoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String nombre = req.getParameter("nombre");
resp.getWriter().println("Hola mundo, " + nombre);
}
}
Podemos observar que es posible utilizar la instancia de HttpServletRequest que se pasa en el parámetro req
para acceder a los parámetros de la petición GET, en este caso nombre; y que podemos escribir la respuesta a través
de la instancia de HttpServletResponse.
Por último se necesario especificar al sistema en qué URL se debe acceder a dicho servlet. Para ello hay que registrarlo
en el fichero web.xml de la siguiente manera:
<servlet>
<servlet-name>holamundo-servlet</servlet-name>
<servlet-class>org.fao.unredd.HolaMundoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>holamundo-servlet</servlet-name>
<url-pattern>/holamundo</url-pattern>
</servlet-mapping>
En el elemento servlet se le dice al sistema que hay un servlet de nombre holamundo-servlet y que es implementado por la clase org.fao.unredd.HolaMundoServlet (vista más arriba), mientras que en el segundo
elemento se establece la URL en la que se puede acceder al servlet. Con esta configuración sería posible acceder al
servlet en una URL similar a esta:
http://localhost:8080/app/holamundo
Note: La especificación Servlet 3.0 permite la inclusión de anotaciones en la clase que implementa el servlet para
especificar la misma información que contiene el web.xml, de tal manera que el fichero no es necesario. Sin embargo,
por simplicidad se evita el uso de anotaciones de este tipo en general.
Filtros
7 Configuración de los plugins
Con anterioridad se ha comentado que el descriptor del plugin “xxx-conf.json” incluye un elemento para la configuración de los distintos modulos RequireJS que forman el plugin.
La configuración que se especifica en dichos elementos queda accesible a los módulos RequireJS mediante el método
config() meta-modulo module. Por ejemplo, si tuviéramos el siguiente descriptor de plugin:
{
"default-conf" : {
"mi-modulo" : {
"mensaje" : "hola mundo"
}
}
}
el siguiente módulo, definido en “mi-modulo.js” podría acceder a su configuración así:
define([ "module" ], function(module) {
alert(module.config());
});
mostrando por pantalla el valor de su configuración, es decir el mensaje “hola mundo”.
7.1 Modificación de la configuración en tiempo de ejecución
Ahora bien, esta configuración está definida en el plugin de forma fija y sólo se puede cambiar tocando el códido del
portal y generando un nuevo WAR, lo cual no es nada práctico. ¿Cómo se puede cambiar la configuración de un plugin
de la aplicación una vez ésta está desplegada y ejecutándose en el servidor?
La manera más sencilla consiste en modificar el fichero plugin-conf.json que se encuentra en el directorio de
configuración del portal. Este fichero tiene la misma estructura que el descriptor del plugin con la única diferencia de
que es usado sólo para sobreescribir la configuración por defecto de los distintos módulos. Así, podríamos editar el
fichero para dejarlo de esta manera:
{
"default-conf" : {
"mi-modulo" : {
"ejemplo" : "hola a todo el mundo"
}
}
}
Y al cargar el módulo mi-modulo aparecerá por la pantalla “hola a todo el mundo”, en lugar de “hola mundo”.
7.2 Modificación de la configuración por programación
En ocasiones la configuración que se quiere pasar al plugin depende de un valor de la base de datos, o de si el usuario
está logado o, en general, de aspectos que se tienen que comprobar por programación. ¿De qué manera es posible
hacer llegar estos valores a un elemento de la interfaz de usuario? La solución son los proveedores de configuración.
Los
proveedores
de
configuración
son
instancias
que
implementan
la
interfaz
org.fao.unredd.portal.ModuleConfigurationProvider, que permite añadir elementos a la configuración de los módulos de la misma manera que se haría manualmente modificando el fichero plugin-conf.json.
Para que la instancia contribuya a la configuración hay que registrarla en la instancia config, por lo que normalmente
se registrará en un context listener con un código similar al siguiente:
ServletContext servletContext = sce.getServletContext();
Config config = (Config) servletContext.getAttribute("config");
config.addModuleConfigurationProvider(new MiConfigurationProvider());
8 Módulos importantes
Entre los módulos más importantes existentes en la plataforma podemos destacar:
• message-bus: Implementación del patrón message bus (Ver Patrón de diseño message-bus)
• communication: Facilita la comunicación con el servidor
• error: Gestión centralizada de los errores
• layout: Crea el layout de la página
• i18n: Contiene las cadenas de los ficheros de traducción .properties
• map: Crea el mapa principal
• layers: Lee la configuración de capas y lanza eventos add-layer y add-group
• customization: Carga el resto de módulos
8.1 Message-bus
De los módulos anteriores, el más importante es message-bus
Función
principal:
Ofrecer
dos
métodos
para
mandar
y
escuchar
mensajes
al/del
bus.
La documentación de las dos funciones puede encontrarse en el código fuente:
https://github.com/nfms4redd/nfms/blob/develop/portal/src/main/webapp/modules/message-bus.js
Valor de retorno: Un objeto con dos propiedades send y listen que permiten respectivamente enviar y escuchar
mensajes.
Mensajes enviados: Ninguno. El módulo se encarga de procesar y canalizar los eventos, pero no inicia ni escucha
ninguno.
Mensajes escuchados: Ninguno.
9 Referencia mensajes
9.1 modules-loaded
Enviado una vez el módulo customization ha cargado todos los módulos especificados por el servidor.
Parámetros: Ninguno.
Ejemplo de uso: Útil para realizar acciones que requieren que todos los módulos hayan sido cargados. Por ejemplo, el
envío de mensajes de potencial interés para algún módulo ha de hacerse tras la carga de todos los módulos, es decir,
una vez este mensaje ha sido enviado.
Más información:
• Secuencia de inicio de la aplicación
9.2 before-adding-layers
Enviado justo antes de que se empiecen a lanzar los eventos add-group y add-layer. Da la oportunidad a otros módulos
de realizar operaciones previas a la carga de las capas.
Parámetros: Ninguno.
Más información:
• Secuencia de inicio de la aplicación
9.3 layers-loaded
Enviado una vez el módulo layers ha lanzado los eventos add-layer y add-group correspondientes a la configuración de capas existente en el servidor.
Parámetros: Ninguno
Ejemplo de uso: Útil para realizar acciones que requieren que las capas de información se hayan cargado, por ejemplo,
para centrar el mapa.
Más información:
• Secuencia de inicio de la aplicación
• moduleconfiguration
9.4 ajax
Escuchado por el módulo communication para realizar llamadas a servicios.
Parámetros: Un objeto con las siguientes propiedades:
• url: URL a la que se quiere pedir la información
• success: función a ejecutar cuando el servidor responda satisfactoriamente
• complete: función a ejecutar cuando el servidor responda, sea satisfactoriamente o tras un error
• errorMsg: Mensaje de error
• error: función a ejecutar cuando el servidor responda con un error. Por defecto se generará un mensaje de error
con el contenido de errorMsg.
• controlCallBack: función que recibe el objeto XMLHttpRequest que representa la petición. Este objeto tiene
métodos como abort() que permiten la cancelación de la petición
Ejemplo de uso:
9.5 error
Escuchado por el módulo error, que muestra un mensaje de error al usuario:
Parametros: Mensaje con el error a mostrar
Ejemplo de uso:
bus.send("error", "Dirección de e-mail incorrecta");
9.6 info-features
Resultados de la petición de información a una única capa.
Parámetros:
• wmsLayerId: Id de la capa a la que pertenecen las features.
• features: array con las features OpenLayers. Cada feature tiene:
– Una propiedad “aliases” que es un array que contiene un objeto con propiedades “name” y “alias” para
cada atributo de la feature. Por ejemplo:
[{
"name" : "ident",
"alias" : "Id"
},
{
"name" : "nprov",
"alias" : "Nombre"
},
{
"name" : "pop96",
"alias" : "Población (1996)"
}]
– Una propiedad “bounds” con el bounding box de la geometría de la feature o null si el servidor no la
devolvió. Siempre en EPSG:900913.
– Una propiedad highlightGeom con la geometría de la feature o el bounding box (en caso de que así se
configure en el layers.json) o null si el servidor no devolvió datos geométricos. Siempre en EPSG:900913.
• x: Posición X en la que se hizo click para obtener la información
• y: Posición Y en la que se hizo click para obtener la información
Ejemplo de uso:
Más información:
9.7 zoom-in
Mueve la escala al nivel inmediatamente mayor
Parámetros: Ninguno
Ejemplo de uso:
bus.send("zoom-in");
Más información:
9.8 zoom-out
Mueve la escala al nivel inmediatamente menor
Parámetros: Ninguno
Ejemplo de uso:
bus.send("zoom-out");
Más información:
9.9 zoom-to
Mueve el encuadre al objeto OpenLayers.Bounds que se pasa como parámetro. El objeto bounds debe estar en el
sistema de referencia del mapa (EPSG:900913)
Parámetros: OpenLayers.Bounds con el extent deseado
Ejemplo de uso:
var bounds = new OpenLayers.Bounds();
bounds.extend(new OpenLayers.LonLat(0,42));
bounds.extend(new OpenLayers.LonLat(10,52));
bounds.transform( new OpenLayers.Projection("EPSG:4326"),
new OpenLayers.Projection("EPSG:900913"));
bus.send("zoom-to", bounds);
Más información:
9.10 initial-zoom
Situa el mapa en la posición inicial
Parámetros: Ninguno
Ejemplo de uso:
bus.send("initial-zoom");
Más información:
9.11 set-default-exclusive-control
Establece el control exclusivo por defecto para el mapa. Sólo un módulo exclusivo está activado en cada momento.
Parámetros: Objeto OpenLayers.Control.
Ejemplo de uso:
var control = new OpenLayers.Control.WMSGetFeatureInfo({
...
});
bus.send("set-default-exclusive-control", [control]);
Más información:
9.12 activate-default-exclusive-control
Activar el control establecido por defecto mediante el mensaje set-default-exclusive-control
Parámetros: Ninguno
Ejemplo de uso:
bus.send("activate-default-exclusive-control");
Más información:
9.13 activate-exclusive-control
Pide la activación exclusiva del control que se pasa como parámetro y la desactivación del control exclusivo que
estuviera activado en el momento de lanzar el mensaje
Parámetros: OpenLayers.Control
Ejemplo de uso:
var clickControl = new OpenLayers.Control({
...
});
bus.send("activate-exclusive-control", [ clickControl ]);
Más información:
9.14 highlight-feature
Indica que se debe resaltar la geometría que se pasa como parámetro
Parámetros: OpenLayers.Geometry
Ejemplo de uso:
Más información:
9.15 clear-highlighted-features
Indica que se deben de eliminar todos los resaltes establecidos mediante highlight-feature
Parámetros: Ninguno.
Ejemplo de uso:
Más información:
9.16 add-group
Indica que se debe añadir un grupo al árbol de capas
Parámetros: Un objeto con las siguientes propiedades:
• id: identificador del grupo
• parentId: Opcional, para grupos dentro de otros grupos hace referencia al grupo contenedor
• name: nombre del grupo
• infoLink: Ruta de la página HTML con información sobre el grupo
Ejemplo de uso:
bus.send("add-group", [ {
id:"grupo_admin",
name:"Límites administrativos"
}]);
Más información:
9.17 add-layer
Indica que se debe añadir una capa a la aplicación
Parámetros: Un objeto con las siguientes propiedades:
• id: id de la capa
• groupId: id del grupo en el que se debe añadir la capa
• label: Texto con el nombre de la capa a usar en el portal
• infoLink: Ruta de la página HTML con información sobre la capa
• inlineLegendUrl: URL con una imagen pequeña que situar al lado del nombre de la capa en el árbol de capas
• queryable: Si se pretende ofrecer herramienta de información para la capa o no
• active: Si la capa está inicialmente visible o no
• wmsLayers: Array con la información de las distintas capas WMS que se accederán desde esta capa. El caso
más habitual es que se acceda sólo a una, pero es posible configurar varias. Los objetos de este array tienen la
siguiente estructura:
– baseUrl: URL del servidor WMS que sirve la capa
– wmsName: Nombre de la capa en el servicio WMS
– imageFormat: Formato de imagen a utilizar en las llamadas WMS
– zIndex: Posición en la pila de dibujado
– legend: Nombre del fichero imagen con la leyenda de la capa.
static/loc/{lang}/images
Estos ficheros se acceden en
– label: Título de la leyenda
– sourceLink: URL del proveedor de los datos
– sourceLabel: Texto con el que presentar el enlace especificado en sourceLink
– timestamps: Array con los instantes de tiempo en ISO8601 para los que la capa tiene información
Ejemplo de uso:
bus.send("add-layer", {
"id" : "meteo-eeuu",
"groupId" : "landcover",
"label" : "Radar EEUU",
"active" : "true",
"wmsLayers" : [ {
"baseUrl" : "http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r-t.cgi",
"wmsName" : "nexrad-n0r-wmst"
} ]
});
Más información:
9.18 layer-visibility
Cambia la visibilidad de una capa
Parámetros:
• id de la capa portal
• valor de visibilidad
Ejemplo de uso:
bus.send("layer-visibility", ["provincias", false]);
Más información:
9.19 time-slider.selection
Lanzado cuando el usuario selecciona un instante temporal global distinto al actual. Generalmente se actualiza el
mapa con la información de esa fecha.
Parámetros: objeto Date con el instante temporal seleccionado
Ejemplo de uso:
var d = new Date();
bus.send("time-slider.selection", d);
Más información:
9.20 layer-time-slider.selection
Lanzado cuando el usuario selecciona un instante temporal específico para una capa (a diferencia del
time-slider.selection cuyo instante es global para todas las capas).
Parámetros:
• id de la portalLayer que ha determinado su instante temporal.
• objeto Date con el instante temporal seleccionado
Ejemplo de uso:
var d = new Date();
bus.send("layer-time-slider.selection", ["mi-portal-layer", date]);
Más información:
9.21 layer-timestamp-selected
Una capa ha escuchado uno de los eventos de selección temporal y ha determinado qué instancia temporal es la que
más se ajusta a esa. La capa selecciona la última instancia temporal que es menor o igual al instante seleccionado o la
primera instancia si el instante seleccionado es anterior a todas sus instancias.
Parámetros:
• id de la portalLayer que ha determinado su instante temporal.
• objeto Date con el instante temporal seleccionado
• cadena de carácteres con el nombre del estilo que se debe usar para esta instancia temporal. Puede ser nulo si la
capa no requiere un estilo distinto por instante.
Más información:
9.22 toggle-legend
Escuchado por el módulo legend-panel para mostrar u ocultar el panel con la leyenda.
Parámetros: Ninguno
Ejemplo de uso:
bus.send("toggle-legend");
Más información:
9.23 register-layer-action
Escuchado por la lista de capas. Instala un botón a la derecha de las capas que realizará una acción al ser pulsado.
Parámetros: Función que devuelve el objeto jQuery que se mostará a modo de botón. Esta función toma como
parámetro el mismo objeto que se lanza en el evento add-layer.
Ejemplo de uso (botón de información):
bus.listen("before-adding-layers", function() {
var showInfoAction = function(portalLayer) {
if (portalLayer.hasOwnProperty("infoFile")) {
aLink = $("<a/>").attr("href", portalLayer.infoFile);
aLink.addClass("layer_info_button");
aLink.fancybox({
"closeBtn" : "true",
"openEffect" : "elastic",
"closeEffect" : "elastic",
"type" : "iframe",
"overlayOpacity" : 0.5
});
return aLink;
} else {
return null;
}
};
bus.send("register-layer-action", showInfoAction);
});
Más información:
9.24 register-group-action
Igual que register-layer-action pero para grupos.
9.25 show-layer-panel
Activa el panel de capas indicado.
Parámetros: identificador del panel a activar. La lista de paneles puede variar en función de los plugins que haya
activados. La lista completa de ids es:
• all_layers_selector
• layers_transparency_selector
• layer_slider_selector (sólo con el plugin layer-time-sliders)
Ejemplo de uso:
bus.send("show-layer-panel", [ "layers_transparency_selector" ]);
Más información:
9.26 show-info
Muestra una ventana emergente con determinada información que se pasa como parámetro.
Parámetros:
• title: Título de la ventana
• link: Bien una url que apunta a la página que se pretende mostrar, o un objeto jquery que será mostrado en la
ventana
• eventOptions: Opcional. Elemento con las opciones para la personalización de la ventana. Actualmente se
utiliza FancyBox por lo que se puede añadir cualquier opción válida de este framework.
Ejemplo de uso:
bus.send("show-info", [ "Mi info", "http://ambiente.gob.am/portal/static/loc/es/html/doc.html" ]);
Más información:
9.27 show-layer-info
Muestra la información asociada a una capa con su atributo infoLink o infoFile.
Parámetros: identificador de la capa.
Ejemplo de uso:
bus.send("show-layer-info", [ "provincias" ]);
9.28 show-group-info
Muestra la información asociada a un grupo con su atributo infoLink o infoFile.
Parámetros: identificador del grupo
Ejemplo de uso:
bus.send("show-group-info", [ "base" ]);
9.29 show-wait-mask
Muestra un indicador de que el sistema está ocupado y el usuario debe esperar
Parámetros: Texto informativo
Ejemplo de uso:
bus.send("show-wait-mask", "Enviando información al servidor...");
9.30 hide-wait-mask
Oculta el indicador mostrado por show-wait-mask
Parámetros: Ninguno
Ejemplo de uso:
bus.send("hide-wait-mask");
9.31 activate-feedback
Activa el modo feedback mostrando la ventana y seleccionando la herramienta para el dibujado del polígono sobre el
que se da el feedback.
Parámetros: ninguno
Ejemplo de uso:
bus.send("activate-feedback");
9.32 deactivate-feedback
Desactiva el modo feedback, ocultando la ventana y volviendo a la herramienta por defecto (navegación).
Parámetros: ninguno
Ejemplo de uso:
bus.send("deactivate-feedback");
10 Temas avanzados
10.1 Secuencia de inicio de la aplicación
La aplicación se inicia generando el fichero index.html mediante un motor de plantillas. Dicho motor rellena:
• Los tags style con las referencias a los ficheros .css del directorio de módulos.
• Una llamada al servicio config.js con el parámetro lang establecido al valor con el que se accedió a
index.html (si se accedió con index.html?lang=es se generará la carga de config.js?lang=es).
La llamada al servicio config.js devuelve un fichero javascript con la configuración de los distintos módulos. La
configuración relevante para el inicio de la aplicación es la del módulo customization, que incluye la lista de
módulos existente en el fichero portal.properties en la propiedad client.modules.
El módulo customization obtiene la lista de módulos de la configuración y se realiza la siguiente secuencia:
1. customization hace una llamada a require para cargar los módulos.
2. una vez cargados, se lanza el mensaje modules-loaded
3. el evento es escuchado por el módulo layers que fue cargado en el primer paso. layers lanza el evento
before-adding-layers y a continuación procesa el árbol de capas y lanza los mensajes add-group y add-layer
correspondientes.
4. layers lanza el mensaje layers-loaded
11 Compilación del proyecto
11.1 Obtención de los fuentes
El código del portal se encuentra alojado en el siguiente repositorio de GitHub: https://github.com/nfms4redd/portal.
GitHub proporciona un servicio de GIT, un conocido sistema de control de versiones. Para aquellos que tengan
conocimientos de uso de GIT, la URL del servicio es [email protected]:nfms4redd/portal.git. Bastaría con ejecutar la
siguiente instrucción para tener una copia de los fuentes instalada en local:
$ git clone [email protected]:nfms4redd/portal.git
En el caso de que no se quiera utilizar GIT, es posible descargarse un fichero zip con el estado actual del repositorio
pulsando el botón “Download ZIP” que ofrece GitHub en la página del repositorio.
Hay que tener en cuenta que la rama por defecto, llamada “develop”, es en la que se realizan los desarrollos y es
posible que sea algo más inestable. En el combo “branch:” es posible elegir otras versiones del código fuente, como
por ejemplo las ramas que comienzan por “release_” que son más estables.
11.2 Instalación de los fuentes en eclipse
Una vez el código fuente ha sido descargado, es necesario abrir Eclipse e importar un proyecto Maven existente
mediante un clic con el botón derecho en el Project Explorer > Import > Import...
En el diálogo que aparece hay que decirle a Eclipse que el proyecto que queremos importar es un proyecto Maven.
Así, habrá que seleccionar Maven > Existing Maven Projects
A continuación hay que seleccionar el directorio en el que hemos puesto el portal y darle a siguiente.
Tras este paso, nos aparecerá una ventana en la que podremos seleccionar los proyectos dentro del repositorio del
portal que queremos importar:
Como se ve en la imagen, seleccionaremos todos menos el proyecto raíz y haremos clic en el botón de finalizar. Es
posible que Eclipse instale algunos plugins tras esta acción.
Warning: El repositorio del portal contiene varios proyectos correspondientes a la aplicación del portal, plugins,
herramientas, etc. Para más información sobre cómo están organizados los proyectos dentro del repositorio ver
Estructura del repositorio
11.3 Ejecución del portal en tomcat desde Eclipse
Para arrancar un Tomcat que contenga el portal, es necesario hacer clic derecho en uno de los proyectos aplicación
(ver Estructura del repositorio), como demo, y seleccionar en el menú contextual que aparece: Debug As > Debug on
server.
En el caso de no tener un servidor instalado aparecerá un diálogo que nos permitirá definir un nuevo servidor. Seleccionaremos “Tomcat v7.0 Server” y en la siguiente pantalla seleccionaremos el directorio donde se encuentra nuestro
Tomcat. Obviamente, es necesario haber descargado e instalado previamente un servidor Apache Tomcat 7.0.
Tras esta operación podremos aceptar el diálogo, tras lo cual el servidor se ejecutará y se abrirá una ventana dentro de
eclipse con el portal. También debe ser posible acceder al mismo mediante la siguiente URL:
http://localhost:8080/unredd-portal/
desde cualquier navegador web.
11.4 Generación del unredd-portal.war
Si lo que se pretende es exclusivamente obtener el fichero .war, es posible obviar Eclipse y utilizar directamente Maven
desde línea de comandos:
$ mvn package
tras el cual el fichero demo/target/unredd-portal.war habrá aparecido. Dicho fichero se puede desplegar en el directorio
webapps de Tomcat para poder usar la aplicación recién compilada.
11.5 Generación del unredd-portal.war desde eclipse
Desde eclipse, se podría generar el war clicando con el botón derecho del ratón en el proyecto portal -> Run as ->
Maven Build.
En la ventana que aparece, escribir “package” en el texto Goals:
y seleccionar la casilla “Skip Tests”. Tras pinchar en el botón Run se iniciará la ejecución. Si hacia el final aparece el
texto:
[INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
todo estará correcto y aparecerá un fichero unredd-portal.war en el directorio target.
11.6 Particularidades de la construcción del proyecto
Maven tiene un método estandarizado para la construcción de proyectos en Java. Este método sigue una secuencia
de actividades o ciclo de vida (explicado aquí) que define que primero se realiza la compilación, luego un testeo, el
empaquetado, etc. y que es común para todos los proyectos Maven.
Sin embargo, a la hora de construir el portal, hay algunas particularidades que hay que tener en cuenta.
Optimización
Por una parte, durante la construcción del proyecto se realizan una optimización de los recursos del cliente (ver
Optimización) que puede tomar bastante tiempo. En algunos casos es posible que esta optimización no sea necesaria.
Para solucionar esto, Maven proporciona perfiles, que son configuraciones que se pueden activar y desactivar. Así,
la optimización está configurada en un perfil “optimize” que está activo por defecto pero se puede desactivar con el
parámetro -P, seguida del nombre del perfil con un signo de exclamación delante (para desactivar):
mvn -P \!optimize package
Tests de integración
Por otra parte, una vez se construye el WAR en la fase package de Maven, se llega a la fase integration-test.
En el caso del portal, se testean las conexiones a bases de datos, etc. por lo que se requiere levantar determinados
servicios externos para que la fase se pase con éxito. Para más información ver Ejecución de los tests de integración
12 Ejecución de los tests de integración
El proyecto que contiene los tests automatizados de integración es integration-tests. En él hay una serie de
tests que se hacen al WAR de demo y en el que se comprueban los servicios de los plugins que demo incluye. Como
estos servicios hacen uso de bases de datos externas, es necesario realizar algún tipo de configuración previa.
12.1 Servicio de base de datos
Lo primero es levantar un servicio de base de datos que pueda ser utilizado por los distintos plugins.
Una vez el servicio está levantado, hay que configurar en el fichero
integration-tests/src/main/resources/org/fao/unredd/functional/functional-test.properties
los parámetros de dicha conexión. Por ejemplo:
db-url=jdbc:postgresql://192.168.2.107:5432/geoserverdata
db-user=geoserver
db-password=unr3dd
db-test-schema=integration_tests
Actualmente esta configuración está apuntando al servidor local ya que así lo requiere el servidor de integración
continua Travis.
12.2 Configuración del plugin Feedback
En el fichero portal.properties es necesario especificar los parámetros necesarios para que el plugin de feedback pueda encontrar la base de datos:
feedback-db-table=integration_tests.comments
y pueda enviar emails:
feedback-mail-host=smtp.gmail.com
feedback-mail-port=587
[email protected]
feedback-mail-password=mipassword
feedback-mail-title=Comentario en portal UNREDD
feedback-mail-text=Por favor, visite $url para confirmar el envío.
Este fichero portal.properties se encuentra en un directorio de configuración propio de los tests de integración, en
integration-tests/test_config. Como para enviar correos es necesario darle al sistema el password
de [email protected] y no queremos guardarlo en un repositorio de código público, en este portal.properties la
propiedad feedback-mail-password tiene el valor $password, que se reemplazará por el valor de la variable de
entorno ONUREDDMAILPASSWORD antes de ejecutar los tests.
12.3 Ejecución de los tests
Una vez los componentes externos están levantados y el portal configurado, sólo queda ejecutar los tests de integración:
mvn verify
Sin embargo, pasaremos por la fase package que incluye una operación de optimización. Éstas se pueden evitar
desactivando el perfil optimize:
mvn -P \!optimize verify
Para más información sobre el uso de Maven ver Particularidades de la construcción del proyecto.
13 Manuales
Las siguientes secciones incluyen manuales para la creación de las tareas de desarrollo sobre el portal más comunes.
13.1 Hello World
Lo primero es crear un nuevo fichero hola-mundo.js en el directorio de módulos. Como contenido podemos definir
símplemente:
alert("hola mundo");
A continuación registramos el módulo en el fichero portal.properties. En su propiedad client.modules añadimos
el nombre de nuestro módulo, que no es otro que el nombre del fichero sin la extensión .js:
client.modules=hola-mundo,layers,communication,iso8601,error-management,map,banner,toolbar,time-slide
Al recargar el portal nos encontraremos con el mensaje “hola mundo” nada más empezar.
13.2 Añadir elementos a la interfaz
Se crea el módulo de la forma habitual, creando el fichero en el directorio de módulos y añadiendo el módulo como
dependencia al pom.xml del proyecto.
A continuación se debe elegir en qué punto de la página se quiere añadir el botón. En este caso queremos añadirlo a la
barra de herramientas principal. Para ello tenemos que obtener el objeto div de dicha barra, el cual nos lo proporciona
el módulo toolbar, que importaremos como dependencia:
define([ "toolbar" ], function(toolbar) {
});
Si observamos el módulo toolbar, podemos ver que devuelve un objeto jQuery con el div y que sólo tenemos que
acceder al objeto toolbar para acceder al div:
return divToolbar
En este punto podríamos realizar una prueba para comprobar que tenemos una referencia valida al div. El siguiente
código hace invisible el div de la barra de herramientas:
define([ "toolbar" ], function(toolbar) {
toolbar.hide();
});
Si hemos hecho todos los pasos correctamente, veremos que la barra de herramientas no aparece, ya que la hemos
escondido en nuestro módulo.
Lo único que queda por hacer es reemplazar el código de prueba anterior por otro que cree un botón. Esto lo podemos
hacer creando un tag <button> con jQuery:
define([ "toolbar", "jquery" ], function(toolbar, $) {
var aButton = $("<button/>").attr("id", "miboton").addClass("blue_button").html("púlsame");
aButton.appendTo(toolbar);
aButton.click(function() {
alert("boton pulsado");
});
});
Nótese que, como queremos utilizar jQuery, tenemos que declararla como un módulo y definirla como parámetro $ en
la función de inicialización.
Por último, podemos añadir un fichero CSS para estilar dicho botón y añadirle un margen, por ejemplo:
#miboton {
margin: 12px;
}
13.3 Añadir una botonera
Este manual pretende añadir un botón al portal creando un módulo botonera que facilite la tarea en lo sucesivo.
Así pues, el proceso será similar, con la diferencia que en lugar de añadir un botón, vamos a añadir un objeto div:
define([ "jquery", "layout" ], function($, layout) {
var botonera = $("<div/>").attr("style", "position:absolute;top:6px; left:7em; z-index:2000")
botonera.appendTo(layout["map"]);
});
En el código anterior podemos observar cómo se crea un objeto div, se estila y se añade al espacio reservado para el
mapa por el módulo layout. Esto último quiere decir que la botonera estará sobre el mapa.
A continuación debemos de dar la posibilidad a otros módulos para que añadan elementos a dicha botonera. Existen
dos maneras: escuchando un mensaje o devolviendo un objeto con un método que añada el botón cuando es invocado.
En este manual veremos esta última posibilidad.
Para ello el módulo deberá devolver un objeto {} con una propiedad que sea una función:
define([ "jquery", "layout" ], function($, layout) {
var botonera = $("<div/>").attr("style", "position:absolute;top:6px; left:8em; z-index:2000")
botonera.appendTo(layout["map"]);
return {
newButton : function(text, callback) {
// Añade el botón
}
};
});
Para utilizar esta funcionalidad, es necesario crear un nuevo módulo que importe el módulo botonera:
define([ "botonera" ], function(botonera) {
});
En este código, la variable botonera recibida en la función de inicialización del módulo es el valor de retorno de
la inicialización del módulo botonera, por lo que es posible hacer llamadas a la propiedad newButton de esta
variable:
define([ "botonera" ], function(botonera) {
botonera.newButton("hola mundo", function() {
alert('hola mundo');
});
});
Por último, queda implementar el código de la función newButton, que debe tomar al menos un texto y una función
callback que se invocará cuando se pinche en el botón:
define([ "jquery", "layout" ], function($, layout) {
var botonera = $("<div/>").attr("style", "position:absolute;top:6px; left:7em; z-index:2000")
botonera.appendTo(layout["map"]);
return {
newButton : function(text, callback) {
var aButton = $("<button/>").html(text);
aButton.appendTo(botonera);
aButton.click(callback);
}
};
});
13.4 Manejo de eventos
El siguiente tutorial muestra cómo a través de los eventos existentes es posible interactuar con la plataforma. Para
ello crearemos un módulo consistente en poner a invisible todas las capas (checkbox desactivado) mediante dos pasos
típicos:
1. Captura de eventos y recogida de información
Para poner invisible las capas se utilizará el evento “layer-visibility”, al que se pasa el identificador de la capa y el
valor de visibilidad. El valor de visibilidad es siempre falso, pero además se necesitará la lista de identificadores de
todas las capas. Es en este punto es necesario escuchar el evento “add-layer” y guardar la información relevante, los
identificadores de las capas:
define([ "message-bus" ], function(bus) {
var layerIds = [];
bus.listen("add-layer", function(event, layerInfo) {
layerIds.push(layerInfo.id);
});
});
2. Ejecución del módulo
Una vez recopilados todos los ids, sólo queda lanzar el mensaje para cada una de las capas. Esto se hará en respuesta
a la pulsación en un botón, utilizando el módulo botonera creado en manuales anteriores:
define([ "message-bus", "botonera" ], function(bus, botonera) {
var layerIds = [];
bus.listen("add-layer", function(event, layerInfo) {
layerIds.push(layerInfo.id);
});
botonera.newButton("todas invisibles", function() {
for (var i = 0; i < layerIds.length; i++) {
bus.send("layer-visibility", [layerIds[i], false]);
}
});
});
Patrón habitual
El presente manual es un ejemplo sencillo de un patrón que se repite una y otra vez a lo largo del portal:
1.- Escuchado de eventos y recogida de información 2.- Realización de una acción con la información obtenida de los
eventos
13.5 Posición del mapa
Cada vez que se quiere añadir un mapa OpenLayers en una página web se comienza escribiendo código en el que
creamos el mapa:
var map = new OpenLayers.Map("map", {
theme : null,
projection : new OpenLayers.Projection("EPSG:4326"),
units : "m",
allOverlays : true,
controls : []
});
para luego interactuar con él añadiéndole capas, instalando controles, etc.:
map.addControl(new OpenLayers.Control.Navigation({
documentDrag : true,
zoomWheelEnabled : true
}));
map.addControl(new OpenLayers.Control.MousePosition({
prefix : '<a target="_blank" ' +
'href="http://spatialreference.org/ref/epsg/4326/">' + 'EPSG:4326</a> coordinates: ',
separator : ' | ',
numDigits : 2,
emptyString : 'Mouse is not over map.'
}));
Sin embargo, en el portal de diseminación ya existe un mapa creado. ¿Cuál es la manera de proceder para obtener una
referencia a dicho mapa y, por ejemplo, mostrar información sobre las coordenadas que se están navegando?
La respuesta es sencilla, símplemente hay que importar el módulo map y obtener la referencia en la función de
inicialización:
define([ "map", "layout" ], function(map, layout) {
var divMap = layout["map"];
var divCoor = $("<div/>").attr("id", "coor");
divMap.append(divCoor);
var control = new OpenLayers.Control.MousePosition({
prefix : '<a target="_blank" ' + 'href="http://spatialreference.org/ref/epsg/4326/">'
div : divCoor.get(0),
separator : ' | ',
numDigits : 2,
emptyString : 'Mouse is not over map.'
});
map.addControl(control);
});
13.6 Lectura de parámetros URL
Los parámetros de la URL son procesados por el servidor y retornados como configuración del módulo
url-parameters.
A su vez, el módulo url-parameters ofrece una función get en su valor de retorno que permite obtener los
valores de los parámetros.
Así pues, para obtener el valor del parámetro lang podemos crear el siguiente módulo:
define([ "url-parameters" ], function(urlParams) {
alert(urlParams.get("lang"));
});
Como se puede observar, se importa el módulo url-parameters en la variable urlParams y posteriormente
se llama a la función urlParams.get pasando como parámetro el nombre del parámetro de la URL cuyo valor
queremos obtener. Esta función retornará null en caso de que el parámetro no exista.
13.7 Servicio hola mundo
El presente manual muestra cómo crear un nuevo servicio que nos devuelve un mensaje “hola mundo” en texto plano
o XML, en función de un parámetro.
La primera tarea consiste en modificar el fichero web.xml para añadir un nuevo Servlet:
<servlet>
<servlet-name>holamundo-servlet</servlet-name>
<servlet-class>org.fao.unredd.portal.HolaMundoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>holamundo-servlet</servlet-name>
<url-pattern>/holamundo</url-pattern>
</servlet-mapping>
El código anterior asocia el servlet holamundo-servlet con la URL /holamundo y lo implementa con la clase
org.fao.unredd.portal.HolaMundoServlet. Ahora sólo es necesario implementar dicha clase:
package org.fao.unredd.portal;
import javax.servlet.http.HttpServlet;
public class HolaMundoServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
}
La
única
particularidad
del
código
javax.servlet.http.HttpServlet.
anterior
es
que
el
servlet
debe
extender
a
El atributo estático serialVersionUID no tiene otro objeto que evitar un warning y es totalmente irrelevante para
el portal.
Si hemos hecho todo correctamente será posible, previo reinicio del servidor, acceder a la URL
http://localhost:8080/unredd-portal/holamundo y obtener un error 405:
método
no permitido.
Nótese que el mensaje es distinto si accedemos a una URL inexistente, como
http://localhost:8080/unredd-portal/holamundonoexiste, donde obtenemos un 404: no
encontrado.
Esto quiere decir que el servlet está bien instalado. Sólo hace falta implementar el método GET, que es el que se está
pidiendo el navegador:
package org.fao.unredd.portal;
import java.io.IOException;
import
import
import
import
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
public class HolaMundoServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
}
Ahora, el servidor debe devolver una página en blanco, pero no debe dar un error. Se llega así al punto en el que
leeremos el parámetro y en función de este devolveremos un XML o texto plano:
package org.fao.unredd.portal;
import java.io.IOException;
import
import
import
import
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
public class HolaMundoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String outputformat = req.getParameter("outputformat");
resp.setCharacterEncoding("utf-8");
if ("xml".equals(outputformat)) {
resp.setContentType("application/xml");
resp.getWriter().write("<response>hola mundo</response>");
} else {
resp.setContentType("text/plain");
resp.getWriter().write("hola mundo");
}
}
}
13.8 Servicio configuración (1 de 2)
En este manual se presenta la forma de implementar un servicio para visualizar algunos aspectos de la configuración, en
concreto la lista de módulos que componen el cliente. En una segunda parte se mostrará cómo realizar modificaciones
a la configuración.
Como se prentende mostrar un servicio, es necesario crear un servlet modificando el web.xml:
<servlet>
<servlet-name>module-list-servlet</servlet-name>
<servlet-class>org.fao.unredd.portal.ModuleListServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>module-list-servlet</servlet-name>
<url-pattern>/moduleList</url-pattern>
</servlet-mapping>
y creando el fichero Java correspondiente:
package org.fao.unredd.portal;
import java.io.IOException;
import
import
import
import
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
public class ModuleListServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
}
En este caso, el servlet debe acceder a la propiedad client.modules de portal.properties:
[...]
info.layerUrl=http://demo1.geo-solutions.it/diss_geoserver/gwc/service/wms
client.modules=layers,communication,iso8601,error-management,map,banner,toolbar,time-slider,layer-lis
map.centerLonLat=24, -4
[...]
Dicha propiedad se puede obtener directamenten via el método getPropertyAsArray de la clase
org.fao.unredd.portal.Config. Una instancia de esta clase se puede encontrar en el ServletContext y
se puede recuperar así:
Config config = (Config) getServletContext().getAttribute("config");
Utilizando la librería net.sf.json se puede codificar la lista de módulos como JSON y devolver el resultado:
package org.fao.unredd.portal;
import java.io.IOException;
import java.io.PrintWriter;
import
import
import
import
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
import net.sf.json.JSONArray;
public class ModuleListServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Config config = (Config) getServletContext().getAttribute("config");
JSONArray json = new JSONArray();
String[] modules = config.getPropertyAsArray("client.modules");
for (String moduleName : modules) {
json.add(moduleName);
}
resp.setContentType("application/json");
resp.setCharacterEncoding("utf8");
PrintWriter writer = resp.getWriter();
writer.write(json.toString());
}
}
13.9 Servicio configuración (2 de 2)
En el manual anterior se muestra un servicio que devuelve un array JSON con los módulos activos en el cliente. En
este manual se dará la opción de modificar esta lista de módulos eliminando un módulo mediante una petición web.
De nuevo se crea un servlet modificando el web.xml:
<servlet>
<servlet-name>module-removal-servlet</servlet-name>
<servlet-class>org.fao.unredd.portal.ModuleRemovalServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>module-removal-servlet</servlet-name>
<url-pattern>/moduleRemoval</url-pattern>
</servlet-mapping>
y creando el fichero Java correspondiente:
package org.fao.unredd.portal;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
import
import
import
import
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
public class ModuleRemovalServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
}
En este caso, en lugar de obtener la propiedad client.modules del fichero portal.properties es necesario
modificarla, lo cual se consigue fácilmente reescribiendo el fichero completo:
[...]
info.layerUrl=http://demo1.geo-solutions.it/diss_geoserver/gwc/service/wms
client.modules=layers,communication,iso8601,error-management,map,banner,toolbar,time-slider,layer-lis
map.centerLonLat=24, -4
[...]
Para ello son de utilidad:
• la clase java.util.Properties, capaz de leer y escribir ficheros de propiedades
• el método getPortalPropertiesFile de la clase org.fao.unredd.portal.Config, que devuelve la ubicación del fichero.
Para la lectura de un fichero de propiedades es necesario crear un InputStream que acceda al fichero:
Properties properties = new Properties();
FileInputStream inputStream = new FileInputStream(
config.getPortalPropertiesFile());
properties.load(inputStream);
inputStream.close();
De forma análoga, la escritura requiere de un OutputStream:
FileOutputStream outputStream = new FileOutputStream(
config.getPortalPropertiesFile());
properties.store(outputStream, null);
outputStream.close();
Para la eliminación del módulo, se procederá a convertirlo en el un ArrayList, de fácil modificación, para luego
regenerar la lista de elementos:
String modules = properties.getProperty("client.modules");
String[] moduleArray = modules.split(",");
ArrayList<String> moduleList = new ArrayList<String>();
Collections.addAll(moduleList, moduleArray);
moduleList.remove(moduleName);
properties.put("client.modules", StringUtils.join(moduleList, ','));
Finalmente el servlet quedaría así:
package org.fao.unredd.portal;
import
import
import
import
import
import
java.io.FileInputStream;
java.io.FileOutputStream;
java.io.IOException;
java.util.ArrayList;
java.util.Collections;
java.util.Properties;
import
import
import
import
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
public class ModuleRemovalServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Config config = (Config) getServletContext().getAttribute("config");
String moduleName = req.getParameter("moduleName");
Properties properties = new Properties();
// Lectura del fichero
FileInputStream inputStream = new FileInputStream(
config.getPortalPropertiesFile());
properties.load(inputStream);
inputStream.close();
// Eliminación del módulo
String modules = properties.getProperty("client.modules");
String[] moduleArray = modules.split(",");
ArrayList<String> moduleList = new ArrayList<String>();
Collections.addAll(moduleList, moduleArray);
moduleList.remove(moduleName);
properties.put("client.modules", StringUtils.join(moduleList, ','));
// Escritura del fichero
FileOutputStream outputStream = new FileOutputStream(
config.getPortalPropertiesFile());
properties.store(outputStream, null);
outputStream.close();
}
}
Nótese que no se devuelve ningún contenido pero que en cualquier caso, cuando el código del servlet se ejecuta sin
error, al cliente le llegará un código HTML “200 OK” indicando que la operación fue satisfactoria.
Comunicación con el cliente
El servlet anterior parte de la base de que las peticiones que se hagan van a ser satisfactorias, se va a eliminar el
módulo, etc. Pero en la realidad esto no es la norma general. ¿Qué sucede si la petición no incluye el parámetro
moduleName? ¿Y si el valor no se corresponde con ninguno de los módulos existentes? ¿Qué pasa si el fichero
portal.properties ha sido eliminado?
El estándar HTML define una serie de códigos que pueden ayudar en la comunicación de estas condiciones excepcionales:
• Ok (200): Ejecución satisfactoria.
• Bad Request (400): La petición no pudo ser entendida por el servidor. Aquí se puede indicar que el nombre del
módulo no se encontró o que no fue especificado el parámetro. Es posible acompañar el código con un mensaje
descriptivo.
• Internal server error (500): Adecuado para indicar errores graves, irrecuperables, como un bug en el código o
que el fichero portal.properties no existe!
La clase org.fao.unredd.portal.ErrorServlet es la encargada de gestionar los errores que se producen
en el sistema. La única característica especial que tiene es que está implementada de tal manera que si se lanza una
excepción org.fao.unredd.portal.StatusServletException, el código que se pasa como parámetro
será el código que se le devuelva al cliente. Además, es posible especificarle a esta instrucción el mensaje que se
enviará al cliente.
Por ejemplo, en caso de que se desee enviar un código 400 cuando el parámetro moduleName no esté presente se
procedería así:
if (moduleName == null) {
throw new StatusServletException(400, "El parámetro moduleName es obligatorio");
}
El segundo parámetro se enviaría codificado en un documento JSON, para que el cliente que realice
la llamada pueda leerlo y presentarlo al usuario convenientemente.
Así pues, si se accede a la URL
http://localhost:8080/unredd-portal/moduleRemoval (sin el parámetro) se obtendrá como resultado un código 400 y el siguiente documento:
{
"message": "El parámetro moduleName es obligatorio"
}
Teniendo esto en cuenta, el servlet anterior se podría escribir así:
package org.fao.unredd.portal;
import
import
import
import
import
import
java.io.FileInputStream;
java.io.FileOutputStream;
java.io.IOException;
java.util.ArrayList;
java.util.Collections;
java.util.Properties;
import
import
import
import
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
public class ModuleRemovalServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Config config = (Config) getServletContext().getAttribute("config");
String moduleName = req.getParameter("moduleName");
if (moduleName == null) {
throw new StatusServletException(400,
"El parámetro moduleName es obligatorio");
}
Properties properties = new Properties();
// Lectura del fichero
try {
FileInputStream inputStream = new FileInputStream(
config.getPortalPropertiesFile());
properties.load(inputStream);
inputStream.close();
} catch (IOException e) {
throw new StatusServletException(500,
"Error grave en el servidor. Contacte al administrador");
}
// Eliminación del módulo
String modules = properties.getProperty("client.modules");
String[] moduleArray = modules.split(",");
ArrayList<String> moduleList = new ArrayList<String>();
Collections.addAll(moduleList, moduleArray);
if (!moduleList.remove(moduleName)) {
throw new StatusServletException(400,
"El módulo especificado no existe");
}
properties.put("client.modules", StringUtils.join(moduleList, ','));
// Escritura del fichero
try {
FileOutputStream outputStream = new FileOutputStream(
config.getPortalPropertiesFile());
properties.store(outputStream, null);
outputStream.close();
} catch (IOException e) {
throw new StatusServletException(500,
"Error grave en el servidor. Contacte al administrador");
}
}
}
Decodificación en el cliente
Por último, cabe destacar que el módulo communication.js escucha un evento ajax que permite realizar llamadas a nuestro servidor y que en caso de error leería el atributo message del documento JSON generado y lo
mostraría al usuario.
El siguiente módulo hace la petición para eliminar el módulo banner cuando se pulsa un botón:
define([ "message-bus", "botonera" ], function(bus, botonera) {
botonera.newButton("remove banner", function() {
bus.send("ajax", {
url : "moduleRemoval?moduleName=banner",
success : function(indicators, textStatus, jqXHR) {
alert("módulo eliminado con éxito");
},
errorMsg : "No se pudo eliminar el módulo"
});
});
});
La primera vez debe funcionar correctamente, pero la segunda debe fallar porque el módulo banner ya no está presente.
Como la comunicación se realiza via el módulo communication con el evento ajax, en caso de error el propio
módulo lee el mensaje y lo muestra al usuario.
13.10 Conexión a base de datos
Conexión a base de datos en Java
Cuando queremos conectar a la base de datos desde un servicio Java tenemos que utilizar la API JDBC (Java DataBase
Connectivity) de Java.
En general, el código para conectar a una base de datos en Java es el siguiente:
Class.forName("org.postgresql.Driver");
Connection connection = DriverManager.getConnection(
"jdbc:postgresql://hostname:port/dbname","username", "password");
...
connection.close();
Warning:
En el código anterior estamos conectando a una base de datos PostgreSQL, para lo cual instanciamos el driver org.postgresql.Driver y conectamos usando la URL propia de PostgreSQL
jdbc:postgresql://hostname:port. Estos dos aspectos cambiarán en función del tipo de base de datos
a la que estemos conectando.
Primero, instanciamos el driver para que se autoregistre en el DriverManager y poder invocar después el método
getConnection para obtener la conexión. Por último, es necesario cerrar la conexión.
Por medio del objeto de tipo Connection podremos obtener instancias de Statement, con las que se pueden
enviar instrucciones SQL al servidor de base de datos.
Conexión a base de datos desde una aplicación web
Partimos de un servicio configurado de esta manera en el descriptor de despliegue:
<!-- database example -->
<servlet>
<servlet-name>example-db</servlet-name>
<servlet-class>org.fao.unredd.portal.ExampleDBServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>example-db</servlet-name>
<url-pattern>/example-db</url-pattern>
</servlet-mapping>
y que implementará su funcionalidad en el método GET:
package org.fao.unredd.portal;
import java.io.IOException;
import
import
import
import
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
public class ExampleDBServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//...
}
}
Aunque sería posible poner el código anterior en el método doGet, no es recomendable debido a que la creación de
una conexión es siempre un proceso costoso.
Configuración conexión en el descriptor de despliegue
Para evitar crear una conexión cada vez, tendremos que configurar el contenedor de aplicaciones, Tomcat en este
ejemplo, para que gestione las conexiones por nosotros. Para ello, tenemos que darle a Tomcat la información necesaria
para conectar modificando dos ficheros.
El primero es el fichero context.xml que existe en el directorio de configuración del servidor conf. Ahí declararemos
un recurso llamado “jdbc/mis-conexiones” que incluirá todos los datos necesarios para conectar: url, usuario, etc.:
<Resource name="jdbc/mis-conexiones" auth="Container" type="javax.sql.DataSource"
driverClassName="org.postgresql.Driver" url="jdbc:postgresql://192.168.0.18:5432/geoserverdat
username="nfms" password="unr3dd" maxActive="20" maxIdle="10"
maxWait="-1" />
El otro fichero a modificar es el descriptor de despliegue web-fragment.xml del plugin que estamos desarrollando
(ver Estructura proyectos plugin), donde añadiremos una referencia al recurso anterior, “jdbc/mis-conexiones”:
<resource-ref>
<description>Application database</description>
<res-ref-name>jdbc/mis-conexiones</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
Note: Al ejecutar una aplicación en Tomcat desde Eclipse se crea un proyecto “Servers”, que contiene una entrada
para el servidor que estamos utilizando y que en el caso de Tomcat incluye los ficheros de configuración, entre otros el
fichero context.xml donde configuramos el recurso. La manera más sencilla de acceder al fichero context del
servidor es a través de dicho proyecto.
Una vez estos ficheros han sido modificados ya no tenemos que preocuparnos de realizar la conexión porque Tomcat
las gestiona por nosotros. Pero, ¿cómo podemos obtener una de estas conexiones gestionadas por Tomcat?
El código Java cambia ligeramente, ya que ahora se obtiene un objeto de tipo java.sql.DataSource que es el
que nos proporciona las conexiones:
InitialContext context;
DataSource dataSource;
try {
context = new InitialContext();
dataSource = (DataSource) context
.lookup("java:/comp/env/jdbc/mis-conexiones");
} catch (NamingException e) {
throw new ServletException("Problema en la configuración");
}
try {
Connection connection = dataSource.getConnection();
// ...
connection.close();
} catch (SQLException e) {
throw new ServletException("No se pudo obtener una conexión");
}
try {
context.close();
} catch (NamingException e) {
// ignore
}
Si sutituímos la línea que contiene los puntos suspensivos por código que haga algo más interesante con la conexión,
podemos devolver un JSON con el array de nombres que haya en una tabla:
package org.fao.unredd.portal;
import
import
import
import
java.io.IOException;
java.sql.Connection;
java.sql.ResultSet;
java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import
import
import
import
import
import
import
javax.naming.InitialContext;
javax.naming.NamingException;
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.sql.DataSource;
import net.sf.json.JSONSerializer;
public class ExampleDBServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
InitialContext context;
DataSource dataSource;
try {
context = new InitialContext();
dataSource = (DataSource) context
.lookup("java:/comp/env/jdbc/mis-conexiones");
} catch (NamingException e) {
throw new ServletException("Problema en la configuración");
}
ArrayList<String> provincias = new ArrayList<String>();
try {
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet result = statement
.executeQuery("SELECT name_1 FROM gis.arg_adm1");
while (result.next()) {
provincias.add(result.getString("name_1"));
}
resp.setContentType("application/json");
JSONSerializer.toJSON(provincias).write(resp.getWriter());
connection.close();
} catch (SQLException e) {
throw new ServletException("No se pudo obtener una conexión", e);
}
try {
context.close();
} catch (NamingException e) {
throw new ServletException("No se pudo liberar el recurso");
}
}
}
La clase DBUtils
Conexiones existentes
Como se puede ver en http://nfms4redd.org/tmp/ref/install/portal.html, el portal incorpora ya una conexión a una base
de datos que se deberá configurar a nivel del contenedor de aplicaciones (Tomcat).
La referencia a esa conexión está configurada en el web-fragment.xml de core, que todo plugin debe incluir
como dependencia (y por tanto, todo plugin puede utilizar):
<resource-ref>
<description>Application database</description>
<res-ref-name>jdbc/unredd-portal</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
Como se puede observar, el nombre es “jdbc/unredd-portal” por lo que con esta información, y usando la clase DBUtils
vista anteriormente, sería posible reescribir el servlet anterior de la siguiente manera y sin tocar ningún fichero de
configuración:
package org.fao.unredd.portal;
import
import
import
import
import
import
java.io.IOException;
java.sql.Connection;
java.sql.ResultSet;
java.sql.SQLException;
java.sql.Statement;
java.util.ArrayList;
import
import
import
import
import
import
import
javax.naming.InitialContext;
javax.naming.NamingException;
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.sql.DataSource;
import net.sf.json.JSONSerializer;
public class ExampleDBServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
final ArrayList<String> provincias = new ArrayList<String>();
try {
DBUtils.processConnection("unredd-portal", new DBUtils.DBProcessor() {
@Override
public void process(Connection connection) throws SQLException {
Statement statement = connection.createStatement();
ResultSet result = statement
.executeQuery("SELECT name_1 FROM gis.arg_adm
while (result.next()) {
provincias.add(result.getString("name_1"));
}
}
});
} catch (PersistenceException e) {
throw new ServletException("No se pudo obtener una conexión", e);
}
resp.setContentType("application/json");
JSONSerializer.toJSON(provincias).write(resp.getWriter());
}
}
13.11 Cómo crear un nuevo plugin
Cuando queremos realizar una implementación que se pueda utilizar fácilmente en portales existentes hay que empaquetar las funcionalidades en cuestión en un plugin.
Creación del proyecto para el plugin
Para ello hay que crear un nuevo proyecto con Maven. Escribiendo desde la línea de comandos:
$ mvn archetype:generate
Dicho comando inicia un asistente que nos permitirá crear un proyecto fácilmente. Primero nos preguntará por el tipo
de proyecto y versión del plugin de Maven. Nos valen los valores por defecto por lo que sólo pulsaremos INTRO un
par de veces:
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 497: INTRO
Choose org.apache.maven.archetypes:maven-archetype-quickstart version:
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
6: 1.1
Choose a number: 6: INTRO
A continuación nos preguntará por el groupId y artifactId, que no es otra cosa que una manera de identificar el plugin.
En este ejemplo usaremos respectivamente “org.fao.unredd” y “holamundo”:
Define value for property 'groupId': : org.fao.unredd
Define value for property 'artifactId': : holamundo
Luego nos preguntará versión y paquete de Java que queremos crear. Podemos aceptar las opciones por defecto
pulsando INTRO en cada caso:
Define value for property 'version':
Define value for property 'package':
1.0-SNAPSHOT: : INTRO
org.fao.unredd: : INTRO
Por último nos pedirá confirmación. Pulsaremos INTRO de nuevo:
Confirm properties configuration:
groupId: org.fao.unredd
artifactId: holamundo
version: 1.0-SNAPSHOT
package: org.fao.unredd
Y: :
[INFO] ---------------------------------------------------------------------------[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quic
[INFO] ---------------------------------------------------------------------------[INFO] Parameter: groupId, Value: org.fao.unredd
[INFO] Parameter: packageName, Value: org.fao.unredd
[INFO] Parameter: package, Value: org.fao.unredd
[INFO] Parameter: artifactId, Value: holamundo
[INFO] Parameter: basedir, Value: /tmp
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] project created from Old (1.x) Archetype in dir: /tmp/holamundo
[INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS
[INFO] -----------------------------------------------------------------------[INFO] Total time: 4:16.288s
[INFO] Finished at: Wed Nov 05 10:10:44 CET 2014
[INFO] Final Memory: 15M/108M
[INFO] ------------------------------------------------------------------------
Tras este proceso, Maven nos reporta BUILD SUCCESS, que quiere decir que el proyecto fue creado con éxito y está
en un directorio con el mismo nombre que el artifactId, es decir, “holamundo”.
Configuración en Eclipse
Para empezar a escribir código, tendremos que importar el proyecto en algún entorno de desarrollo, como Eclipse.
Para ello hacemos clic con el botón derecho en el Project Explorer > Import > Import...
En el diálogo que aparece hay que decirle a Eclipse que el proyecto que queremos importar es un proyecto Maven.
Así, habrá que seleccionar Maven > Existing Maven Projects
A continuación hay que seleccionar el directorio que Maven acaba de crear y finalizar el asistente.
Para finalizar la importación en eclipse tendremos que crear el directorio src/main/resources que no viene
creado por defecto pero que es donde se incluirán todos los desarrollos para la parte cliente (ver Estructura proyectos
plugin). Una vez creado habrá que hacer clic derecho en el proyecto y seleccionar Maven > Update project, tras lo
cual src/main/resources aparecerá como un directorio de código:
Desarrollo de un módulo
Ahora que tenemos el proyecto en Eclipse podemos crear un módulo hola mundo. Los módulos tienen que estar en el
directorio nfms/modules en src/main/resources por lo que tendremos que crear ese directorio. Dentro de
ese directorio podemos crear el módulo, como se ve en la imagen:
Reutilización del módulo
Por último, queremos que nuestro plugin se incluya en alguna aplicación, por ejemplo demo. Esto es tan fácil como
incluir nuestro plugin como dependencia de demo. Para ello abrimos el pom.xml de demo e incluimos una sección
<dependency> adicional con los datos que introdujimos en nuestro plugin al inicio del manual:
Ahora sólo queda ejecutar demo en un servidor Tomcat y ver el resultado:
Warning: Es posible que el plugin no aparezca inicialmente por problemas de refresco, se recomienda clicar con
el botón derecho uno de los proyectos y seleccionar Maven > Update project seleccionando en el diálogo que
aparece todos los proyectos implicados (plugins y aplicación).
13.12 Cómo crear una nueva aplicación
Creación del proyecto
Cuando el objetivo es crear una aplicación que agrupe uno o más plugins existentes tenemos que crear un tipo de
proyecto distinto (ver Estructura proyectos aplicación para información sobre los artefactos que tiene que tener un
proyecto tal).
Al igual que en el caso de crear un nuevo plugin, lo primero es crear un nuevo proyecto maven. El proceso es idéntico
a Creación del proyecto para el plugin sólo que utilizaremos “mi-app” como artifactId.
La configuración de dicho proyecto en Eclipse es también realizada de forma idéntica al caso de los plugins: Configuración en Eclipse.
Selección de los plugins que componen la aplicación
Una vez el proyecto está creado, es necesario especificar los plugins que van a utilizarse en la aplicación. Para ello
habrá que modificar el fichero pom.xml incluyendo los elementos <dependency> de core, el cargador de plugins,
y de base, plugin principal:
<dependency>
<groupId>org.fao.unredd</groupId>
<artifactId>core</artifactId>
<version>3.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.fao.unredd</groupId>
<artifactId>base</artifactId>
<version>3.1-SNAPSHOT</version>
</dependency>
Además, para que Maven pueda descargar estas dependencias, hay que especificar algunos repositorios de librerías:
<pluginRepositories>
<pluginRepository>
<id>nfms4redd</id>
<name>nfms4redd maven repository</name>
<url>http://maven.nfms4redd.org/</url>
</pluginRepository>
</pluginRepositories>
<repositories>
<repository>
<id>osgeo</id>
<name>Open Source Geospatial Foundation Repository</name>
<url>http://download.osgeo.org/webdav/geotools/</url>
</repository>
<repository>
<id>nfms4redd</id>
<name>nfms4redd maven repository</name>
<url>http://maven.nfms4redd.org/</url>
</repository>
<repository>
<id>EclipseLink</id>
<url>http://download.eclipse.org/rt/eclipselink/maven.repo</url>
</repository>
<repository>
<id>geosolutions</id>
<name>GeoSolutions public maven repository</name>
<url>http://maven.geo-solutions.it/</url>
</repository>
</repositories>
El fichero pom.xml quedaría de la siguiente manera:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instan
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0
<modelVersion>4.0.0</modelVersion>
<groupId>org.fao.unredd</groupId>
<artifactId>mi-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mi-app</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<pluginRepositories>
<pluginRepository>
<id>nfms4redd</id>
<name>nfms4redd maven repository</name>
<url>http://maven.nfms4redd.org/</url>
</pluginRepository>
</pluginRepositories>
<repositories>
<repository>
<id>osgeo</id>
<name>Open Source Geospatial Foundation Repository</name>
<url>http://download.osgeo.org/webdav/geotools/</url>
</repository>
<repository>
<id>nfms4redd</id>
<name>nfms4redd maven repository</name>
<url>http://maven.nfms4redd.org/</url>
</repository>
<repository>
<id>EclipseLink</id>
<url>http://download.eclipse.org/rt/eclipselink/maven.repo</url>
</repository>
<repository>
<id>geosolutions</id>
<name>GeoSolutions public maven repository</name>
<url>http://maven.geo-solutions.it/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.fao.unredd</groupId>
<artifactId>core</artifactId>
<version>3.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.fao.unredd</groupId>
<artifactId>base</artifactId>
<version>3.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
Ejecución de la aplicación desde eclipse
Para la ejecución del proyecto como aplicación web dentro de Eclipse tenemos que realizar dos configuraciones adicionales.
Lo primero es configurar el proyecto para que Eclipse entienda que es una aplicación web. Para ello hay que modificar
el elemento packaging del fichero mi-app/pom.xml como se puede ver en el listado anterior correspondiente al
pom.xml, estableciendo el valor a “war”. Tras editar el fichero habrá que clicar en el proyecto con botón derecho y
seleccionar Maven > Update project.
A continuación es necesario proporcionar a la aplicación un directorio de
proporciona a la aplicación información sobre las capas del mapa, etc.
el
que
hay
en
demo/src/main/webapp/WEB-INF/default_config
mi-app/src/main/webapp/WEB-INF/default_config.
configuración, que
Podemos tomar
y
copiarlo
en
Por último, para ejecutar la aplicación tendremos que operar como se muestra con demo en el punto Ejecución del
portal en tomcat desde Eclipse, pero con el proyecto mi-app que acabamos de crear.
Empaquetado
Warning: Para que el proceso funcione es necesario que exista el descriptor de despliegue de aplicaciones JEE, el
fichero src/main/webapp/WEB-INF/web.xml. Bastaría con crear ese fichero con el siguiente contenido:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web</web-app>
Para realizar el empaquetado tenemos que ejecutar el comando mvn package en el directorio mi-app. Esto también se puede hacer desde Eclipse haciendo clic con el botón derecho en el proyecto mi-app y seleccionando Run
As > Maven Build. En la ventana que aparece hay que especificar “package” en “Goals”, como se puede ver en la
siguiente imagen:
Al pinchar en el botón Run, Maven se ejecutará y mostrará por la consola el resultado. Cuando el proceso se termina
con éxito se obtiene el fichero .war en el directorio target del proyecto y un mensaje similar a éste:
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
--- maven-war-plugin:2.2:war (default-war) @ mi-app --Packaging webapp
Assembling webapp [mi-app] in [/home/fergonco/temp/howtoworkspace/mi-app/target/mi-app-1.0-SNA
Processing war project
Copying webapp resources [/home/fergonco/temp/howtoworkspace/mi-app/src/main/webapp]
Webapp assembled in [172 msecs]
Building war: /home/fergonco/temp/howtoworkspace/mi-app/target/mi-app-1.0-SNAPSHOT.war
WEB-INF/web.xml already added, skipping
-----------------------------------------------------------------------BUILD SUCCESS
-----------------------------------------------------------------------Total time: 3.299s
Finished at: Thu Nov 06 11:40:09 CET 2014
[INFO] Final Memory: 12M/172M
[INFO] ------------------------------------------------------------------------
Warning: Si la aplicación tiene como dependencia un plugin que hemos desarrollado nosotros, es necesario que
dicho plugin esté disponible para Maven, lo cual se consigue ejecutando el goal “install” en dicho plugin.
Empaquetado con optimización
Cuando una aplicación tiene muchos módulos y librerías Javascript, hojas de estilo CSS, etc. la carga puede ser un
poco lenta. Para acelerar esto se puede configurar Maven para que realice un proceso de optimización y combine todos
estos ficheros en uno sólo.
Primero, hay que introducir la siguiente sección en el pom.xml de mi-app tras la sección
<dependencies></dependencies>:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
<executions>
<execution>
<id>unpack-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/requirejs
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.fao.unredd</groupId>
<artifactId>jwebclient-analyzer-maven-plugin</artifactId>
<version>4.0.1</version>
<executions>
<execution>
<id>generate-buildconfig</id>
<phase>prepare-package</phase>
<goals>
<goal>generate-buildconfig</goal>
</goals>
<configuration>
<mainTemplate>${project.build.directory}/requirejs/ma
<webClientFolder>${project.build.directory}/requirejs
<buildconfigOutputPath>${project.build.directory}/bui
<mainOutputPath>${project.build.directory}/requirejs/
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>ro.isdc.wro4j</groupId>
<artifactId>wro4j-maven-plugin</artifactId>
<version>1.7.6</version>
<executions>
<execution>
<phase>prepare-package</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
<configuration>
<wroManagerFactory>ro.isdc.wro.maven.plugin.manager.factory.Configura
<extraConfigFile>${basedir}/src/main/config/wro.properties</extraConf
<targetGroups>portal-style</targetGroups>
<minimize>true</minimize>
<contextFolder>${basedir}/target/requirejs/nfms/</contextFolder>
<destinationFolder>${basedir}/src/main/webapp/optimized/</destination
<wroFile>${basedir}/src/main/config/wro.xml</wroFile>
</configuration>
</plugin>
<plugin>
<groupId>com.github.bringking</groupId>
<artifactId>requirejs-maven-plugin</artifactId>
<version>2.0.4</version>
<executions>
<execution>
<phase>prepare-package</phase>
<goals>
<goal>optimize</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- optional path to a nodejs executable -->
<!--<nodeExecutable> -->
<!--/opt/nodejs/node -->
<!--</nodeExecutable> -->
<!-- path to optimizer json config file -->
<configFile>${project.build.directory}/buildconfig.js</configFile>
<fillDepsFromFolder>${project.build.directory}/requirejs/nfms/modules
<!-- optional path to optimizer executable -->
<!--<optimizerFile> -->
<!--${basedir}/src/main/scripts/r.js -->
<!--</optimizerFile> -->
<!-- optional parameters to optimizer executable -->
<optimizerParameters>
<parameter>optimize=uglify</parameter>
<!--<parameter>baseUrl=${baseDir}</parameter> -->
</optimizerParameters>
<!-- Whether or not to process configFile with maven filters. If you
use this option, some options in your configFile must resolve
paths (see below) -->
<filterConfig>
true
</filterConfig>
<!-- Skip requirejs optimization if true -->
<skip>
false
</skip>
</configuration>
</plugin>
</plugins>
</build>
Esta configuración hace referencia a dos ficheros existentes en el directorio src/main/config,
wro.properties y wro.xml. El contenido de wro.properties será:
preProcessors=cssDataUri,cssImport,semicolonAppender,cssMinJawr
postProcessors=
Mientras que para wro.xml pondremos:
<?xml version="1.0" encoding="UTF-8"?>
<groups xmlns="http://www.isdc.ro/wro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.isdc.ro/wro wro.xsd">
<group name="portal-style">
<css>/modules/**.css</css>
<css>/styles/**.css</css>
</group>
</groups>
Una vez realizada esta configuración, podemos generar el WAR de nuevo.
Aparentemente este WAR es igual que el anterior, pero a diferencia de aquél, justo antes de empaquetar se habrán generado dos ficheros:
src/main/webapp/optimized/portal.js y
src/main/webapp/optimized/portal-style.css, que incluyen respectivamente todo el código
Javascript y todos los estilos de los plugins usados por la aplicación.
Cuando despleguemos tal WAR, podremos seleccionar poniendo la variable de entorno MINIFIED_JS a “true” el
modo optimizado, que cargará el portal bastante más rápido.
13.13 Cómo dar licencia libre a un plugin
Una vez hemos realizado nuestro plugin y tenemos claro que queremos publicarlo con una licencia para que la gente
lo reutilice de forma libre, tenemos que seguir los siguientes pasos:
1. Elegir una licencia libre
2. Aplicar la licencia a nuestro proyecto
Elegir una licencia libre
Existen muchísimas licencias de software libre por lo que para este tutorial nos limitaremos al ámbito de las más
utilizadas. Además, para facilitar la aplicación de la licencia nos limitaremos también a las soportadas por el
maven-license-plugin.
Para obtener un listado de las licencias basta con ejecutar el objetivo license-list del plugin Maven license:
$ mvn license:license-list
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------------------------------------------------------[INFO] Building Maven Stub Project (No POM) 1
[INFO] -----------------------------------------------------------------------[INFO]
[INFO] --- license-maven-plugin:1.8:license-list (default-cli) @ standalone-pom --[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform depen
[INFO] Available licenses :
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
agpl_v3
apache_v2
bsd_2
bsd_3
cddl_v1
epl_only_v1
epl_v1
eupl_v1_1
fdl_v1_3
gpl_v1
gpl_v2
gpl_v3
lgpl_v2_1
lgpl_v3
mit
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
GNU Affero General Public License (AGPL) version 3.0
Apache License version 2.0
BSD 2-Clause License
BSD 3-Clause License
COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
Eclipse Public License - v 1.0
Eclipse Public + Distribution License - v 1.0
European Union Public License v1.1
GNU Free Documentation License (FDL) version 1.3
GNU General Public License (GPL) version 1.0
GNU General Public License (GPL) version 2.0
GNU General Public License (GPL) version 3.0
GNU General Lesser Public License (LGPL) version 2.1
GNU General Lesser Public License (LGPL) version 3.0
MIT-License
-----------------------------------------------------------------------BUILD SUCCESS
-----------------------------------------------------------------------Total time: 2.228s
Finished at: Thu Mar 05 06:46:17 CET 2015
Final Memory: 10M/144M
------------------------------------------------------------------------
Cada licencia tiene unas particularidades, pero está fuera del ámbito de este tutorial analizar estas diferencias. Para el
siguiente punto, aplicaremos a nuestro proyecto la licencia GPLv3 (GNU General Public License (GPL) version 3.0),
que es la misma que tiene el portal de FAO.
Aplicar la licencia a nuestro proyecto
Ahora que tenemos nuestro proyecto y sabemos la licencia que queremos que tenga, GPLv3, ¿cómo se la aplicamos?
Para aplicar esta licencia tenemos que incluir en la raíz de nuestro proyecto un fichero LICENSE.txt con el texto de la
licencia y en cada fichero de código, una cabecera similar a la siguiente:
/*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
NOMBRE DEL PROYECTO
Copyright (C) AÑO ORGANIZACIÓN
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
*
*/
Esto, que sería bastante tedioso de realizar a mano, lo hace de forma automática el plugin license de Maven, pero
para que pueda llevar estas acciones a cabo es necesario configurarlo en el pom.xml con los datos propios de nuestro
proyecto, que se usarán para personalizar el texto de la cabecera.
Vamos a suponer que partimos de un pom.xml tan sencillo como éste:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instan
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>mi.organizacion</groupId>
<artifactId>pluginParaX</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>PluginParaX</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Para configurar nuestro plugin tendremos que añadir una sección build/plugins, dentro de la cual pondremos la
configuración del plugin license:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instan
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>mi.organizacion</groupId>
<artifactId>pluginParaX</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>PluginParaX</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Aquí va la configuración del plugin license -->
</plugins>
</build>
</project>
Para la configuración del plugin usaremos algo similar a este elemento plugin. Nótese que en un fichero XML, los
símbolos <!-- y --> sirven para abrir y cerrar comentarios:
<plugin>
<!-- Identificación del plugin license -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<!-- Configuración para el plugin anterior -->
<configuration>
<!-- Año en el que se publica el plugin -->
<inceptionYear>2015</inceptionYear>
<!-- Organización que publica el código -->
<organizationName>FAO</organizationName>
<!-- Nombre del proyecto como aparecerá en la licencia -->
<projectName>PluginParaX</projectName>
<!-- licencia escogida de la lista del punto anterior -->
<licenseName>gpl_v3</licenseName>
<!-directorios donde se encuentran los ficheros de código
cuya cabecera queremos editar
-->
<roots>
<root>src/main/java</root>
<root>src/main/resources/nfms/modules</root>
<root>src/main/resources/nfms/styles</root>
</roots>
<!-Patrones que indentifican los ficheros cuya cabecera
queremos editar en los directorios especificados por
"roots"
-->
<includes>
<include>*.java</include>
<include>*.js</include>
<include>*.css</include>
</includes>
</configuration>
</plugin>
Finalmente, insertando el elemento plugin anterior en el pom.xml, el fichero quedaría de la siguiente manera:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instan
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>mi.organizacion</groupId>
<artifactId>pluginParaX</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>PluginParaX</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- Identificación del plugin license -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<!-- Configuración para el plugin anterior -->
<configuration>
<!-- Año en el que se publica el plugin -->
<inceptionYear>2015</inceptionYear>
<!-- Organización que publica el código -->
<organizationName>FAO</organizationName>
<!-- Nombre del proyecto como aparecerá en la licencia -->
<projectName>PluginParaX</projectName>
<!-- licencia escogida de la lista del punto anterior -->
<licenseName>gpl_v3</licenseName>
<!-directorios donde se encuentran los ficheros de código
cuya cabecera queremos editar. Especificaremos los
directorios donde se encuentran los ficheros Java y
los módulos Javascript.
-->
<roots>
<root>src/main/java</root>
<root>src/main/resources/nfms/modules</root>
<root>src/main/resources/nfms/styles</root>
</roots>
<!-Patrones que indentifican los ficheros cuya cabecera
queremos editar en los directorios especificados por
"roots". Especificaremos ficheros Java, Javascript y
las hojas de estilo CSS.
-->
<includes>
<include>*.java</include>
<include>*.js</include>
<include>*.css</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Una vez la configuración está realizada, ya sólo queda realizar las dos acciones necesarias: añadir el texto de la licencia
y las cabeceras. Para añadir la licencia podemos ejecutar el objetivo update-project-license:
$ mvn license:update-project-license
[INFO] Scanning for projects...
[WARNING]
[WARNING] Some problems were encountered while building the effective model for mi.organizacion:plugi
[WARNING] 'build.plugins.plugin.version' for org.codehaus.mojo:license-maven-plugin is missing. @ lin
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed proj
[WARNING]
[INFO]
[INFO] -----------------------------------------------------------------------[INFO] Building PluginParaX 1.0-SNAPSHOT
[INFO] -----------------------------------------------------------------------[INFO]
[INFO] --- license-maven-plugin:1.8:update-project-license (default-cli) @ PluginParaX --[INFO] Will create or update license file [gpl_v3] to /tmp/pluginParaX/LICENSE.txt
[INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS
[INFO] -----------------------------------------------------------------------[INFO] Total time: 1.738s
[INFO] Finished at: Thu Mar 05 08:48:02 CET 2015
[INFO] Final Memory: 9M/144M
[INFO] ------------------------------------------------------------------------
que añadirá el fichero LICENSE.txt en la raíz del proyecto.
Y por último, para añadir las cabeceras, usaremos el objetivo update-file-header:
$ mvn license:update-file-header
[INFO] Scanning for projects...
[WARNING]
[WARNING] Some problems were encountered while building the effective model for mi.organizacion:Plugi
[WARNING] 'build.plugins.plugin.version' for org.codehaus.mojo:license-maven-plugin is missing. @ lin
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed proj
[WARNING]
[INFO]
[INFO] -----------------------------------------------------------------------[INFO] Building PluginParaX 1.0-SNAPSHOT
[INFO] -----------------------------------------------------------------------[INFO]
[INFO] --- license-maven-plugin:1.8:update-file-header (default-cli) @ PluginParaX --[INFO] Will search files to update from root /tmp/pluginParaX/src/main/java
[INFO] Will search files to update from root /tmp/pluginParaX/src/main/resources/nfms/modules
[INFO] Scan 4 files header done in 27.798ms.
[INFO]
* add header on 4 files.
[INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS
[INFO]
[INFO]
[INFO]
[INFO]
[INFO]
-----------------------------------------------------------------------Total time: 1.874s
Finished at: Thu Mar 05 08:50:36 CET 2015
Final Memory: 10M/144M
------------------------------------------------------------------------
Por último, sólo nos queda poner nuestro plugin en un lugar accesible para que lo puedan encontrar otros desarrolladores.
13.14 Publicación de un plugin en un repositorio Maven
Para que los plugins desarrollados puedan ser reutilizados en otros desarrollos, ya sean propios o de terceras partes, es
necesario subirlos a un repositorio Maven. Para ello modificaremos dos ficheros:
• El pom.xml de nuestro plugin
• El fichero de configuración settings.xml de Maven
Un repositorio Maven es una estructura de directorios que contiene los plugins y sus dependencias organizados por
groupId y artifactId. El repositorio principal de Maven se encuentra en http://central.maven.org/maven2/ y contiene
las librerías de uso general. Para los plugins y las librerías más específicas del portal REDD, FAO pone a disposición
el servidor maven.nfms4redd.org. Es este servidor el que podemos utilizar para subir nuestro plugin a un sitio
accesibile.
Si hemos desarrollado un plugin, seguro que tenemos Maven ya configurado para que se descargue las dependencias
del portal del repositorio de FAO. Pero ahora habrá que configurarlo para que, además de descargar los plugins, pueda
subirlos.
Para subir los plugins al repositorio de FAO hay que utilizar el servicio ftp://maven.nfms4redd.org/repo.
Esto se configura en el pom.xml de nuestro plugin mediante dos elementos. El primero es un elemento dentro de
<build> que dice a Maven que vamos a acceder por FTP:
<build>
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ftp</artifactId>
<version>2.3</version>
</extension>
</extensions>
</build>
El segundo le dice a Maven la URL del servidor:
<distributionManagement>
<repository>
<id>nfms4redd</id>
<url>ftp://maven.nfms4redd.org/repo</url>
<uniqueVersion>false</uniqueVersion>
</repository>
</distributionManagement>
Como no queremos compartir el usuario y contraseña para acceder al FTP, lo que hacemos es especificar sólo un
identificador, en este caso nfms4redd, al que haremos referencia desde el fichero de configuración de Maven
settings.xml para asignar a ese id el usuario y contraseña. El fichero settings.xml se encuentra en el
directorio .m2 del “HOME” del usuario:
En dicho fichero, crearemos un elemento server con el mismo identificador que el especificado en el pom.xml del
plugin. Y en ese elemento pondremos el usuario y contraseña.
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>nfms4redd</id>
<username>XXXXX</username>
<password>XXXXX</password>
</server>
</servers>
</settings>
Una vez la configuración está terminada, es necesario pedir a Maven que ejecute la tarea (goal) deploy.
Warning: Es posible que la ejecución de Maven falle si se ejecuta desde Eclipse y tenemos configurado sólo un
JRE y no un JDK. En tal caso la solución es simple: instalar un JDK y utilizarlo por defecto.