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