domingo, 20 de abril de 2008

Incorporando Google Maps en Application Express

Diversos artículos pueden encontrarse en Internet acerca de cómo implementar Google Maps en Oracle Application Express (APEX). Desafortunadamente, la mayoría son algo básicos y se limitan a cargar un mapa dentro de una región, no teniendo interacción con la base de datos.

En esta oportunidad vamos a ver cómo lograr desplegar puntos dinámicamente en un mapa a partir de datos almacenados en una tabla. Únicamente se requieren conocimientos básicos de Application Express, PL/SQL y Javascript.

Este artículo no pretende mostrar todas las funcionalidades de la API de Google Maps, sino resolver el problema puntual de programación en Oracle, luego dependerá de las habilidades Javascript del programador y de conocer a fondo las funcionalidades que brinda esta maravillosa API.

Aclarado esto, comencemos, paso a paso:

Paso 1: Tener APEX instalado.

Lo primero es tener APEX instalado, ya que necesitaremos la URL o IP para el paso siguiente.
Si tienen duda si APEX está instalado en su base de datos, ver mi artículo anterior.

Paso 2: Solicitar una clave en Google Maps.

Ir a la siguiente direccion http://code.google.com/apis/maps/signup.html para registrarnos y solicitar un código que necesitamos para utilizar el servicio. En el registro, se debe ingresar la URL donde será utilizado Google Maps.
Por ejemplo, si utilizamos el espacio gratuito Oracle Apex, podemos ingresar la URL http://apex.oracle.com/pls/otn/. Si se está desarrollando localmente (en la propia máquina) puede registrarse la IP 127.0.0.1, pero cuando finalice el desarrollo habrá que solicitar una nueva clave con la URL de producción, ya que Google requiere que el sitio registrado sea de acceso público.
El código generado será referenciado desde de nuestro script.

Paso 3: Manos a la obra con APEX!

En nuestra nueva aplicación APEX, como primer paso debemos crear una página.

Editar los atributos de la página y en el campo Enfoque de Cursor elegir el valor No enfocar Cursor.

En la sección Cabecera HTML, incluir el siguiente script reemplazando nuestra clave:
<script src="http://maps.google.com/maps?file=api&v=2&key=la_clave_de_google_maps&hl=es"
type="text/javascript"></script>
<script type="text/javascript">

//<![CDATA[

function load() {
if (GBrowserIsCompatible()) {
miMapa();
}
}

//]]>
</script>
En este javascript acabamos de definir la función load, la cual invoca a otra función miMapa que crearemos más adelante.

En el campo Atributo de Cuerpo HTML de Página, pegar lo siguiente:
onload="load();" onunload="GUnload();"
  • Se puede observar cómo en el evento onload de la página, se invoca a la función load incluída en el cabezal HTML.
Hecho esto, guardar los cambios efectuados en la página

Ahora queda la parte interesante, ya que deberemos crear la función miMapa la cual ejecutará la carga del mapa y los puntos de referencia extraídos de la base de datos. Esta función será generada por un bloque anónimo PL/SQL el cual será ejecutado cada vez que se carga la página.
Para este ejemplo, se creó una sencilla tabla geopuntos la cual contiene información sobre los puntos de referencia que queremos desplegar en nuestro mapa:
id_punto     NUMBER(10)    NOT NULL
latitud NUMBER(18,15) NOT NULL
longitud NUMBER(18,15) NOT NULL
descripcion VARCHAR2(100)
  • La precisión de latitud y longitud es adecuada para almacenar coordenadas geográficas de Google Maps.
  • Cada punto puede enriquecerse con valiosa información como pueden ser imagenes, archivos, links, íconos, etc.
  • Se recomienda consultar la extensa documentación y tutoriales que brinda Google Maps en su sitio oficial.
En el área Presentación de Página, en la sección Procesos, vamos a implementar nuestro bloque PL/SQL.
Crear un nuevo proceso de tipo PL/SQL. En Atributos de Proceso, ingresar un nombre cualquiera y elegir el valor En Carga, Antes de Cabecera. Clickear siguiente, y en Proceso, incluir el siguiente bloque:
DECLARE
CURSOR cr_puntos IS select * from puntosmapa;
BEGIN
htp.p('<script type="text/javascript">');
htp.p('//<![CDATA[');

htp.p('function crearMarca(lat, long, descr) {');
htp.p(' var point = new GLatLng(lat,long);');
htp.p(' var opts = {title: descr};');
htp.p(' var mark = new GMarker(point,opts);');
htp.p(' GEvent.addListener(mark, "click", function() {
mark.openInfoWindowHtml(''<div><B>'' + descr + ''</B><BR>Lat: '' + lat +
''<BR>Long: '' + long + ''</div>'');');
htp.p(' });');
htp.p('return mark;');
htp.p('}');

htp.p('function miMapa() {');
htp.p('var map = new GMap2(document.getElementById("map"));');
htp.p('map.addControl(new GLargeMapControl());');
htp.p('map.addControl(new GMapTypeControl());');
htp.p('map.addControl(new GOverviewMapControl());');
htp.p('map.setCenter(new GLatLng(-22.915739,-43.22912), 11);');

-- Recorro cursor e inserto los puntos
FOR punto IN cr_puntos
LOOP
htp.p('var marker = crearMarca('||to_char(punto.latitud,'999.9999999999999999')||','
||to_char(punto.longitud,'999.9999999999999999')||',"'||punto.descripcion||'");');
htp.p('map.addOverlay(marker);');
END LOOP;
htp.p('}');

htp.p('//]]>');
htp.p('</script>');
END;
Este es el punto neurálgico de la aplicación: en este bloque PL/SQL resolvemos la consulta a la base de datos, la creación del mapa y la publicación de los puntos de referencia extraídos. Notar el uso de la función PL/SQL htp.p(). Así como DBMS_OUTPUT.PUT_LINE envia caracteres a la salida en pantalla, la función htp.p envía cadenas de caracteres al browser, y esto precisamente lo que necesitamos ya que queremos construir el Javascript a ser ejecutado en la carga de la página.

Puede apreciarse la sencillez de esta implementación, en pocas líneas crea un mapa, lo centra y carga los controles básicos de zoom. La particularidad en este caso es el FOR LOOP, el cual recorre un cursor definido sobre la tabla, y en cada iteración define una marca "publicando" Javascript en tiempo de ejecución. Cada referencia tiene un toolTip cuando se pasa el mouse por encima y un globo informativo que se abre al clickear sobre la marca.

Y eso es todo, tras guardar el proceso (dejar el resto de los valores por defecto), hemos concluído nuestra aplicación y ya está lista para ser explorada.

Las posibilidades son muchísimas, y cuanto más se profundice sobre la documentación, mejores funcionalidades podremos lograr en APEX.

Un ejemplo implementado
Hice una página de demostración para que puedan ver que realmente funciona. En mi tabla de datos tengo almacenados puntos turísticos de Río de Janeiro, ciudad donde vivo actualmente :)

Para saber las coordenadas geográficas de un punto en la tierra, podemos utilizar por ejemplo la página Geocoder.

Hay mucha documentación y ejemplos disponibles sobre Google Maps y realmente es fácil comenzar a obtener resultados sorprendentes.

Finalmente le agradezco a Emilio Le Mener por su ayuda en Javascript.

Espero que este tutorial haya sido útil y puedan comenzar a implementar sin problemas con APEX y Google Maps.
Ver también:
Google Maps API
Presentación Google Developer Day 2007

viernes, 18 de abril de 2008

Cómo saber si Application Express (APEX) está instalado en Oracle

Ejecutar la siguiente consulta en SQL*Plus con un usuario con rol DBA:

SELECT username
FROM dba_users
WHERE username IN ('FLOWS_010500','FLOWS_010600','FLOWS_020000','FLOWS_020200','FLOWS_030000', 'FLOWS_030100');

Si no se obtiene ninguna fila como resultado, entonces APEX no está instalado en la base de datos.

jueves, 17 de abril de 2008

Oracle libera parches de seguridad para todos sus productos


Este 15 de abril fueron publicados parches para todos los productos Oracle, incluída la base de datos, la E-Business Suite, PeopleSoft, Siebel y otros productos.

En cuanto a la base de datos, los parches abarcan todas las versiones desde la 9.0.1.5, y algunos de los bugs que se corrigen son vulnerabilidades graves que permiten explotar la base de datos aún sin autenticación. Este es un asunto de alerta máxima y no debe demorarse la decisión de actualizar el software. La instalación es sencilla y no toma mucho tiempo.

El Application Server 9i Release 1, version 1.0.2.2 también presenta vulnerabilidades en el JInitiator 1.3.1.14, Enterprise Manager 1.0.2.2 y el Enterprise Portal 9.0.4.3. Estos agujeros de seguridad pueden ser explotados sin necesidad de usuario y password.

Oracle recomienda fuertemente descargar estos patches (gratuitamente desde otn.oracle.com) y aplicarlos en todos los casos para evitar riesgos ya detectados.

Ver también:
Oracle Critical Patch Update Advisory - April 2008

lunes, 14 de abril de 2008

Sobre comentarios y cómo actualizar secuencias

Cuando se realizan cargas de datos en un esquema existente y no se utilizan las secuencias para asignar los valores de llave primaria, se produce un desfasaje entre identificadores y secuencias. Este desfasaje provoca errores de clave primaria ya existente, ya que los valores retornados por secuencia.NEXTVAL, pueden ya existir entre los datos recientemente cargados.

Para sincronizar las secuencias con los identificadores de llave primaria, puede crearse un script en PL/SQL el cual recree cada secuencia con su valor inicial según el máximo identificador de la tabla correspondiente. Para poder realizarlo, necesitamos primero tener una correspondencia entre tabla y secuencia, y en Oracle esta dependencia no existe.

Es una buena oportunidad para comenzar a adoptar la buena práctica de usar comentarios sobre los objetos. Muchas veces, en el proceso de creación de una base de datos, este paso es omitido y considerado innecesario. Supuestamente, los objetos "no necesitan describirse" fundamentandose que el diseño es lo suficientemente explicativo y que los creadores van a permanecer durante todo el proceso de desarrollo, por lo tanto no vale la pena perder el tiempo describiendo los objetos. ¿No suenan estas excusas a holgazanería?

No se deben ignorar estas máximas: 1) el diseñador puede abandonar el proyecto y 2) las personas externas que miren nuestro modelo pueden no entenderlo.

En este sentido, el responsable de base de datos debe velar porque el modelo quede totalmente documentado y que no dependa de las personas que lo crearon, en definitiva que sea lo más auto-explicativo posible.

Alguien puede considerar suficiente que la base de datos quede documentada en un diagrama o en un documento de word, pero ningún esfuerzo es suficiente si podemos hacer un poquito más. Los comentarios sobre objetos son almacenados en el diccionario de datos, y van a ser exportados junto a los objetos cuando estos sean migrados. Sin necesidad de consultar diagramas ni documentos, un programador en apuros puede hacer un DESCRIBE sobre una tabla en SQLPLUS, y saber al instante donde encontrar el dato que busca. Los comentarios ayudan a disipar las dudas más rapido, sobre todo a los que no están empapados en el modelo.

Ahora si, volviendo al tema inicial, ¿cómo relacionamos una tabla con su secuencia? Precisamente, colocando en el comentario de la columna, el nombre de la secuencia que se utiliza:
SQL> COMMENT ON cuentas.id_cuenta IS 'CUENTA_SEQ'
Acabamos de documentar que para la columna id_cuenta de la tabla cuentas, se debe utilizar la secuencia cuenta_seq. Observar que el efecto de esta sentencia es únicamente documental, todavía puede utilizarse la secuencia de la forma que se desee. La ventaja es que ahora podemos consultar la tabla de diccionario dba_col_comments en nuestros scripts.

A modo de nota, mencionamos que tambien se pueden comentar tablas y vistas, con esta sintaxis:
SQL> COMMENT ON cuentas 'Tabla para el registro de cuentas corrientes personales'
Finalmente, el bloque que actualiza las secuencias utilizando los comentarios sobre columnas.
DECLARE
v_max NUMBER(10) := 0;
v_desc varchar2(50);
BEGIN

FOR t IN (SELECT u.table_name,
u.column_name,
uc.comments secuencia
FROM user_tab_columns u,
user_col_comments uc
WHERE u.table_name = uc.table_name
AND u.column_name = uc.column_name
AND u.column_name LIKE 'ID_%')
LOOP
EXECUTE IMMEDIATE 'SELECT MAX(' || t.column_name || ') FROM ' ||
t.table_name INTO v_max;

dbms_output.put_line('Eliminando secuencia '|| t.secuencia||'...');
v_desc := 'DROP SEQUENCE '|| t.secuencia;
EXECUTE IMMEDIATE 'DROP SEQUENCE '|| t.secuencia;

EXECUTE IMMEDIATE 'CREATE SEQUENCE '|| t.secuencia ||' INCREMENT BY
1 START WITH '|| (NVL(v_max,0) + 1);
dbms_output.put_line('Secuencia '|| t.secuencia||' recreada.');

END LOOP;
END;
Se requiere el permiso CREATE SEQUENCE por parte del ejecutor de este script.