Árvore de páginas

Índice



Consideraciones Generales

La información contenida en este documento tiene el objetivo de demostrar cómo realizar la integración entre TOTVS Fluig Plataforma y aplicaciones externas. Para una comprensión completa de esta información, algunos conocimientos se consideran requisitos previos, incluyendo entre ellos:

  • Visión general del producto TOTVS Fluig Plataforma
  • Visión general de la integración de sistemas
  • JavaScript
  • WebServices
  • SOAP
  • Progress® 4GL

  • Progress® Open App Server
  • Progress® Open Client for Java
  • Datasets (TOTVS Fluig Plataforma)
  • Java™
  • Apache Flex®

En varias partes de este documento se presentarán fragmentos de código en diferentes lenguajes de programación, con el fin de demostrar el uso de las capacidades de integración de TOTVS Fluig Plataforma. Sin embargo, este documento no pretende capacitar al lector en el uso de estas tecnologías más allá de los fines anteriormente descritos, siendo responsabilidad del lector buscar información en profundidad sobre estos lenguajes.

Con el fin de facilitar la comprensión de la información que se presenta y la simulación de los conceptos presentados, los ejemplos citados en este documento utilizan la funcionalidad de Datasets como un ejemplo del uso de las capacidades de integración del producto. Sin embargo, es importante señalar que otros puntos del producto tienen disponibles las mismas características de integración existentes en los Datasets, especialmente las personalizaciones de procesos y formularios.

Visión General

Aunque empíricos, todas las empresas tienen procesos de negocio que permiten que la empresa cumpla con su objetivo, ya sea la prestación de un servicio, la producción de bienes materiales o el comercio de mercancías. Una empresa posee una infinidad de procesos, así cada persona en la organización participa obligatoriamente en al menos uno de estos procesos, y todos ellos comparten información entre sí en algún momento. Los procesos pueden ser formales (como la contratación de un profesional) o informales (como un incentivo para la innovación), críticos (facturación) o satélites (control de envío de tarjetas de cumpleaños).

Con la llegada de las tecnologías de Sistema de Información, varios sistemas comenzaron a apoyar estos procesos de negocio, en especial aquellos considerados más críticos para la operación de la empresa. El mejor ejemplo de ello es la adopción de los sistemas ERP que ofrecen soporte a los procesos de varias áreas de la empresa.

TOTVS Fluig Plataforma pretende ser una plataforma agnóstica de gestión de procesos, documentos e identidades a través de una interfaz de comunicación colaborativa. Esto se puede percibir en mayor o menor grado en cada una de sus funcionalidades, desde las más simples (como la colaboración) hasta las más complejas (como DM y BPM).

No obstante, parte de estos procesos dependen en gran medida de los sistemas de información ya existentes en la empresa y por esta razón, la arquitectura de TOTVS Fluig Plataforma está diseñada para permitir la integración a estos sistemas, permitiendo que los procesos modelados tengan mayor valor agregado.

TOTVS Fluig Plataforma permite tanto el acceso por el producto a los sistemas externos (para consultar o alimentar la información) como también posibilita que otros sistemas se conecten para consultar información o para ejecutar operaciones de transacción.


El principal canal de integración del producto es a través de WebServices, que se están convirtiendo en el estándar más común de integración con cualquier aplicación. A través de ellos, se puede tener acceso a las funcionalidades de TOTVS Fluig Plataforma y ofrecer acceso por medio del producto a aplicaciones externas. Este documento dedica una sección específica a la integración a través de WebServices.

La otra forma de integración es a través de llamadas a Progress® Open AppServer, que se recomienda para usuarios que necesitan integrar TOTVS Fluig Plataforma con aplicaciones desarrolladas en esta plataforma.

WebServices

La integración a través de WebServices utiliza el protocolo SOAP y, al ser un estándar abierto, permite que sistemas desarrollados en plataformas completamente diferentes como Java ™, Microsoft® .Net, C, C ++, PHP, Ruby, Pearl, Python, entre otras, puedan intercambiar información entre sí de manera transparente.

Acceso a WebServices de TOTVS Fluig Plataforma

TOTVS Fluig Plataforma ofrece un conjunto de WebServices que permiten el acceso a la información del producto o la ejecución de tareas, como por ejemplo iniciar solicitudes de procesos. Para tener la lista de los WebServices disponibles, ingrese a: 

http://<host>:<porta>/webdesk/services 

Cada link que se muestra dirige el navegador a la URL de WSDL del servicio. WSDL (Web Servicio Description Language) tiene la descripción del servicio en formato XML y lo utilizan las herramientas de desarrollo para crear componentes que representarán este servicio.

Tenga en cuenta que cada tipo de atributo que se espera, por ejemplo el atributo expirationDate del objeto DocumentDto[] es una fecha, pero cada lenguaje lo interpreta de manera diferente, vea a continuación algunos ejemplos:

  • C#: dateTime
  • Java™: XMLGregorianCalendar
  • Progress®: DATETIME-TZ


Vía Apache Flex®

Al igual que la mayoría de las herramientas de desarrollo, Apache Flex® permite crear stubs para acceder a web services. Estos stubs encapsulan todas las operaciones de empaquetado y desempaquetado de la información del estándar XML para los tipos nativos de la plataforma.

Utilice el paso a paso para visualizar el proceso de creación de los stubs para un servicio ofrecido por TOTVS Fluig Plataforma:

    La creación de los stubs en Flex® se lleva a cabo a través del menú Data, opción Import WebService(WSDL), como muestra la siguiente imagen.

    En la primera ventana, se solicita la carpeta dentro del proyecto actual donde deben generarse los stubs.

    En la siguiente pantalla, se debe informar la dirección de WSDL donde se encuentra el servicio. También se puede definir si se accede desde la estación de cliente o desde el servidor LiveCycle Data Services.

    En la última pantalla, se debe informar el package que se utilizará y cuál es el nombre de la clase principal (ya sugeridos por Flex™ Builder™).

    Una vez finalizado el proceso, Flex ™ Builder ™ agregará al proyecto un conjunto de clases que serán utilizadas por el programador para invocar los servicios, como se muestra a continuación:

    Para el ejemplo que se presenta a continuación, se utilizó IDE Adobe® Flex™ Builder™ 3.0 con Flex® SDK 3.2. Para otras versiones de IDE y/o SDK, el proceso de creación de los stubs puede sufrir algunas variaciones. 

    El siguiente fragmento de código presenta un ejemplo de invocación de WebService de acceso a los Datasets de TOTVS Fluig Plataforma:

    ECMDatasetServiceClient.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="{this.start()}">
    	<mx:Script>
    		<![CDATA[
    			import generated.webservices.ValuesDto;
    			import generated.webservices.DatasetDto;
    			import generated.webservices.GetDatasetResultEvent;
    			import generated.webservices.SearchConstraintDtoArray;
    			import generated.webservices.StringArray;
    			import generated.webservices.ECMDatasetServiceService;
    			import mx.rpc.events.FaultEvent;
    			import mx.collections.ArrayCollection;
    		
    			//Crea una instancia de stub de acceso al servicio
    			private var ds:ECMDatasetServiceService = new ECMDatasetServiceService();
    			public function start() : void {
    				//Crea tipos auxiliares que se utilizarán en la llamada de servicio
    				var fields:StringArray = new StringArray();
    				var constraints:SearchConstraintDtoArray = new SearchConstraintDtoArray();
    				var order:StringArray = new StringArray();
    				//Define las funciones para tratamiento de la devolución
    				ds.addEventListener(GetDatasetResultEvent.GetDataset_RESULT, resultGetDataset);
    				ds.addEventListener(FaultEvent.FAULT,faultGetDataset);
    				
    				//invoca el método getDataset del servicio
    				ds.getDataset("adm", 1, "adm", constraints, order, fields, "colleague");
    			}
    			
    			//Tratamiento de los datos devueltos por el servicio invocado.
    			public function resultGetDataset(ev:GetDatasetResultEvent) : void {
    				//Recupera la devolución del servicio, en forma de un DatasetDto
    				var dataset:DatasetDto = ev.result as DatasetDto;
    				//Instala una string con todos los datos del dataset
    				var line:String = "";
    				
    				//Encabezado con el nombre de los campos
    				var columnsArray:ArrayCollection = new ArrayCollection(dataset.columns);
    				for (var j:int = 0; j < columnsArray.length; j++) {
    					line += columnsArray.getItemAt(j) + "\t";
    				}
    				//Línea de datos
    				var valuesArray:ArrayCollection = new ArrayCollection(dataset.values);
    				for (var j:int = 0; j < valuesArray.length; j++) {
    					var row:ValuesDto = valuesArray.getItemAt(j) as ValuesDto;
    					line += "\n" + j + ":";
    					
    					for (var i:int = 0; i < row.length; i++) {
    						line += row.getItemAt(i) + "\t";
    					}
    				}
    				
    				//Muestra la string creada en un textarea en la pantalla
    				this.sysout.text = line;
    			}
    			
    			public function faultGetDataset(ev:FaultEvent) : void {
    				this.sysout.text = ev.fault.faultString;
    			}
    		]]>
    	</mx:Script>
    	<mx:TextArea id="sysout" name="sysout" width="100%" height="100%" 
    		paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5"/>
    </mx:Application>

    Existe un bug de Flex® que impide el funcionamiento correcto de servicios que trabajan con matrices multidimensionales de datos, como en el ejemplo anterior, donde se devuelve un array (de líneas del Dataset) de array (de las columnas de cada registro).

    Para solucionar este problema, es necesario cambiar la clase generada por Flex™ Builder™ que encapsulará el array multidimensional. En el ejemplo anterior, esta clase es DatasetDto, que deberá cambiarse (línea 11) como se muestra en el siguiente ejemplo:

    public class DatasetDto
    {
    	/**
    	 * Constructor, initializes the type class
    	 */
    	public function DatasetDto() {}
                
    	[ArrayElementType("String")]
    	public var columns:Array;
    	[ArrayElementType("ValuesDto")]
    	public var values:Array = new Array(); //iniciando el array
    }

    Otros servicios que no trabajan con arrays multidimensionales no exigen cambios en el código generado.


    Via Java™

    Existen muchas implementaciones de uso de WebServices en Java ™ y en este ejemplo vamos a utilizar las bibliotecas disponibles en Java ™ 7.

    Del mismo modo que en el ejemplo anterior, en Apache Flex®, el primer paso consiste en utilizar la dirección WSDL para generar los stubs en Java ™. El siguiente comando presenta un ejemplo de cómo generar estos stubs:

    wsimport -d <output_directory> <wsdl_url>

    A través de este comando se generan los stubs en el directorio de destino (output_directory), según la descripción del archivo wsdl (wsdl_url).

    A partir de los stubs generados, se puede consumir el WebService como se muestra en el siguiente ejemplo:

    ECMDatasetServiceClient.java
    package com.fluig.examples;
    import javax.xml.ws.BindingProvider;
    import net.java.dev.jaxb.array.StringArray;
    import com.totvs.technology.ecm.dataservice.ws.DatasetDto;
    import com.totvs.technology.ecm.dataservice.ws.DatasetService;
    import com.totvs.technology.ecm.dataservice.ws.ECMDatasetServiceService;
    import com.totvs.technology.ecm.dataservice.ws.SearchConstraintDtoArray;
    import com.totvs.technology.ecm.dataservice.ws.ValuesDto;
    /*
     * Clase para invocar servicio DatasetService
     */
    public class ECMDatasetServiceClient {
    	//Instancia DatasetServiceService.
    	private ECMDatasetServiceService ecmDatasetServiceService = new ECMDatasetServiceService();
    	private DatasetService service = ecmDatasetServiceService.getDatasetServicePort();
    	
    	//Inicia ejecución de la clase
    	public static void main(String[] args) {
    		ECMDatasetServiceClient client = new ECMDatasetServiceClient();
    		
    		//Configura acceso a WebServices.
    		BindingProvider bp = (BindingProvider) client.service;
    		bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, 
    				"http://localhost:8080/webdesk/ECMDatasetService");
    		try {
    			client.getDataset();
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    	public void getDataset() throws Exception {
    		
    		//Crea los parámetros utilizados en la llamada
    		int companyId = 1;
    		String username = "adm";
    		String password = "adm";
    		String name = "colleague";
    		StringArray fields = new StringArray();
    		SearchConstraintDtoArray constraints = new SearchConstraintDtoArray();
    		StringArray order = new StringArray();
    		
    		//Invoca el servicio de dataset
    		DatasetDto result = service.getDataset(
    				companyId, username, password, name, fields, constraints, order);
    		
    		//Presenta el encabezado
    		for (String columnName : result.getColumns()) {
    			System.out.print(columnName + "\t");
    		}
    		System.out.println();
    		
    		//Presenta las líneas del dataset
    		for (ValuesDto row : result.getValues()) {
    			for (Object value : row.getValue()) {
    				System.out.print(value + "\t");
    			}
    			System.out.println();
    		}
    	}
    }

    Al utilizar los WebServices vía Java ™, se debe prestar atención al tipo de cada atributo y al tipo de devolución de WebService. Por ejemplo, para valores del tipofecha se debe utilizar la clase XMLGregorianCalendar:

    DocumentDto document = new DocumentDto();
    
    XMLGregorianCalendar date = DatatypeFactory.newInstance().newXMLGregorianCalendar();
    date.setYear(2013);
    date.setMonth(10);
    date.setDay(16);
    date.setHour(0);
    date.setMinute(0);
    date.setSecond(0);
    
    document.setExpirationDate(date);


    Vía Progress® 4GL

    Al igual que en los ejemplos anteriores, el primer paso para consumir un Webservice en Progress® es utilizar una utilidad que leerá la dirección WSDL y generará la información necesaria para su acceso. A diferencia de Java™ y Flex®, Progress® no genera objetos de stub, pero sí una documentación sobre cómo consumir los servicios descritos en el archivo WSDL. Aunque en algunas situaciones es posible utilizar los tipos nativos de Progress® como parámetros, dependiendo del tipo de dato utilizado es necesario manipular el XML SOAP para extraer o enviar una información.

    Para generar la documentación de un servicio, se debe utilizar la utilidad bprowsdldoc como en el siguiente ejemplo:

    bprowsdldoc <URL_TO_WSDL>

    Mediante la ejecución de esta utilidad se generarán algunos archivos HTML con la información sobre la forma de consumir el servicio. Esta documentación proporciona información y ejemplos de cómo llevar a cabo la conexión con el servicio y cómo utilizar los métodos y datatypes del servicio. 

    El siguiente código presenta un ejemplo de cómo consumir el servicio:

    wsECMDatasetService.p
    /* Parte I - Invocar o WebService */
    DEFINE VARIABLE hWebService     AS HANDLE NO-UNDO.
    DEFINE VARIABLE hDatasetService AS HANDLE NO-UNDO.
    
    DEFINE VARIABLE cFields  AS CHARACTER EXTENT 0 NO-UNDO.
    DEFINE VARIABLE cOrder   AS CHARACTER EXTENT 0 NO-UNDO.
    DEFINE VARIABLE cDataset AS LONGCHAR NO-UNDO.
    
    DEFINE TEMP-TABLE item NO-UNDO
        NAMESPACE-URI ""
        FIELD contraintType AS CHARACTER
    	FIELD fieldName     AS CHARACTER
    	FIELD finalValue    AS CHARACTER
    	FIELD initialValue  AS CHARACTER.
     
    DEFINE DATASET dConstraints NAMESPACE-URI "http://ws.dataservice.ecm.technology.totvs.com/"
    	FOR item.
    
    CREATE SERVER hWebService.
    hWebService:CONNECT("-WSDL 'http://localhost:8080/webdesk/ECMDatasetService?wsdl'").
    RUN DatasetService SET hDatasetService ON hWebService.
    
    RUN getDataset IN hDatasetService(INPUT 1,
                                      INPUT "adm",
                                      INPUT "adm",
                                      INPUT "colleague",
                                      INPUT cFields,
                                      INPUT DATASET dConstraints,
                                      INPUT cOrder,
                                      OUTPUT cDataset).
    
    DELETE OBJECT hDatasetService.
    hWebService:DISCONNECT().
    DELETE OBJECT hWebService.
    
    /* Parte II - Faz o parser do XML e criar um arquivo texto separado por tabulacao */
    DEFINE VARIABLE iCount  AS INTEGER   NO-UNDO.
    DEFINE VARIABLE iCount2 AS INTEGER   NO-UNDO.
    DEFINE VARIABLE hDoc    AS HANDLE    NO-UNDO.
    DEFINE VARIABLE hRoot   AS HANDLE    NO-UNDO.
    DEFINE VARIABLE hValues AS HANDLE    NO-UNDO.
    DEFINE VARIABLE hEntry  AS HANDLE    NO-UNDO.
    DEFINE VARIABLE hText   AS HANDLE    NO-UNDO.
    DEFINE VARIABLE cValue  AS CHARACTER NO-UNDO.
    
    OUTPUT TO c:\dataset.txt.
    
    CREATE X-DOCUMENT hDoc.
    CREATE X-NODEREF hRoot.
    CREATE X-NODEREF hEntry.
    CREATE X-NODEREF hText.
    CREATE X-NODEREF hValues.
    
    hDoc:LOAD("longchar", cDataset, FALSE).
    hDoc:GET-DOCUMENT-ELEMENT(hRoot).
    
    /* Percorre as colunas <columns> */ 
    DO iCount = 1 TO hRoot:NUM-CHILDREN WITH 20 DOWN:
        hRoot:GET-CHILD(hEntry, iCount).
        IF hEntry:NAME <> "columns" THEN
            NEXT.
    
        hEntry:GET-CHILD(hText, 1).
        PUT UNFORMATTED hText:NODE-VALUE "~t".
        DOWN.
    END.
    PUT UNFORMATTED SKIP.
    
    /* Percorre os registros <values> */
    DO iCount = 1 TO hRoot:NUM-CHILDREN WITH 20 DOWN:
        hRoot:GET-CHILD(hValues, iCount).
        IF hValues:NAME <> "values" THEN
            NEXT.
    
        /* Percorre os campos <value> */
        DO iCount2 = 1 TO hValues:NUM-CHILDREN:
            hValues:GET-CHILD(hEntry, iCount2).
    
            IF hEntry:NUM-CHILDREN = 0 THEN
                cValue = "".
            ELSE DO:
                hEntry:GET-CHILD(hText, 1).
                cValue = hText:NODE-VALUE.
            END.
            PUT UNFORMATTED cValue "~t".
        END.
    
        PUT UNFORMATTED SKIP.
    END.
    
    OUTPUT CLOSE.
    
    DELETE OBJECT hValues.
    DELETE OBJECT hText.
    DELETE OBJECT hEntry.
    DELETE OBJECT hRoot.
    DELETE OBJECT hDoc.

    Acceso a WebServices desde TOTVS Fluig Plataforma

    TOTVS Fluig Plataforma permite realizar llamadas a WebServices de terceros a través del registro de Servicios en la visualización de Servicios de Fluig Studio.

    Para agregar un nuevo WebService, es necesario ingresar a la opción Incluir Servicio, abriendo el asistente Nuevo Servicio, e informar al servidor de Fluig donde se agregará el servicio, un código identificador para él, su descripción, la URL para WSDL y su tipo (en este caso WebService). En el siguiente ejemplo, se utilizará un WebService público para consultar la tabla periódica, cuya dirección de WSDL es http://www.webservicex.com/periodictable.asmx?wsdl.

    Basado en esta información, TOTVS Fluig Plataforma extraerá la información sobre el WebService informado y finalizará el registro de este servicio. 

    Una vez que el servicio está registrado, se pueden ver las clases y métodos disponibles en este servicio y que se utilizarán en los códigos JavaScript que lo usarán. La siguiente pantalla presenta un ejemplo de visualización de WebService.


    Los servicios agregados en TOTVS Fluig Plataforma se pueden instanciar y utilizar en los puntos donde el producto permite personalización utilizando JavaScript, como en scripts para eventos globales, eventos de procesos, eventos de definición de formulario o Datasets. En el siguiente ejemplo se creará un Dataset que usará este servicio para traer los datos de la tabla periódica.

    El siguiente código presenta una implementación de ejemplo del uso de un servicio en la construcción de un Dataset:

    periodicTable.js
    function createDataset(fields, constraints, sortFields) {
    	//Crea el dataset
    	var dataset = DatasetBuilder.newDataset();
    	dataset.addColumn("elementName");
    	// Conecta el servicio y busca los libros
    	var periodicService = ServiceManager.getService('PeriodicTable');
    	var serviceHelper = periodicService.getBean();
    	var serviceLocator = serviceHelper.instantiate('net.webservicex.Periodictable');
    	var service = serviceLocator.getPeriodictableSoap();
    	//Invoca el servicio
    	try {
    		var result = service.getAtoms();
    		var NewDataSet = new XML(result);
    		for each(element in NewDataSet.Table) {
    			dataset.addRow(new Array(element.ElementName.toString()));
    		}
    	} catch(erro) {
    		dataset.addRow(new Array(erro));
    	}
    	return dataset;
    }

    El primer paso para invocar el servicio es solicitar a TOTVS Fluig Plataforma que cargue el servicio, a partir del método ServiceManager.getService('PeriodicTable'). El valor informado como parámetro debe ser el código utilizado cuando se registró el servicio.

    Cuando el servicio se haya cargado, se utiliza el método getBean() para devolver una utilidad para acceder a las clases del servicio, a través del método instantiate. A través de esta utilidad, se pueden instanciar las clases disponibles, que se encuentran en el registro del Servicio (como se puede ver en la imagen anterior).

    Después de que se haya instanciado el objeto utilidad del servicio, las clases se deben instanciar y los métodos que se deben invocar depende de cada WebService utilizado y, deben recurrir a su documentación para obtener más información.

    Para el servicio de la tabla periódica es necesario realizar los siguientes pasos:

    var serviceLocator = serviceHelper.instantiate('net.webservicex.Periodictable');
    var service = serviceLocator.getPeriodictableSoap();
    var result = service.getAtoms();

    Donde:

    • Paso 1: Instanciar la clase net.webservicex.Periodictable para tener acceso al localizador del servicio; 

    • Paso 2: Invocar el método getPeriodictableSoap para instanciar el servicio;
    • Paso 3: Invocar el método getAtoms para tener la lista de los elementos.

    En el caso de este servicio, el método getAtoms devuelve una string que contiene un XML con la lista de todos los elementos, como se muestra en el siguiente ejemplo:

    <NewDataSet>
    	<Table>
    		<ElementName>Actinium</ElementName>
    	</Table>
    	<Table>
    		<ElementName>Aluminium</ElementName>
    	</Table>
    	...
    </NewDataSet>

    Para recorrer el XML y extraer los datos disponibles, se utilizan las funcionalidades de tratamiento de XML de JavaScript que facilita la manipulación de datos de este tipo. Para más información sobre esta funcionalidad, ingrese a : http://www.ecma-international.org/publications/standards/Ecma-357.htm ohttp://www.xml.com/pub/a/2007/11/28/introducing-e4x.html.

    El siguiente ejemplo presenta el código utilizado para recorrer el XML devuelto:

    var NewDataSet = new XML(result);
    for each(element in NewDataSet.Table) {
    	dataset.addRow(new Array(element.ElementName.toString()));
    }

    Una vez implementado el código del Dataset, es posible visualizarlo, como muestra la siguiente figura:


    WebServices con Autenticación Básica

    Para consumir WebServices que utilizan la autenticación básica (WSS o WS-Security), es necesario utilizar el método getBasicAuthenticatedClient localizado en provider del servicio (el mismo que se obtiene vía ServiceManager). Este método proporciona un client autenticado.


    Los parámetros que se deben informar en el método siguen el siguiente orden:

    1. Instancia del servicio
    2. Nombre de la clase del servicio
    3. Usuario para la autenticación
    4. Contraseña para la autenticación

    Utilizando el ejemplo del servicio PeriodicTable presentado anteriormente, el código de la llamada tendría los siguientes cambios:

    var serviceLocator = serviceHelper.instantiate('net.webservicex.Periodictable');
    var service = serviceLocator.getPeriodictableSoap();
    var authenticatedService = serviceHelper.getBasicAuthenticatedClient(service, "net.webservicex.PeriodictableSoap", 'usuario', 'senha');
    var result = authenticatedService.getAtoms();
    WebService con client personalizado

    Atención

    Esta técnica es válida para atualización 1.3.7 o superior.

    En integraciones que utilizan los servicios creados con CXF con sistemas que no soportan el protocolo HTTP/1.1 (Protheus, por ejemplo), se debe utilizar este método configurando el parámetro "disable.chunking" con el valor "true".

    Para personalizar el client que accede al servicio se debe utilizar el método getCustomClient, localizado en provider del servicio (el mesmo que se obtiene vía ServiceManager). Esta configuración exige la creación de un mapa de parámetros con sus respectivos valores para pasar al método, según el siguiente snippet:

    		var properties = {};
    		properties["basic.authorization"] = "true";
    		properties["basic.authorization.username"] = "username";
    		properties["basic.authorization.password"] = "password";
    		properties["disable.chunking"] = "true";
    		properties["log.soap.messages"] = "true";
    		
    		var serviceLocator = serviceHelper.instantiate('net.webservicex.Periodictable');
    		var service = serviceLocator.getPeriodictableSoap();
    		var customClient = serviceHelper.getCustomClient(service, "net.webservicex.PeriodictableSoap", properties);
    		var result = customClient.getAtoms();


    Los parámetros que se pueden configurar son los siguientes:

    Propriedad
    Función
    basic.authorization

    Cuando se define como "true", hace lo mismo que el método getBasicAuthenticatedClient, pero permite aplicar la configuración de autenticación junto con las demás personalizaciones que se detallan a continuación. Para configurar la autenticación, las siguientes propiedades con "username" y "password" también necesitan definirse.

    basic.authorization.username
    Usuario que se utilizará para autenticación básica.
    basic.authorization.password
    Contraseña de usuario utilizado para autenticación básica.
    disable.chunking

    Cuando se define como "true", desactiva el envío de solicitudes grandes en "fragmentos" menores. Puede ser útil cuando el servicio llamado no admite este tipo de solicitud.

    log.soap.messages
    Cuando se define como "true", permite que los mensajes SOAP utilizados en las solicitudes realizadas a los servicios se presenten en el log del servidor, lo que facilita la depuración en caso de fallas.

    Progress® Open AppServer


    Así como es posible invocar operaciones en WebServices, TOTVS Fluig Plataforma también permite realizar llamadas a programas en Progress® 4GL (o ABL) expuestos vía Progress® Open AppServer.

    En los siguientes ejemplos, se crearán Datasets que a través de la capa de servicio, tendrán acceso a las lógicas en 4GL que realizarán la extracción de datos. Aunque los códigos 4GL, en este ejemplo, sean muy simples, abarcan los casos más comunes requeridos en el día a día, ya que la complejidad de la integración se encuentra en las interfaces (parámetros de entrada y salida) de cada procedure expuesto y no en su lógica interna.

    Tenga en cuenta que los ejemplos que aquí se presentan pretenden demostrar la dinámica de la integración entre Progress® y TOTVS Fluig Plataforma sin entrar en detalles específicos de las tecnologías involucradas. La capa de servicios Progress® de TOTVS Fluig Plataforma crea una interfaz en JavaScript para la biblioteca Java Open AppServer Client, de Progress® y, por lo tanto, para más información sobre cómo integrar aplicaciones Java ™ y Progress® vea la documentación suministrada por Progress®.

    Caso de Uso

    Los ejemplos que se muestran a continuación, tienen como objetivo crear cuatro Datasets 1 en TOTVS Fluig Plataforma:

    1. Tipos de Centro de Costo, que deben devolver los tipos de centros de coste existente en la aplicación en Progress® (en este caso, EMS2).
    2. Naturaleza de los Centros de Costo, que debe devolver los tipos posibles de naturaleza, según la aplicación en Progress® (en este caso, EMS2).
    3. Centros de Costo, que debe devolver los registros en la tabla cuenta 2.
    4. Usuarios Comunes, que debe generar una lista de usuarios comunes entre TOTVS Fluig Plataforma y la aplicación en Progress® (utilizando la tabla usuar_mestre)


      1 - Los ejemplos utilizan una base de datos de EMS2 para consultar los centros de costo y los usuarios. No obstante, sólo dos tablas y seis campos se utilizan en total, lo que no debe perjudicar la comprensión de la lógica por parte del lector, ni impedir la creación de un esquema equivalente para pruebas, en caso de ser necesario.

      2 - El código presentado para extracción de los centros de costo tiene fines puramente educativos y no puede considerarse para el uso en producción. Para obtener más información acerca de cómo extraer los centros de costos de EMS2, consulte su documentación técnica.

    Para los tres primeros casos, la lógica de extracción de la información deseada estará expuesta en un programa con varios procedures, uno para cada necesidad que aquí se presenta:

    CostCenterUtils.p
    /**************************************************************************
    ** Utilidad que ofrece procedures para la extracción de información
    ** sobre centros de costo.
    **************************************************************************/
    DEFINE TEMP-TABLE ttCC NO-UNDO
        FIELD cuenta    LIKE cuenta.ct-codigo /* CHARACTER */
        FIELD naturaleza LIKE cuenta.naturaleza  /* INTEGER   */
        FIELD tipo     LIKE cuenta.tipo      /* INTEGER   */
        FIELD titulo   LIKE cuenta.titulo.   /* CHARACTER */
     
    /*-------------------------------------------------------------------
      Procedure: readCostCenters
       Objetivo: Devuelve una temp-table con la lista de centros de costo.
    ----------------------------------------------------------------------*/
    PROCEDURE readCostCenters:
        DEFINE OUTPUT PARAMETER TABLE FOR ttCC.
        FOR EACH cuenta:
            CREATE ttCC.
            ASSIGN
                ttCC.cuenta    = cuenta.ct-codigo
                ttCC.naturaleza = cuenta.naturaleza
                ttCC.tipo     = cuenta.tipo
                ttCC.titulo   = cuenta.titulo.
        END.
    END.
    /*-------------------------------------------------------------------
      Procedure: readCostNatureTypes
       Objetivo: Devuelve una string con las naturalezas de los centros de costo,
                 separadas por coma.
    ----------------------------------------------------------------------*/
    PROCEDURE readCostNatureTypes:
        DEFINE OUTPUT PARAMETER cNatureList AS CHARACTER NO-UNDO.
        cNatureList = {adinc/i01ad047.i 03}.
    END.
    /*-------------------------------------------------------------------
      Procedure: readCostTypes
       Objetivo: Devuelve una string con los tipos de centro de costo,
                 separados por coma.
    ----------------------------------------------------------------------*/
    PROCEDURE readCostTypes: 
        DEFINE OUTPUT PARAMETER cTypeList   AS CHARACTER NO-UNDO.
        cTypeList = {adinc/i02ad047.i 3}.
    END.

    En el caso de la extracción de usuarios comunes a los dos productos, se utilizará un único programa, según el siguiente código:

    verifyUsers.p
    /**************************************************************************
    ** Utilidad que recibe una temp-table con una lista de usuarios y devuelve
    ** otra, sólo con los usuarios de la lista que existan en la base de datos.
    **************************************************************************/
    DEFINE TEMP-TABLE ttUsers
        FIELD cod_usuar   AS CHARACTER
        FIELD nom_usuario AS CHARACTER
        INDEX principal	  IS PRIMARY UNIQUE cod_usuar.
    	
    DEFINE TEMP-TABLE ttOutUsers LIKE ttUsers.
    DEFINE INPUT  PARAMETER TABLE FOR ttUsers.
    DEFINE OUTPUT PARAMETER TABLE FOR ttOutUsers.
    FOR EACH ttUsers:
       IF CAN-FIND(usuar_mestre WHERE usuar_mestre.cod_usuar = ttUsers.cod_usuar) THEN DO:
            CREATE ttOutUsers.
            BUFFER-COPY ttUsers TO ttOutUsers.
        END.
    END.

    Los dos códigos presentados tienen diferencias significativas en la forma en la que se utilizan y en cómo van a ser expuestos por Progress®. En el primer caso, el programa se carga de manera persistente y sus procedures se pueden ejecutar de forma independiente. En el segundo, el programa se ejecuta de manera no persistente y la lógica principal se encuentra en el main-block. Los procedures internos, si existen, están destinados a mejorar la organización del código y no se pueden utilizar de manera aislada.

    Configuración de AppServer

    Algunas informaciones importantes en la configuración del AppServer:

    1. AppServer debe cargarse en el modo Stateless;
    2. En la configuración del agente, en el campo Propath, se debe agregar el directorio donde están ubicados los archivos compilados (.r).

      Importante: Cuando se utiliza una ruta relativa (\\servidor\carpeta), el servicio Windows® de Progress® (AdminService) se debe iniciar con un usuario de la red que posea permiso para acceder al directorio informado.

    Exponiendo códigos 4GL con ProxyGen

    El primer paso para poder ejecutar rutinas en Progress® 4GL es crear la biblioteca cliente, que se realiza mediante la aplicación ProxyGen, que acompaña a la instalación de Progress®, como se muestra a continuación.

    Utilice el paso a paso para visualizar el proceso de creación del proxy:


      • En la primera pantalla del ProxyGen, el punto principal que se debe tener en cuenta es el nombre del Proyecto (en el ejemplo, EMSProxies). La información de este campo será utilizada por ProxyGen para nombrar la clase de acceso al servicio, y se utilizará en la configuración del servicio en Fluig. En esta pantalla también es necesario configurar PROPATH de manera correcta, para que se puedan encontrar los archivos compilados (.r).


      • El segundo paso consiste en introducir los procedures que se expondrán de forma persistente o no persistente. La elección de la opción que se va a utilizar depende de la forma en la que se construyó cada objeto expuesto. Después de introducidos los procedures, haga clic en la opción Generate.



      • Durante el proceso de generación de proxy, en la pestaña General, marque la opción Java en Client Proxy e ingresar el directorio en el que se generará el proxy en Output Dir. Observe también el campo AppService, éste debe ser el nombre del servicio publicado en AppServer, de lo contrario no se podrá conectar al servidor.



      • La última información relevante para la generación de proxy es el nombre del paquete (package) donde se crearán las clases. Esta información se utiliza durante la configuración del servicio Progress® en Fluig. Para finalizar haga clic en el botón OK.

       



      • Una vez creadas las clases, es necesario empaquetarlas en un archivo .JAR. Esto se puede realizar por línea de comando, utilizando el siguiente comando:

      jar -cvf <jar_file_name> <directorio>


      Sólo tenga en cuenta que en el archivo generado, es necesario que las clases estén en los directorios correctos. En el ejemplo presentado, el directorio con debe incluirse y estar en la raíz del archivo JAR. Por ser compatible con el formato ZIP, otra opción es generar un archivo con las clases generadas (respetando los directorios) y renombrarlo con la extensión .JAR.


      Dependiendo de la versión de Progress®, las pantallas pueden sufrir alguna variación en la cantidad y disposición de los campos. En caso de dudas consulte la documentación



      Configuración del Servicio en Fluig

      El registro de un servicio se realiza a través de Fluig Studio, en la view Visualización de Servicios, en la opción  Incluir Servicio. La siguiente pantalla muestra el asistente del nuevo servicio y los campos utilizados para el registro del servicio Progress®:



      • Donde:

        • ServidorServidor de Fluig donde se agregará el servicio;
        • Código: Código único que identificará el servicio en el sistema. Este código se utilizará en los códigos JavaScript para acceder a este servicio;
        • Descripción: Texto que describe el servicio de datos;
        • URL: Identifica la URL de acceso al servicio AppServer, como por ejemplo AppServer://<servidor>/<nome_serviço>;
          En caso de que no esté utilizando el NameServer estándar se debe informar el puerto de acceso a NameServer Ej.: AppServer://<servidor>:<porta_NameServer>/<nome_serviço>. 
          Tenga en cuenta que dependiendo de la configuración del servicio y/o de la forma de conexión, la URL puede sufrir cambios. Verifique la documentación de Open AppServer para más información.
        • Tipo: Identifica el tipo de servicio (Progress o WebService). Se debe seleccionar Progress;
        • Objeto Remoto: Identifica la clase de acceso de proxy. Esta clase generalmente está compuesta por el nombre del paquete utilizado para generar las clases Proxy, más el nombre del proyecto en ProxyGen. 
          Ejemplo: En las pantallas que presentan ProxyGen, se utilizó el paquete com.fluig.samples.ems, y el nombre que se dio al proyecto en ProxyGen fue EMSProxies. En este caso, la clase del objeto remoto será com.fluig.samples.ems.EMSProxies;
        • Usuario: Usuario utilizado para conectarse al servicio, como se define en la configuración de AppServer;
        • Contraseña: Contraseña utilizada para conectarse al servicio, como se define en la configuración de AppServer;
        • Parámetros Extras: Parámetros extras (y opcionales) utilizados para conectarse a AppServer. Verifique la documentación de Open AppServer para verificar las opciones disponibles en cada versión de Progress®;
        • Directorio del archivo de Proxy: Archivo .JAR que contiene las clases generadas por ProxyGen. Se debe utilizar el botón Seleccionar Archivo para localizarlo.


      Cuando el servicio se haya agregado, se pueden ver las clases disponibles y los métodos existentes en cada una de ellas. Esta información es importante para guiar el desarrollo de los códigos de personalización que harán uso de este servicio. Para ver las clases y los métodos del servicio, utilice la opción Consulta Servicio en Visualización de Servicios, como muestra la siguiente pantalla:



      Visión general de los objetos involucrados

      El acceso a los procedures expuestos en AppServer implica cuatro elementos que interactúan entre sí de acuerdo al siguiente diagrama:



      Donde:

      • Script Code: es el código en JavaScript que hará uso de los procedures expuestos en AppServer. Como se mencionó anteriormente, este JavaScript puede ser de cualquier naturaleza, tales como la implementación de un Dataset o la personalización de un evento de proceso.
      • Service Provider: Objeto recuperado a través del método ServiceManager.getService que proporciona el acceso a las funcionalidades del servicio. Este objeto se encarga de gestionar la conexión y los recursos asignados por el servicio durante la ejecución del script.
      • Service Helper: Objeto recuperado a través del método getBean en ServiceProvider que pone a disposición un conjunto de métodos de utilidad que permiten, entre otras cosas, crear objetos específicos de Progress® (StringHolder, ResultSetHolder, etc.), tener acceso al objeto remoto de ProxyGen e instanciar clases. Para más información sobre Service Helper consulte aquí.
      • ProxyGen Clases: Clases generadas por ProxyGen y serán utilizadas por el desarrollador para la ejecución de las rutinas en Progress®. La lista de las clases disponibles, así como sus métodos, se pueden ver en la Visualización de Servicios de Fluig Studio.

      Procedures Persistentes y No Persistentes

      Cuando se agrega un procedure al proyecto de ProxyGen, éste debe configurarse en dos listas: Procedures Persistentes o No Persistentes Esta decisión implica directamente en la forma en la que se accede a estos objetos por la biblioteca generada y, por consiguiente, en la forma en la que el desarrollador accederá a ella en los códigos JavaScript.

      Los procedures expuestos de forma no persistente dan origen a métodos en la clase configurada como Objeto Remoto (o Proxy) en el Servicio, y su ejecución se lleva a cabo llamando al método correspondiente, por ejemplo:

      serviceHelper.getProxy().verifyUsers(inputTT, holder).

      Los procedures expuestos de forma persistente dan origen a nuevas clases que pueden instanciarse a través de llamadas a métodos en el Objeto Remoto (a través de la Visualización de Servicios en Fluig Studio se pueden verificar los métodos disponibles en la clase), o por el método createManagedObject. La llamada a través del método createManagedObject permite que  Fluig tenga control sobre el ciclo de vida de este objeto, liberándolo de forma automática al final del método. Si el objeto es instanciado de forma manual, el desarrollador debe codificar la liberación del objeto (método _release()), bajo pena de bloquear un nuevo agente de AppServer en cada invocación del método.


      Parámetros de Entrada y Salida

      Otro punto importante en la invocación de las rutinas en 4GL es observar cuales son tipos de parámetros de entrada y salida de cada procedure o programa. Dependiendo del tipo de dato (CHARACTER, INTEGER, TEMP-TABLE, etc.), del tipo de parámetro (INPUT, INPUT-OUTPUT, BUFFER, etc.) y de la versión utilizada de Progress®, la forma de manipular estos parámetros puede variar.

      Para los tipos más simples, como strings, datas o valores enteros, ProxyGen utiliza un mapeo directo para los tipos o clases estándares del lenguaje Java™. Para tipos complejos, como temp-tables y buffers, ProxyGen utiliza clases que forman parte de la biblioteca de runtime de estos servicios.

      Cuando los parámetros son de entrada y salida (INPUT-OUTPUT) o de salida (OUTPUT), los tipos primitivos deben sustituirse por clases Holders, que contendrán el valor devuelto después de la ejecución del método. Los ejemplos más comunes son StringHolder o ResultSetHolder.

      Los tipos de datos utilizados por cada método se pueden consultar a través de la Visualización de Servicios en Fluig Studio. Tenga en cuenta que dependiendo de la versión de Progress® puede existir variación en los tipos de parámetros utilizados y en la manera de utilizarlos. En caso de duda, consulte la documentación suministrada por Progress®.

      Creación de los Datasets

      Cuando el servicio Progress® se haya agregado en Fluig, es posible utilizarlo en los puntos donde el producto permite la personalización utilizando JavaScript, como en los scripts para eventos globales, eventos de procesos, eventos de definición de formulario o Datasets.

      La forma de invocar las rutinas expuestas por el servicio es siempre la misma, independientemente de qué punto se está llamando. Sin embargo, para facilitar la comprensión del uso de servicios Progress® en Fluig y facilitar la reproducción de los ejemplos presentados en el entorno del lector, todos los siguientes ejemplos utilizarán Datasets como meta.

      Como ya se ha visto anteriormente, los Datasets que se presentarán aquí son Tipos de Centro de CostoNaturaleza de los Centros de CostoCentros de Costo yUsuarios en Común.


      Tipos de Centro de Costo

      El siguiente código presenta la implementación del Dataset de Tipos de Centro de Costo. La explicación de cada paso de la implementación se presentará después del código:

      dsTipoCentroCusto.js
      function createDataset(fields, constraints, sortFields) {
      	
      	//Paso 1 - Crea el dataset
      	var dataset = DatasetBuilder.newDataset();
          dataset.addColumn("id");
          dataset.addColumn("descripcion");
          
      	//Paso 2 - Invoca el servicio registrado en Fluig
      	var servico = ServiceManager.getService("EMS2");
       
      	//Paso 3 - Carga el objeto utilidad para integración con Progress
          var serviceHelper = servico.getBean();
       
      	//Paso 4 - Carga el procedure persistente CostCenterUtils.p
          var remoteObj = serviceHelper.createManagedObject("CostCenterUtils");
      	//Paso 5 - Invoca el procedure que devuelve una string con los tipos de CC
          var types = serviceHelper.createStringHolder();
          remoteObj.readCostTypes(types);
      	//Paso 6 - Rompe la string en un array con cada uno de los tipos
          var typeArray = types.getStringValue().split(",");
       
      	//Paso 7 - Agrega cada tipo devuelto
          for(var pos = 0; pos < typeArray.length; pos++) {
              dataset.addRow(new Array(pos + 1, typeArray[pos]));
          }
          return dataset;
      }


      Donde:

      • Paso 1: Crea el dataset y agrega los campos del mismo;
      • Paso 2: La invocación del servicio registrada en Fluig se realiza por el método ServiceManager.getService, y el valor pasado como parámetro debe ser el código del servicio. Tenga en cuenta que en este punto no es necesario informar ningún parámetro de conexión al servicio, puesto que esto ya se realizó en su registro;
      • Paso 3: Utiliza el método getBean para devolver un objeto utilidad para servicios Progress®. Esta utilidad proporciona una serie de métodos que facilitan la interacción con el proxy generado y sus métodos se presentarán con más detalle, más adelante en este documento;
      • Paso 4: Realiza la carga del objeto CostCenterUtils de manera gestionada, a través del método createManagedObject de la utilidad creada anteriormente;
      • Paso 5: Invoca el método deseado, en este caso readCostTypes, pasando un StringHolder que recibirá el valor de salida;
      • Paso 6: Transforma la string recibida como parámetro en un array con las opciones. El carácter , (como) se utiliza para determinar los puntos de ruptura de string;
      • Paso 7: Recorre el array creado, agregando una línea en el Dataset para cada ítem del array. 


      La siguiente pantalla presenta la visualización de los datos del Dataset creado:

      Naturaleza de los Centros de Costo

      El Dataset de Naturaleza de los Centros de Costo es muy similar al Dataset de tipo de centros de costo. En la práctica, el único cambio es que el procedure se llama:

      dsNaturezaCentroCusto.js
      function createDataset(fields, constraints, sortFields) {
      
      	var dataset = DatasetBuilder.newDataset();
          dataset.addColumn("id");
          dataset.addColumn("descricao");
      
      	var servico = ServiceManager.getService("EMS2");
      	var serviceHelper = servico.getBean();
      	var remoteObj = serviceHelper.createManagedObject("CostCenterUtils");
      
      	var types = serviceHelper.createStringHolder();
      	remoteObj.readCostNatureTypes(types);
      
          var typeArray = types.getStringValue().split(",");
      
          for(var pos = 0; pos < typeArray.length; pos++) {
              dataset.addRow(new Array(pos + 1, typeArray[pos]));
          }
      
          return dataset;
      }


      Después del registro del Dataset, se puede ver su contenido:


      Centros de Costo

      El Dataset de Centro de Costo posee una estructura muy similar a la de los dos Dataset visto anteriormente. La principal diferencia es que, en este caso, el procedure devuelve una temp-table con los centros de costos, lo que cambia la forma en la que se manipulan los datos.

      Dependiendo de la versión de Progress®, los objetos utilizados pueden variar. A continuación, se presentan ejemplos de la codificación para Progress® 9 y OpenEdge® 10, respectivamente. En ambos casos, el resultado presentado por Dataset será el mismo.

      Codificación Progress® 9

      Las temp-table en Progress® 9 son manejadas por los objetos que implementan la interfaz java.sql.ResultSet:

      dsCentroCustoP9.js
      function createDataset(fields, constraints, sortFields) {
          
      	//Crea la estructura del Dataset
          var dataset = DatasetBuilder.newDataset();
          dataset.addColumn("cuenta");
          dataset.addColumn("titulo");
          dataset.addColumn("naturaleza");
          dataset.addColumn("tipo");
          
      	//Recupera el servicio y carga el objeto remoto
          var servico = ServiceManager.getService("EMS2");
          var serviceHelper = servico.getBean();
          var remoteObj = serviceHelper.createManagedObject("CostCenterUtils");
          
          //Lee las cuentas corrientes
          var holder = serviceHelper.createResultSetHolder();
          remoteObj.readCostCenters(holder);
          
          //Recorre los registros, cargando el Dataset con los datos
          var rs = holder.getResultSetValue();
          while (rs.next()) {
              var cuenta 	 = rs.getObject("cuenta");
              var naturaleza = rs.getObject("naturaleza");
              var tipo 	 = rs.getObject("tipo");
              var titulo   = rs.getObject("titulo");
      	
              dataset.addRow(new Array(cuenta, titulo, naturaleza, tipo));
          }
          
          return dataset;
      }
      Codificación OpenEdge® 10

      En OpenEdge® 10, las temp-tables devueltas se encapsulan como objetos de la clase ProDataGraph. Esta clase también se utiliza cuando se usan parámetros del tipo DATASET:

      dsCentroCustoOE10.js
      function createDataset(fields, constraints, sortFields) {
          
      	//Crea la estructura del Dataset
          var dataset = DatasetBuilder.newDataset();
          dataset.addColumn("cuenta");
          dataset.addColumn("titulo");
          dataset.addColumn("naturaleza");
          dataset.addColumn("tipo");
          
      	//Recupera el servicio y carga el objeto remoto
          var servico = ServiceManager.getService("EMS2");
          var serviceHelper = servico.getBean();
          var remoteObj = serviceHelper.createManagedObject("CostCenterUtils");
          
          //Lee las cuentas corrientes
          var holder = serviceHelper.createProDataGraphHolder();
          remoteObj.readCostCenters(holder);
          
      	//Recorre los registros, cargando el Dataset con los datos
          var ttCC = holder.getProDataGraphValue().getProDataObjects("ttCC");
          for (var row_index = 0; row_index < ttCC.size(); row_index++) {
              var row = ttCC.get(row_index);
              dataset.addRow(new Array(row.get("cuenta"),
                                       row.get("titulo"),
                                       row.get("naturaleza"),
                                       row.get("tipo")));
          }
          
          return dataset;
      }


      Visualización del Dataset:


      Usuarios en Común

      La primera diferencia entre el Dataset de usuarios comunes y los ejemplos anteriores, es que en este caso es necesario pasar una temp-table como parámetro al procedure invocado.

      La segunda diferencia es que el código 4GL se implementa en un programa no persistente, lo que cambia la forma como la lógica se invoca desde el código JavaScript.

      La tercera diferencia que se puede observar en este caso es que es posible transformar un Dataset en los tipos de datos requeridos por Progress® (ResultSet o ProDataGraph).

      Codificación Progress® 9
      dsUsuariosComunsP9.js
      function createDataset(fields, constraints, sortFields) {
          
          //Crea el nuevo Dataset
          var dataset = DatasetBuilder.newDataset();
          dataset.addColumn("usuario");
          dataset.addColumn("nombre");
          
      	//Recupera los usuarios de TOTVS Fluig Plataforma
          var campos = new Array("colleaguePK.colleagueId", "colleagueName");
          var colleaguesDataset = DatasetFactory.getDataset("colleague", campos, null, null);
          
          //Instancia el servicio
          var servico = ServiceManager.getService("EMS2");
          var serviceHelper = servico.getBean();
          
          //Transforma el dataset en un ResultSet (v9) y crea holder de salida
          var inputTT = colleaguesDataset.toResultSet();
          var holder = serviceHelper.createResultSetHolder();
          
          //Invoca el procedure en el Progress
          serviceHelper.getProxy().verifyUsers(inputTT, holder);
          
          var rs = holder.getResultSetValue();
          while (rs.next()) {
              dataset.addRow(new Array(rs.getObject("cod_usuar"), rs.getObject("nom_usuario")));
          }
          
          return dataset;
      }
      Codificación OpenEdge® 10
      dsUsuariosComunsOE10.js
      function createDataset(fields, constraints, sortFields) {
          
      	//Crea el nuevo Dataset
          var dataset = DatasetBuilder.newDataset();
          dataset.addColumn("usuario");
          dataset.addColumn("nombre");
          
      	//Recupera los usuarios de TOTVS Fluig Plataforma
          var campos = new Array("colleaguePK.colleagueId", "colleagueName");
          var colleaguesDataset = DatasetFactory.getDataset("colleague", campos, null, null);
          
          //Instancia el servicio
          var servico = ServiceManager.getService("EMS2");
          var serviceHelper = servico.getBean();
      	
          //Transforma el dataset en un ProDataGraph (v10) y crea holder de salida
          var inputTT = serviceHelper.toProDataGraph(colleaguesDataset);
          var holder = serviceHelper.createProDataGraphHolder();
          
          //Invoca el procedure en el Progress
          serviceHelper.getProxy().verifyUsers(inputTT, holder);
          
          var ttCC = holder.getProDataGraphValue().getProDataObjects("ttOutUsers");
          for (var row_index = 0; row_index < ttCC.size(); row_index++) {
              var row = ttCC.get(row_index);
              dataset.addRow(new Array(row.get("cod_usuar"), row.get("nom_usuario")));
          }
          
          return dataset;
      }


      Visualización del Dataset:


      Service Helper

      La siguiente tabla presenta la lista de métodos existentes en la clase utilidad para servicios Progress®:

      Devolución
      Método y Descripción
      java.lang.ObjectcreateBigDecimalHolder()
      Crea un objeto Holder para el tipo DECIMAL
      java.lang.ObjectcreateBooleanHolder()
      Crea un objeto Holder para el tipo LOGICAL
      java.lang.ObjectcreateByteArrayHolder()
      Crea un objeto Holder para el tipo RAW
      java.lang.ObjectcreateCOMHandleHolder()
      Crea un objeto Holder para el tipo COM-HANDLE
      java.lang.ObjectcreateDateHolder()
      Crea un objeto Holder para el tipo DATE
      java.lang.ObjectcreateHandleHolder()
      Crea un objeto Holder para el tipo WIDGET-HANDLE (Handle)
      java.lang.ObjectcreateIntHolder()
      Crea un objeto Holder para el tipo INTEGER
      java.lang.ObjectcreateLongHolder()
      Crea un objeto Holder para el tipo RECID
      java.lang.ObjectcreateManagedObject(java.lang.String objName)
      Lee un archivo .p o .r que haya sido expuesto a través de AppServer de forma persistente. A través de este método el proveedor del servicio puede administrar el ciclo de vida de estos objetos, liberándolos al final de la ejecución del script.
      java.lang.ObjectcreateMemptrHolder()
      Crea un objeto Holder para el tipo MEMPTR
      java.lang.ObjectcreateProDataGraph(java.lang.Object metadata)
      Crea un objeto de la clase ProDataGraph
      java.lang.ObjectcreateProDataGraphHolder()
      Crea un objeto Holder para el tipo ProDataGraphHolder
      java.lang.ObjectcreateProDataGraphMetaData(java.lang.String name)
      Crea un objeto de la clase ProDataGraphMetadata
      java.lang.ObjectcreateProDataObjectMetaData(java.lang.String tableName, int numFields, boolean bimageFlag, int numIndexes, java.lang.String multiIxCols, java.lang.String XMLNamespace, java.lang.String XMLPrefix)
      Crea un objeto de la clase ProDataObjectMetadata.
      Crea un objeto para un dataset (Temp-table).
      java.lang.ObjectcreateResultSetHolder()
      Crea un objeto Holder para el tipo TABLE
      java.lang.ObjectcreateRowidHolder()
      Crea un objeto Holder para el tipo ROWID
      java.lang.ObjectcreateStringHolder()
      Crea un objeto Holder para el tipo CHARACTER.
      java.lang.ObjectgetProxy()
      Devuelve la instancia del objeto de conexión a AppServer, ya conectado y disponible para su uso. El objeto remoto es la principal clase generada por ProxyGen.
      java.lang.Objectinstantiate(java.lang.String className)
      Instancia un objeto de una clase dentro de la biblioteca de proxy.
      java.lang.ObjecttoProDataGraph(com.datasul.technology.webdesk.dataset.DefaultDataset d)
      Transforma un dataset en un ProDataGraph.