jueves, 25 de mayo de 2017

Válvulas - File Adapter

Uso de válvulas en el File Adapter

Existe una funcionalidad de pre-procesamiento de archivos que podemos añadir al File Adapter de la Oracle SOA Suite. Personalmente no la conocía, pero me parece muy práctica y es por eso que decidí compartir esta entrada del blog.

El requerimiento es simple. Tal vez sea común trabajar con archivos de tipo CSV, posicionales, de texto plano, etc. En lo primero que pensamos para resolver una situación donde se involucren archivos es en el File Adapter.

Entre sus características están leer el archivo haciendo uso de streaming, utilizar un NXSD para transformar el contenido de manera nativa a XML, o simplemente no leer el contenido y transportar el archivo hacia algún sitio (file system, FTP).

En esta ocasión, la necesidad es un tanto "fuera de lo común": procesar un archivo de Excel.

Si bien el File Adapter no tiene entre sus capacidades nativas procesar un archivo de este tipo (me refiero a leer el contenido), tampoco es ciencia saber cómo resolver el problema. No existe una forma de transformar un archivo Excel mediante un NXSD.

Una de las opciones que puede pasar por nuestra cabeza es leer el archivo con el File Adapter (sin procesar el contenido) y posteriormente con una actividad Java Embedding procesar el contenido. Sin duda esta es una forma de abordar la solución.

Sin embargo, existe una manera más elegante de resolverlo: las válvulas y pipelines que se pueden crear para el File Adapter.

Flujo del File Adapter


El flujo que normalmente sigue el File Adapter para procesar un archivo de entrada, es el siguiente:

File System > (1) > File o FTP Adapter > (2) > Transformación (NXSD) > (3) > Proceso BPEL o Pipeline de OSB o Mediador

Donde:

(1) - El File Adapter lee el archivo o archivos de la ruta indicada
(2) - El contenido del archivo se traslada a un proceso de traducción donde mediante un NXSD transformamos el contenido a XML. El traductor publica el contenido hacia el SCA (tiempo de ejecución).
(3) - Finalmente, el SCA publica el contenido hacia el engine de servicio (BPEL, OSB, Mediator).

Flujo del File Adapter haciendo uso de Pipelines y Válvulas


En el caso de las válvulas y los pipelines, va a existir un paso intermedio, justo entre los pasos (1) y (2), el resto del flujo es el mismo.

De tal suerte que nuestro flujo de procesamiento quedaría como sigue:


File System >  (1) > File o FTP Adapter > (1A) > Pipeline > (1B) > Válvula > (1C) > File o FTP Adapter (2) > Transformación (NXSD) > (3) > Proceso BPEL o Pipeline de OSB o Mediador

Donde:

(1A) - El File Adapter va a trasladar el contenido hacia un Pipeline, que tiene asociadas válvulas de pre-procesamiento.
(1B) - El Pipeline va a ejecutar las válvulas de pre-procesamiento en el orden que nosotros indiquemos.
(1C) - La última válvula que se ejecute, va a devolver el control al Pipeline, y este a su vez va a devolver el contenido del archivo pre-procesado al FTP Adapter. A partir de ahí, el flujo es el mismo.

Implementación de la válvula


Implementar la válvula es relativamente simple. Vamos a necesitar dos proyectos: uno Java y un compuesto.

Proyecto Java


El proyecto Java contendrá básicamente el código fuente para la conversión de XLS a CSV. Para esto usamos, como dije antes, Apache POI.

La clase implementada es la siguiente:

package excelvalves;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import java.util.Date;
import java.util.Iterator;

import oracle.tip.pc.services.pipeline.AbstractValve;
import oracle.tip.pc.services.pipeline.InputStreamContext;
import oracle.tip.pc.services.pipeline.PipelineException;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

public class ExcelToCsv extends AbstractValve {

    public InputStreamContext execute(InputStreamContext inputStreamContext) throws IOException, PipelineException {
        System.out.println("The valve will begin executing the inputstream");
        // Get the input stream that is passed to the Valve
        InputStream originalInputStream = inputStreamContext.getInputStream();
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        
        // Read workbook into HSSFWorkbook
        Workbook workbook = new HSSFWorkbook(originalInputStream);

        // Read worksheet into HSSFSheet
        Sheet datatypeSheet = workbook.getSheetAt(0);

        // To iterate over the rows
        Iterator<Row> iterator = datatypeSheet.iterator();

        //store the csv string
        StringBuffer data = new StringBuffer();
        
        int i = 0;

        //Loop through rows.
        while (iterator.hasNext()) {
            int j = 0;
            Row currentRow = iterator.next();
            Iterator<Cell> cellIterator = currentRow.iterator();
            while (cellIterator.hasNext()) {
                Cell currentCell = cellIterator.next();
                if (currentCell.toString().length() > 0 && i > 0) {
                    switch (currentCell.getCellType()) {
                    case Cell.CELL_TYPE_BOOLEAN:
                        data.append(currentCell.getBooleanCellValue() + ",");
                        break;

                    case Cell.CELL_TYPE_NUMERIC:
                        DateFormat df = new SimpleDateFormat("yyyyMMdd");
                        Date date = currentCell.getDateCellValue();
                        data.append(df.format(date) + ",");
                        break;

                    case Cell.CELL_TYPE_STRING:
                        data.append(currentCell.getStringCellValue() + ",");
                        break;

                    case Cell.CELL_TYPE_BLANK:
                        data.append("" + ",");
                        break;

                    default:
                        data.append(currentCell + ",");
                    }
                    if (j == 15) {
                        data.append('\n');
                    }
                }
                j++;
            }
            i++;
        }
        System.out.println("snippet from stream: " + data.toString());
        ByteArrayInputStream bin = new ByteArrayInputStream(data.toString().getBytes());
        inputStreamContext.setInputStream(bin);
        System.out.println("done processing the stream in the valve");
        return inputStreamContext;
    }

    @Override
    public void finalize(InputStreamContext inputStreamContext) {
        // TODO Implement this method
    }

    @Override
    public void cleanup() throws PipelineException, IOException {
        // TODO Implement this method


    }
}

Las librerías necesarias para el proyecto son las siguientes:


Lo siguiente es compilar, generar un perfil de despliegue y desplegar el proyecto en un JAR.


Proyecto SOA (compuesto)


Vamos a crear un compuesto con un File Adapter como servicio expuesto. En el asistente de configuración colocar lo siguiente:

Definir la interfaz posteriormente.


Mantener JNDI por default.


Seleccionar operación de lectura (no leer el contenido del archivo).


Leer cualquier archivo (*.*).


Frecuencia de lectura: 10 segundos.


Seleccionar el NXSD con el que convertiremos el CSV a XML.


Finalizar la configuración.


Una vez que tenemos el adaptador listo, vamos a crear el Pipeline. Para esto basta con crear un archivo XML nuevo (ExcelPipeline.xml), debajo de la carpeta SOA:



Este archivo XML va a definir el orden en que se van a ejecutar las válvulas. Aquí lo único que hay que colocar es el nombre de las clases que hemos creado en el proyecto Java (completamente calificadas): en mi caso excelvalves.ExcelToCsv, que fue el nombre que le puse a mi clase donde implementé la válvula. Aquí podemos agregar una o más, dependiendo de nuestras necesidades de implementación.


Finalmente, vamos a agregar en el archivo de configuración del adaptador (JCA), la referencia hacia el Pipeline: <property name="PipelineFile" value="ExcelPipeline.xml"/>




Después de esto, ligamos el adapter a un proceso BPEL. De tal suerte que nuestro compuesto queda de la siguiente forma:


Debajo de la carpeta SCA-INF/lib, debemos colocar el JAR generado de nuestra válvula (el que generamos en el proyecto Java).

Desplegamos el proyecto del compuesto a nuestra infraestructura y lo probamos colocando un archivo de Excel en la ruta que configuramos en nuestro File Adapter.

Deberá iniciarse una instancia y aparecer lo siguiente en nuestros logs:


En Enterprise Manager podremos ver que la instancia se ha ejecutado correctamente:


Los detalles de la traza:


En este caso el BPEL no hace otra cosa sino un assign de la entrada a una variable para posteriormente terminar:


Observamos que los datos provenientes del Excel han sido asignados de manera correcta a la variable:


Comprobamos que todo se haya realizado bien, comparando con los datos de nuestro archivo de Excel:


Resumen


  • Las válvulas pueden servir para pre y/o post procesamiento (archivos de entrada y/o de salida).
  • Las válvulas serán ejecutadas en el orden que definan dentro del pipeline.
  • El pipeline a usar se configura dentro del archivo JCA del adaptador.
  • El pipeline se define en un archivo XML.
  • Las clases de pre y/o post procesamiento se implementan en Java y reciben como entrada un InputStream.
  • Se pueden utilizar frameworks adicionales dentro de las clases Java que implementan la lógica de nuestras válvulas. En mi caso utilicé Apache POI para el procesamiento del archivo Excel (https://poi.apache.org/).
  • El JAR que contiene la implementación de las válvulas se debe incluir en el proyecto (compuesto, OSB) que lo utiliza. Para el caso de un compuesto, lo ponemos debajo de SCA-INF/lib.

No hay comentarios.:

Publicar un comentario