lunes, 5 de julio de 2021

Añadir componente personalizado en la zona derecha de Forms

ADVERTENCIA: Lo que voy a comentar en esta entrada utiliza funciones no documentadas de Forms. Funciona con la versión actual 12.2.1.4.0, pero no puedo ofrecer ninguna garantía de que en futuras versiones funcione o lo haga de esta forma.

Oracle Forms utiliza Java para ejecutarse en el cliente y proporciona herramientas para poder integrar componentes Java personalizados (Bean Area), precisamente estos componentes son los que voy a mostrar como a través de ellos alterar el comportamiento nativo de Forms.

Lo primero es conocer que el tipo de layout Java que usa Forms.

BorderLayout: https://docs.oracle.com/javase/7/docs/api/java/awt/BorderLayout.html


Donde:

  • North: Menú superior y botonera horizontal.
  • West: Botonera vertical.
  • South: Barra de estado.
  • Center: Ventana del programa Forms.
Del BorderLayout no han usado la zona East. Veamos cómo podemos utilizarla para lo que nos interese.

Una vez inicialicemos la zona "East" quedará visible para todos los programas Forms que tengamos abiertos, por lo que va a ser una zona común a toda la aplicación.

Para inicializarla vamos a utilizar un Bean Area que estará oculto en un programa, para ello añadimos al lienzo un ITEM de tipo "Bean Area", en "Implementation Class" indicaremos una clase de implementación que más adelante veremos en detalle y ponemos el item en el lienzo con tamaño 0, 0 para que no sea visible para el usuario.


Lo importante de esto es lo que se hace en la clase de implementación. Para que según se inicialice el bean area también inicialice la zona "East" del BorderLayout haremos lo siguiente:

package com.test.efm;

import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JLabel;
import oracle.forms.handler.IHandler;
import oracle.forms.ui.VBean;

public class BeanExample extends VBean {
  transient IHandler handler = null;
  private java.awt.Frame frame = null;

  public void init(IHandler handler) {
    super.init(handler);
    this.handler = handler;
    this.frame = this.handler.getApplet().getFrame();
    JLabel eastLabel = new JLabel("EAST", JLabel.CENTER);
    eastLabel.setOpaque(true);
    eastLabel.setBackground(Color.YELLOW);
    eastLabel.setPreferredSize(new Dimension(190,768));
    this.frame.add("East", eastLabel);
    frame.validate();
  }
}


Con handler.getApplet().getFrame() hacemos referencia al frame con el BorderLayout y luego con frame.add("East", xxxx); añadimos el componente.

Resultado:

Video ejemplo:




viernes, 2 de julio de 2021

NAME_IN y COPY con vitaminas

Para acceder a los valores de los campos del registro en el que se encuentra el cursor se puede hacer simplemente usando :bloque.campo.

Si se hace desde una unidad de programa de una PLL habría que usar el Bult-in NAME_IN que se usa de de la siguiente forma: NAME_IN('BLOQUE.CAMPO'). Además de permitir usarse en desde una librería también nos permite hacer código genérico en tiempo de ejecución.

De forma similar, para asignar un valor habría que usar COPY. Se usaría de la siguiente forma: COPY('VALOR A ASIGNAR', 'BLOQUE.CAMPO');

Cuando se quiere acceder a valores de campos de otros registros distintos al actual es donde entra en juego la propiedad DATA_VALUE de Get_Item_Instance_Property y Set_Item_Instance_Property.

Para acceder al valor de un campo del registro anterior al actual se haría con:

Get_Item_Instance_Property('BL.CAMPO', TO_NUMBER(:system.cursor_record) - 1, DATA_VALUE);

Para cambiar el valor de un campo del registro anterior: 

Set_Item_Instance_Property('BL.CAMPO', TO_NUMBER(:system.cursor_record) - 1, DATA_VALUE, 'VALOR A ASIGNAR');

Por ejemplo, si queremos colorear un registro basándonos en registros anteriores, se podría hacer de la siguiente forma. POST-QUERY:

DECLARE
  PROCEDURE pinta_campo(p_campo VARCHAR2) IS
  BEGIN
   Set_Item_Instance_Property(p_campo,
                              CURRENT_RECORD, 
                              VISUAL_ATTRIBUTE, 'AMARILLO');
  END;
BEGIN
  IF :system.trigger_record = 1 OR
    Get_Item_Instance_Property('USER_TAB_COLUMNS.TABLE_NAME',
       TO_NUMBER(:system.trigger_record) - 1,
       DATA_VALUE) != :user_tab_columns.table_name THEN
    pinta_campo('USER_TAB_COLUMNS.TABLE_NAME');
    pinta_campo('USER_TAB_COLUMNS.COLUMN_NAME');
    pinta_campo('USER_TAB_COLUMNS.DATA_TYPE');
  END IF;
END;



FMB de ejemplo: giip_data_value.fmb

jueves, 21 de mayo de 2015

Extraer contenido de contenedores OLE

Una funcionalidad muy útil en versiones de Forms 6i o anteriores, era la de almacenar archivos en contenedores OLE, para ello bastaba con tener un campo en el bloque asociado a un campo BLOB de la base de datos y ya era posible guardar en él archivos mediante la aplicaciones con soporte OLE2, por ejemplo WORD, EXCEL, PDF, ...

El problema es que desde Forms 9i esta funcionalidad ya no existe, por lo que hay que buscar alternativas, lo normal es a través de implementar la carga / descarga de archivos usando la librería WEBUTIL.

Aparte de tener que cambiar la implementación, el principal problema con el que me he encontrado ha sido la necesidad de migrar de un sistema a otro debido a que los archivos almacenados en un contenedor OLE tienen un envoltorio que hace imposible que se abran directamente, es necesario abrirlos a través de ese contenedor, por lo que había que hacerlo de forma manual uno a uno desde el programa.

La solución que he encontrado a este problema es mediante la aplicación 7zip para Windows que es capaz de separar el envoltorio del contenido (con la de Linux no funciona).


El PDF se encuentra en el archivo CONTENTS dentro de la carpeta "Tenant Object", como se puede observar, en este caso el envoltorio ocupa mucho más que el propio PDF. En algunos casos, puede llamarse "[1]Ole10Native", en ese caso hay que modificar el script que descomprime con 7zip.

IMPORTANTE: El código que voy a poner a continuación, debes usarlo bajo tú responsabilidad y después de haber hecho una copia de seguridad de los datos que vas a procesar, no me hago responsable de ninguna pérdida de datos por la utilización de este código.

Vamos a ver como extraer los archivos de un campo BLOB a un directorio de Oracle. Vamos a crear un directorio llamado BLOB_TEMP (en el propio servidor de base de datos) y lo mapeamos a una ruta:

CREATE DIRECTORY BLOB_TEMP as 'c:\temp\pdfs';
GRANT READ, WRITE ON DIRECTORY BLOB_TEMP TO USUARIO;

Exportamos a ese directorio el contenido del campo BLOB de la tabla

DECLARE
  CURSOR cur_pdfs IS
     SELECT i. archivo campo_blob, i.rowid || '.pdf.7z' nombre_archivo
       FROM tabla i
      WHERE DBMS_LOB.GETLENGTH(i.archivo) > 0
        AND DBMS_LOB.INSTR(i.archivo, HEXTORAW('25504446')) > 0;

  blob_length     INTEGER;
  out_file        UTL_FILE.file_type;
  v_buffer        RAW(32767);
  chunk_size      PLS_INTEGER;
  blob_position   PLS_INTEGER;
BEGIN
  FOR reg IN cur_pdfs LOOP
    blob_position := 1;
    chunk_size := 32767;
    blob_length := DBMS_LOB.getlength(reg.campo_blob);
    out_file := UTL_FILE.fopen('BLOB_TEMP', reg.nombre_archivo, 'wb', chunk_size);

    WHILE blob_position <= blob_length LOOP
      IF blob_position + chunk_size - 1 > blob_length THEN
        chunk_size := blob_length - blob_position + 1;
      END IF;

      DBMS_LOB.READ(reg.campo_blob, chunk_size, blob_position, v_buffer);
      UTL_FILE.put_raw(out_file, v_buffer, TRUE);
      blob_position := blob_position + chunk_size;
    END LOOP;

    UTL_FILE.fclose(out_file);
  END LOOP;
END;
/

Una vez tenemos los archivos, extraemos el contenido con un script, por ejemplo, le llamamos extraepdf.cmd con el siguiente contenido.

for %%f in (*.7z) do (
"C:\Program Files\7-Zip\7z.exe" e "%%f" "Tenant Object\CONTENTS"
rename CONTENTS %%f.pdf
del %%f
)?

Luego, los volvemos a cargar, sobreescribiendo el archivo en el mismo campo de la tabla.

DECLARE
  CURSOR cur_pdfs IS
     SELECT i.rowid, i.rowid || '.pdf.7z.pdf' nombre_archivo, i.archivo
       FROM tabla i
      WHERE DBMS_LOB.GETLENGTH(i.archivo) > 0
        AND DBMS_LOB.INSTR(i.archivo, HEXTORAW('25504446')) > 0;

  l_bfile       BFILE;
  v_resultado   VARCHAR2(30) := 'OK';
  v_archivo     BLOB;
BEGIN
  FOR reg IN cur_pdfs LOOP
    l_bfile := BFILENAME('BLOB_TEMP', reg.nombre_archivo);

    IF DBMS_LOB.fileexists(l_bfile) = 1 AND NVL(DBMS_LOB.getlength(l_bfile), 0) != 0 THEN
      DBMS_LOB.createtemporary(v_archivo, TRUE);
      DBMS_LOB.fileopen(l_bfile, DBMS_LOB.file_readonly);
      DBMS_LOB.loadfromfile(v_archivo, l_bfile, DBMS_LOB.getlength(l_bfile));
      DBMS_LOB.fileclose(l_bfile);
      
      UPDATE tabla i
         SET archivo = v_archivo
       WHERE rowid = reg.rowid;
       
      COMMIT;
      UTL_FILE.fremove('BLOB_TEMP', reg.nombre_archivo);
    END IF;
  END LOOP;
END;
/

domingo, 8 de febrero de 2015

Reports Builder 11g en Windows 64 bits

Por lo general, al intentar arranca Reports Builder en un Windows de 64 bits nos dará el siguiente error: "REP-50125: rwbuilder.conf:java.lang.NullPointerException"



Vamos a ver como solucionarlo suponiendo que el directorio base de Oracle es C:\Oracle (si fuese otro las rutas cambian).

Editar el archivo: C:\Oracle\Middleware\asinst_1\config\reports\bin\reports.bat

Borrar la línea: set TNS_ADMIN=%ORACLE_INSTANCE%\config

Editar el archivo: C:\Oracle\Middleware\Oracle_FRHome1\bin\rwbuilder.bat

Cambiar: $$Instance.directory$$ por: C:\Oracle\Middleware\asinst_1
Cambiar: $$Instance.oracle_home$$ por: C:\Oracle\Middleware\Oracle_FRHome1

Para arrancar el Report Builder ejecutaremos un archivo de comandos con código similar al siguiente: ReportsBuilderF11g.cmd. (Las rutas pueden variar según donde estén las plls o instalado Oracle):

SET CLASSPATH=C:\Oracle\Middleware\Oracle_FRHome1\forms\j2ee\frmsrv.jar;C:\Oracle\Middleware\Oracle_FRHome1\jlib\ldapjclnt11.jar;C:\Oracle\Middleware\Oracle_FRHome1\jlib\debugger.jar;C:\Oracle\Middleware\Oracle_FRHome1\jlib\ewt3.jar;C:\Oracle\Middleware\Oracle_FRHome1\jlib\share.jar;C:\Oracle\Middleware\Oracle_FRHome1\jlib\utj.jar;C:\Oracle\Middleware\Oracle_FRHome1\jlib\zrclient.jar;C:\Oracle\Middleware\Oracle_FRHome1\reports\jlib\rwrun.jar;C:\Oracle\Middleware\Oracle_FRHome1\forms\java\frmwebutil.jar;C:\Oracle\Middleware\Oracle_FRHome1/jlib/start_dejvm.jar;C:\Oracle\Middleware\Oracle_FRHome1\opmn\lib\optic.jar
SET PATH=C:\Oracle\Middleware\Oracle_FRHome1\bin;C:\Oracle\Middleware\Oracle_FRHome1\jdk\jre\bin\client;C:\Oracle\Middleware\Oracle_FRHome1\jdk\bin
SET REPORTS_PATH=Z:\plls
SET NLS_DATE_FORMAT=DD/MM/YYYY
SET NLS_NUMERIC_CHARACTERS=,.
SET UI_ICON=Z:\iconos
SET NLS_SORT=BINARY
SET TNS_ADMIN=C:\Oracle
SET NLS_LANG=SPANISH_SPAIN.UTF8
start rwbuilder.bat USERID=USUARIO/PASSWORD@CADENA_CONEXION

martes, 25 de marzo de 2014

Generación incorrecta de PDF en Reports 6 en Windows 7 y 8

Al generar archivos desde Reports 6 en PDF en sistemas operativos Windows 7 y Windows 8 está metiendo mal la etiqueta "CreatorDate", abre el paréntesis y sin meter la fecha no lo llega a cerrar. Esto produce que alguna versión de Adobe Reader y de otros visores de PDF no sean capaces de leer el archivo.


Ese mismo PDF generado con el mismo parche de Forms & Reports en un Windows 2003 lo genera correctamente:


Para solucionarlo podemos hacer que no intente meter la línea /CreatorDate modificando la librería RWLIB60.DLL. Para ello necesitaremos un editor Hexadecimal para poder editarla, yo utilizo "HxD - Editor Hexadecimal", que se puede descargar de su página web: http://www.mh-nexus.de.

Abrimos la librería y buscamos (Ctrol + F) el texto CreatorDate


Seleccionamos desde /CreatorDate hasta el punto que está antes de /Author, tal y como se muestra en la siguiente imagen:


Luego hay que ir a Edición -> Rellenar Selección



En la ventana que abre, dejamos 00 en Valores hexadecimales y pulsamos "Aceptar".


Grabamos la librería, y ahora el resultado de la generación con este cambio será el siguiente:




viernes, 17 de enero de 2014

Conexión a webs con Applets de Java sin MANIFEST.MF correcto

Ya ha llegado la actualización 1.7.0_45 de Java. Esta actualización bloquea por defecto todas las aplicaciones que se despliegan desde un navegador que no tengan las últimas directivas de seguridad definidas en el archivo MANIFEST.MF

Para salir del paso, es posible, en el equipo donde corre el navegador añadir excepciones a esa validación. Para ello (en Windows) vamos a Panel de Control -> Programas (Si tenemos activado "Ver por: Categoría) -> Java

En la pestaña "Seguridad" en la sección "Lista de excepciones de sitios" pulsamos sobre el botón "Editar lista de sitios" y añadimos el sitio en que confiamos de que no nos vaya a ejecutar nada peligroso en nuestro equipo.


He puesto como ejemplo "https://www.bbva.es", ya que a día de hoy para validarse con el DNI electrónico usa un applet Java que no tiene el MANIFEST.MF correcto, y lo peor de todo, cuando vas a validar una operación da un error de error de firma del archivo.