Archivo de la categoría: APEX

Select All / Unselect All Checkbox in Interactive Report Header

Cómo añadir un control en los IR de APEX para marcar / desmarcar todas las casilla de tipo «checkbox» del listado.

Interactive Report + Column confirm button

Introducción

Vamos a convertir una columna de un Interactive Report (IR) de APEX en un botón de cambio de estado con confirmación previa.

Partimos de un IR convencional, sobre una copia de la tabla EMP a la que hemos añadido una columna ACTIVE con el valor por defecto ‘YES’:

apex_ir_emp_step01

Las imágenes que nos servirán para el botón se han cargado en el apartado de recursos compartidos de la aplicación (Shared Components) asociadas a la aplicación:

apex_ir_emp_step02

Nota: Es importante acordarse de descargar los scripts de instalación y desintalación de cada una de las imágenes para incluirlos en el apartado «Supporting Objects». De esta forma, las imágenes se incorporarán de manera automática a los procesos de exportación / importación de la aplicación.

Paso 1: Preparamos el enlace

En nuestro ejemplo vamos a reemplazar la columna EMP.ACTIVE del informe por un botón que nos permita cambiar el valor entre ‘YES’ y ‘NO’ pulsando sobre él. Además, incluiremos una petición de confirmación previa para evitar que se ejecute por error.

El primer paso es crear una función PL/SQL que nos facilite la generación del código HTML con el que vamos reemplazar la información actual de la columna. Las siguientes líneas nos servirán:

En este ejemplo se construye un enlace HTML con las siguientes características:

  • Ejecuta una función JavaScript para solicitar la confirmación del usuario.
  • Incluye una etiqueta de imagen con la referencia al fichero cargado en Shared Componens y que está asociado al estado actual del registro:
    • La imagen «switch_on_2.gif» para los valores ‘YES’.
    • La imagen «switch_off_2.gif» para el resto de valores.

Para indicar la ubicación de la imagen se utiliza el parámetro «p_img_prefix». Por norma general, las imágenes utilizadas en una aplicación APEX se suelen alojar en uno de estos tres lugares:

  • #WORKSPACE_IMAGES#: Cargadas en Shared Components y asociadas al espacio de trabajo.
  • #APP_IMAGES#: Cargadas en Shared Components y asociadas a la aplicación.
  • #IMAGE_PREFIX#: Ruta definida para las imágenes estáticas. Por defecto es «/i/».

Una vez compilada la función, debemos modificar la consulta del informe para invocarla en lugar de la columna ACTIVE:

apex_ir_emp_step03

Al guardar estos cambios y volver a cargar el informe de empleados debería aparecernos algo similar a lo siguiente:

apex_ir_emp_step04

¡Vamos bien! Tal y como estaba configurada la columna ACTIVE en el informe, APEX no puede interpretar el código HTML que está generando la función GET_LINK_CHNG_ACTIVE. Pero esto tiene fácil solución. Solo tenemos que editar los atributos del informe y sustituir el valor «Display as a text» por «Standard Report Column» en la definición de esta columna:

apex_ir_emp_step05

Una vez aceptados los cambios, si volvemos a cargar el informe deberíamos encontrarnos con una imagen como la siguiente:

apex_ir_emp_step05

Paso 2: Control de la acción

De momento, si pulsamos sobre la imagen no ocurrirá nada. El código HTML que hemos generado invoca una función JavaScript que aun no hemos declarado. Para que nuestra aplicación tenga constancia de ella deberemos acceder a las propiedades de la página y en el espacio «Function and Global Variable Declaration» incluir el código necesario:

function chng_confirm(p_empno, p_active) {
  apex.confirm(
    'Change EmpNO '+p_empno+' Active status?',
    {request:'CHNG_STAT',
     set:{'P25_EMPNO_CHNG_STAT':p_empno,
          'P25_ACTIVE_CHNG_STAT':p_active}
    }
  );
}

Esta función se encarga de solicitar la confirmación al usuario y, en caso de obtenerla, ejecutar el submit de la página asignando los siguientes valoresde:

  • Item P25_EMPNO_CHNG_STAT = p_empno
  • Item P25_ACTIVE_CHNG_STAT = p_active
  • REQUEST = ‘CHNG_STAT’

Estos Items no existen aun en nuestra página. Así que vamos a crearlos con las siguientes características:

apex_ir_emp_step06

Nota: Es importante tener en cuenta que la propiedad «Value Protected» de cada un de estos Items debe ser ‘NO’ para permitir al código JavaScript modificar su valor después de que el usuario confirme la acción.

Paso 3: Modificación de los datos

Por último, deberemos crear el proceso que se encargue de ejecutar la acción y modifique los datos pertinentes. Es recomendable que este proceso se encuentre compilado en base de datos y, de esta forma, separar las operaciones sobre el modelo de datos del apartado visual. El código podría ser como el que sigue:

create procedure SET_ACTIVE (
    p_empno  in number,
    p_active in varchar2
    )
as
begin

  if p_empno is not null and p_active is not null
  then

    update emp_test set
      active = decode(p_active,
                      'YES', 'NO',
                      'NO', 'YES',
                      active)
    where empno = p_empno
      and active = p_active;

  end if;

end SET_ACTIVE;

Por último, necesitamos indicar al motor de APEX cómo y en qué momento debe ejecutar este bloque de código. De esto se va a encargar un proceso de página, o Page Processing, con las siguientes características:

  • Type: «PL/SQL anonymous block»
  • Process point: «On Submit and Before Computation»
  • Conditions: «Request = Expression 1» y con la expresión igual a la cadena’CHNG_STAT’ que hemos indicado anteriormente en nuestra función JavaScript.

El código PL/SQL a ejecutar en este caso es muy breve:

begin
  SET_ACTIVE(:P25_EMPNO_CHNG_STAT, :P25_ACTIVE_CHNG_STAT);
end;

Si volvemos a cargar el informe, ya podremos activar y desactivar registros de empleados tras aceptar la confirmación que se nos va a solicitar.

apex_ir_emp_confirm

Anuncio publicitario

APEX y ORA-02020: too many database links in use

Introducción

El entorno APEX en el que se produjo el error está compuesto por varios espacios de trabajo (o workspaces) independientes entre sí. Cada espacio de trabajo cuenta con un enlace de base de datos para conectarlo a la instancia de la que extrae los datos de trabajo. De esta forma, tanto los desarrolladores APEX como los usuarios finales de las aplicaciones web solo tienen acceso a los datos a través de la instancia en la que se ejecuta APEX y se puede controlar de forma centralizada la seguridad en lo relativo al acceso a los datos.

Esta arquitectura nos permite interponer una capa entre el acceso web de los usuarios finales y las bases de datos de producción, además de descargar a estas bases de datos de la tarea de generar el contenido web y controlar la navegación de los usuarios. En definitiva, separamos por capas cada tarea:

  • Navegación: servidor web (cualquiera de los soportados por APEX).
  • Generación de contenidos HTML: instancia APEX.
  • Repositorios de datos: instanicas de producción (bases de datos remotas).

Arquitectura APEX con acceso remoto a los datos

Error ORA-02020: too many database links in use

A medida que los usuarios accedían a los distintos espacios de trabajo iba aumentando el número de conexiones que se establecían entre la instancia de base de datos que aloja APEX y las distintas instancias a las que se conecta para obtener los datos. Esto no debía constituir ningún problema ya que se utlizaba un servidor web con un pool de conexiones para gestionar las peticiones HTML y las conexiones a través de data base links para acceder a los datos. El entorno debería responder bien ante la concurrencia de conexiones propia de los entornos web. Pero algo no iba bien. Empecé a recibir correos de los usuarios quejándose de que les aparecía el siguiente error en sus navegadores:

ORA-20001: get_dbms_sql_cursor error ORA-02020: too many database links in use

Nos pusimos a investigar las causas el problema.

Primer intento: aumentar el número de conexiones permitido

Lo primero fue acudir a la documentación oficial sobre errores Oracle. Y en ella se puede leer lo siguiente:

ORA-02020: too many database links in use
Cause: The current session has exceeded the INIT.ORA open_links maximum.
Action: Increase the open_links limit, or free up some open links by committing or rolling back the transaction and canceling open cursors that reference remote databases.

Estaba claro que se abrían muchos enlaces de base de datos. Nuestra arquitectura lo propiciaba. Pero por el mismo motivo, metiante el pool de conexión, el sistema debería compartir conexiones y nos sorprendía que se acabara produciendo este error. La primera medida fue seguir la recomendación y aumentar el número de conexiones permitidas que define el parámetro open_links. Durante unos días dejamos de recibir correos de los usuarios con la incidencia, pero la tregua no duró mucho. Con el paso de los días y el aumento en el uso de las aplicaciones APEX instaladas, los correos con el error por exceso de conexiones abiertas volvieron a llegar.

Estaba claro que aumentar el límite de conexiones no era la solución. Siempre se puede poner un número máximo de conexiones desorbitado, y no preocuparse más. Pero no es una solución muy adecuada si queremos crear un entorno robusto y seguro. La segunda alternativa que propone la documentación oficial del error tampoco nos daba muchas pistas. Nuestras conexiones desde APEX a las instancias remotas eran casi en su totalidad conexiones para consulta de datos: vistas locales que lanzaban selects sobre el modelo de datos remoto. No abríamos transacciones remotas y, por lo tanto, descartamos la necesidad de incluir sentencias commit o rollback.

Segundo intento: apex_util.close_open_db_links

Así que seguimos investigando. Esta vez acudimos a My Oracle Support (MOS), antiguo Metalink, y allí encotramos la nota Doc ID 1950408.1 de la que resumo el apartado que utilizamos para aplicarlo a nuestro entorno:

Oracle REST Data Services (ORDS) / APEX Listener

1. Add the following entries to the defaults.xml associated with your ORDS / APEX Listener configuration.

<entry key="procedure.postProcess">apex_util.close_open_db_links</entry>
<entry key="procedure.preProcess">apex_util.close_open_db_links</entry>

2. Restart ORDS / the APEX Listener where it is deployed, so the new entries will be recognized.

Parecía algo sencillo de aplicar, que atacaba exactamente el problema que teníamos y que lo hacía mediante un paquete PL/SQL oficial de APEX. Nos pusimos manos a la obra: incluimos las entradas en el fichero de configuración y realizamos una primera batería de pruebas. Esta vez parecía que todo iba a funcionar bien. Pero pronto observamos que en algunos apartados de las aplicaciones que íbamos probando aparecían nuevos errores aunque, esta vez, no se mostraban en pantalla. Tras un examen del log del servidor web encontramos las siguientes trazas:

oracle.dbtools.rt.web.WebErrorResponse internalError
ORA-02080: database link is in use
[...]
java.sql.SQLSyntaxErrorException: ORA-02080: database link is in use
[...]

El proceso apex_util estaba intentando cerrar conexiones cuando todavía se encontraban en uso. Como ya hemos comentado, las aplicaciones que ejecuta nuestra instancia APEX apenas generan inserciones, actualizaciones o borrado de datos en las instancias remotas. Por lo tanto, no deberían quedarse transacciones abiertas. ¡Qué estaba pasando!

Tercer y último intento: una modificación del MOS ID 1950408.1

Volvimos a buscar en MOS y Google para intentar acotar el origen del problema. Las vistas que utilizan nuestras aplicaciones APEX para acceder a las tablas de las instancias remotas, a pesar de ser una select, parece que si generan una transacción y esta impide el cierre de la conexión si no ejecutamos previamente un commit o un rollback. Es un efecto de utilizar data base links: la instancia remota asigna un bloque de undo a la conexión abierta y no se libera hasta recibir un comando de finalización de transacción.

La alternativa de invocar el cierre de los enlaces de base de datos desde un procedimiento dentro de cada aplicación APEX (un procedimiento de aplicación o un procedimiento que se ejecutara en las páginas que abrieran conexiones remotas) había sido descartada desde el principio porque nos obligaba a modificar el código de cada una de las aplicaciones y, además, identificar el nombre de cada database link en los distintos entornos. Resultaba poco práctico y difícil de mantener. Pero ante el problema de las transacciones abiertas y los nuevos errores que generaba la segunda solución que aplicamos, decidimos montar un escenario de test. Y funcionó a la perfección.

De todas formas, el cierre de los enlaces desde el interior de cada aplicación APEX seguía sin convencernos. Así que decidimos hacer una prueba más. Desactivamos el proceso de aplicación con el cierre de conexiones remotas que habíamos creado en el escenario de pruebas. Y volvimos a activar el parámetro en el fichero defaults.xml del servidor web. Pero en esta ocasión solo lo activamos para el preprocesado:

<entry key="procedure.preProcess">apex_util.close_open_db_links</entry>

Con esta configuración por fin logramos solucionar el problema de los database links abiertos sin tener que modificar cada una de las aplicaciones APEX.