Index:
General Considerations
The information contained in this document is intended to demonstrate how to perform the integration between Fluig and external applications. In order to have a full understanding of this information, some knowledge is considered a prerequisite, including:
- Overview of Fluig product
- Overview of system integration
- JavaScript
- WebServices
- SOAP
Progress® 4GL
- Progress® Open App Server
- Progress® Open Client for Java
- Datasets (Fluig)
- Java™
- Apache Flex®
In several parts of this document, code snippets will be presented in different programming languages in order to show the use of Fluig integration resources. However, this document does not intend to empower the reader to use these technologies beyond the above-described purpose, the reader being responsible for searching for more detailed information on these languages.
With the intent of making it easier to understand the information presented and simulation of the concepts presented, the examples mentioned in this document use the Dataset feature as an example of the use of the product integration resources. However, it is important to note that other points of the product have the same integration characteristics available as those existing in Datasets, especially customizations of forms and processes.
Overview
Although empirical, every company has business processes that enable the company to fulfill its objective, whether it is the provision of a service, the production of products or trading goods. A company has a multitude of processes, with each person in the organization being part of at least one of these processes, and all of them exchange information with each other at some point. The processes can be formal (such as hiring a professional) or informal (such as an incentive to innovation), critical (billing) or satellite (birthday card send control).
With the advent of Information System technologies, several systems began to provide support for these business processes, especially those considered most critical to company operation. The best example of this is the use of ERP systems that support the processes in various company areas.
Fluig has the objective of being an agnostic platform that manages processes, documents and identities through one collaborative communication interface. This can be realized to a greater or lesser degree in every one of its features, from the most simple (such as collaboration) to the more complex (such as DM and BPM).
However, part of these processes have a high reliance on existing information systems in the company and, therefore, Fluig's architecture is designed to enable integration with these systems, allowing the modeled processes to have higher added value.
Fluig allows both access through the product to external systems (to query or feed information) and enables other systems to connect to query information or to run transactional operations.
Integração Com Aplicativos Externos > image2013-8-20 15:33:42.png" src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-20%2015%3A33%3A42.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/image2013-8-20%2015%3A33%3A42.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406805" data-linked-resource-type="attachment" data-linked-resource-default-alias="image2013-8-20 15:33:42.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > image2013-8-20 15:33:42.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-20%2015%3A33%3A42.png?version=1&modificationDate=1377284793000&api=v2">
O principal canal de integração do produto é através de WebServices, que vêm se tornando o padrão mais comum de integração com qualquer aplicativo. Através deles, é possível ter acesso às funcionalidades do Fluig e dar acesso pelo produto à aplicativos externos. Este documento dedica uma seção específica sobre integração via WebServices.
A outra forma de integração é via chamadas ao Progress® Open AppServer e é indicada para usuários que precisem integrar o Fluig com aplicativos desenvolvidos nesta plataforma.
WebServices
A integração via WebServices utiliza o protocolo SOAP e, por ser um padrão aberto, permite que sistemas desenvolvidos em plataformas totalmente diferentes como Java™, Microsoft® .Net, C, C++, PHP, Ruby, Pearl, Python, entre outras, possam trocar informações entre si de forma transparente.
Acessando os WebServices do Fluig
O Fluig disponibiliza um conjunto de WebServices que permitem o acesso às informações do produto ou a execução de tarefas, como iniciar solicitações de processos por exemplo. Para ter a lista dos WebServices disponíveis, acesse o endereço:
http://<host>:<porta>/webdesk/services
Cada link apresentado direciona o navegador para a URL do WSDL do serviço. O WSDL (Web Service Description Language) possui a descrição do serviço no formato XML e é utilizado pelas ferramentas de desenvolvimento para a criação dos componentes que representarão este serviço.
Atente para cada tipo do atributo que é esperado, por exemplo, o atributo expirationDate do objeto DocumentDto[] é uma data, porém cada linguagem interpreta de maneira diferente, veja alguns exemplos abaixo:
- C#: dateTime
- Java™: XMLGregorianCalendar
- Progress®: DATETIME-TZ
Via Apache Flex®
Como a grande maioria das ferramentas de desenvolvimento, o Apache Flex® permite criar stubs para o acesso a web services. Estes stubs encapsulam todas as operações de empacotamento e desempacotamento das informações do padrão XML para os tipos nativos da plataforma.
Utilize o passo-a-passo para visualizar o processo de criação dos stubs para um serviço disponibilizado pelo Fluig:
A criação dos stubs no Flex® é feito através do menu Data, opção Import WebService(WSDL), conforme a imagem abaixo.
Integração Com Aplicativos Externos > image2013-8-21 10:30:3.png" src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-21%2010%3A30%3A3.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/image2013-8-21%2010%3A30%3A3.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406802" data-linked-resource-type="attachment" data-linked-resource-default-alias="image2013-8-21 10:30:3.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > image2013-8-21 10:30:3.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-21%2010%3A30%3A3.png?version=1&modificationDate=1377284793000&api=v2">
Na primeira janela, é solicitada a pasta dentro do projeto corrente onde devem ser gerados os stubs.
Integração Com Aplicativos Externos > image2013-8-21 10:34:29.png" src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-21%2010%3A34%3A29.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/image2013-8-21%2010%3A34%3A29.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406800" data-linked-resource-type="attachment" data-linked-resource-default-alias="image2013-8-21 10:34:29.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > image2013-8-21 10:34:29.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-21%2010%3A34%3A29.png?version=1&modificationDate=1377284793000&api=v2">
Na tela a seguir, deve ser informado o endereço do WSDL onde se encontra o serviço. Também é possível definir se ele será acessado da estação cliente ou do servidor LifeCycle Data Services.
Integração Com Aplicativos Externos > image2013-8-21 10:36:18.png" src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-21%2010%3A36%3A18.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/image2013-8-21%2010%3A36%3A18.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406797" data-linked-resource-type="attachment" data-linked-resource-default-alias="image2013-8-21 10:36:18.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > image2013-8-21 10:36:18.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-21%2010%3A36%3A18.png?version=1&modificationDate=1377284793000&api=v2">
Na última tela, deve-se informar o package que será utilizado e qual o nome da classe principal (já sugeridos pelo Flex™ Builder™).
Integração Com Aplicativos Externos > image2013-8-21 10:39:19.png" src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-21%2010%3A39%3A19.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/image2013-8-21%2010%3A39%3A19.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406796" data-linked-resource-type="attachment" data-linked-resource-default-alias="image2013-8-21 10:39:19.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > image2013-8-21 10:39:19.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-21%2010%3A39%3A19.png?version=1&modificationDate=1377284793000&api=v2">
Uma vez finalizado o processo, o Flex™ Builder™ adicionará ao projeto um conjunto de classes que serão utilizadas pelo programador para invocar os serviços, conforme a figura abaixo:
Integração Com Aplicativos Externos > image2013-8-21 10:57:40.png" src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-21%2010%3A57%3A40.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/image2013-8-21%2010%3A57%3A40.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406795" data-linked-resource-type="attachment" data-linked-resource-default-alias="image2013-8-21 10:57:40.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > image2013-8-21 10:57:40.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-21%2010%3A57%3A40.png?version=1&modificationDate=1377284793000&api=v2">
Para o exemplo apresentado abaixo, foi utilizado a IDE Adobe® Flex™ Builder™ 3.0 com Flex® SDK 3.2. Para outras versões da IDE e/ou SDK, o processo para criação dos stubs pode sofrer algumas variações.
O trecho de código abaixo apresenta um exemplo de invocação do WebService de acesso aos Datasets do Fluig:
<?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; //Cria uma instância do Stub de acesso ao serviço private var ds:ECMDatasetServiceService = new ECMDatasetServiceService(); public function start() : void { //Cria tipos auxiliares, que serão utilizados na chamado do serviço var fields:StringArray = new StringArray(); var constraints:SearchConstraintDtoArray = new SearchConstraintDtoArray(); var order:StringArray = new StringArray(); //Define as funções para tratamento do retorno ds.addEventListener(GetDatasetResultEvent.GetDataset_RESULT, resultGetDataset); ds.addEventListener(FaultEvent.FAULT,faultGetDataset); //invoca o método getDataset do serviço ds.getDataset("adm", 1, "adm", constraints, order, fields, "colleague"); } //Tratamento dos dados retornados do serviço invocado. public function resultGetDataset(ev:GetDatasetResultEvent) : void { //Recupera o retorno do serviço, na forma de um DatasetDto var dataset:DatasetDto = ev.result as DatasetDto; //Monta uma string com todos os dados do dataset var line:String = ""; //Cabeçalho com o nome dos campos var columnsArray:ArrayCollection = new ArrayCollection(dataset.columns); for (var j:int = 0; j < columnsArray.length; j++) { line += columnsArray.getItemAt(j) + "\t"; } //Linha de dados 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"; } } //Mostra a string criada em um textarea na tela 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 um bug do Flex® que impede o funcionamento correto de serviços que trabalhem com matrizes multidimensionais de dados, como no exemplo acima, onde é retornado um array (de linhas do Dataset) de array (das colunas de cada registro).
Para contornar este problema, é preciso alterar a classe gerada pelo Flex™ Builder™ que irá encapsular o array multidimensional. No exemplo acima, este classe é a DatasetDto, que deverá ser alterada (linha 11) conforme o exemplo abaixo:
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 o array }
Outros serviços que não trabalhem com arrays multidimensionais não exigem alterações no código gerado.
Via Java™
Existem muitas implementações de uso de WebServices em Java™ e neste exemplo vamos utilizar as bibliotecas disponíveis no Java™ 7.
Da mesma forma como no exemplo anterior, em Apache Flex®, o primeiro passo consiste em utilizar o endereço WSDL para a geração dos stubs em Java™. O comando abaixo apresenta um exemplo de como gerar estes stubs:
wsimport -d <output_directory> <wsdl_url>
Através deste comando são gerados os stubs no diretório de destino (output_directory), conforme a descrição do arquivo wsdl (wsdl_url).
Integração Com Aplicativos Externos > image2013-8-20 16:0:2.png" src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-20%2016%3A0%3A2.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/image2013-8-20%2016%3A0%3A2.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406803" data-linked-resource-type="attachment" data-linked-resource-default-alias="image2013-8-20 16:0:2.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > image2013-8-20 16:0:2.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/image2013-8-20%2016%3A0%3A2.png?version=1&modificationDate=1377284793000&api=v2">
A partir dos stubs gerados, é possível consumir o WebService como no exemplo abaixo:
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; /* * Classe para invocar serviço DatasetService */ public class ECMDatasetServiceClient { //Instancia DatasetServiceService. private ECMDatasetServiceService ecmDatasetServiceService = new ECMDatasetServiceService(); private DatasetService service = ecmDatasetServiceService.getDatasetServicePort(); //Inicia execução da classe public static void main(String[] args) { ECMDatasetServiceClient client = new ECMDatasetServiceClient(); //Configura acesso ao 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 { //Cria os parâmetros utilizados na chamada 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 o serviço de dataset DatasetDto result = service.getDataset( companyId, username, password, name, fields, constraints, order); //Apresenta o cabeçalho for (String columnName : result.getColumns()) { System.out.print(columnName + "\t"); } System.out.println(); //Apresenta as linhas do dataset for (ValuesDto row : result.getValues()) { for (Object value : row.getValue()) { System.out.print(value + "\t"); } System.out.println(); } } }
Ao utilizar os WebServices via Java™, deve-se atentar para o tipo de cada atributo e para o tipo de retorno do WebService. Por exemplo, para valores do tipo data deve ser utilizado a classe 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);
Via Progress® 4GL
Assim como nos exemplos anteriores, o primeiro passo para consumir um Webservice em Progress® é utilizar um utilitário que irá ler o endereço WSDL e gerar as informações necessárias para acessá-lo. Diferente do Java™ e Flex®, o Progress® não gera objetos de stub, mas apenas uma documentação sobre como consumir os serviços descritos no arquivo WSDL. Embora em algumas situações seja possível utilizar os tipos nativos do Progress® como parâmetros, dependendo do tipo de dado utilizado é preciso manipular o XML SOAP para extrair ou enviar uma informação.
Para gerar a documentação de um serviço, deve-se utilizar o utilitário bprowsdldoc como no exemplo abaixo:
bprowsdldoc <URL_TO_WSDL>
Com a execução deste utilitário, serão gerados alguns arquivos HTML com as informações sobre como consumir o serviço. Esta documentação apresenta informações e exemplos de como realizar a conexão com o serviçoe e a utilizar as métodos e datatypes do serviço.
O código abaixo apresenta um exemplo de como consumir o serviço:
/* 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.
Acessando WebServices a partir do Fluig
O Fluig permite fazer chamadas a WebServices de terceiros através do cadastro de Serviços na Visualização de Serviços do Fluig Studio.
Para adicionar um novo WebService, é preciso clicar na opção Incluir Serviço, abrindo o assistente Novo Serviço, e informar o servidor do Fluig onde será adicionado o serviço, um código identificador para ele, a sua descrição, a URL para o WSDL e o seu tipo (neste caso WebService). No exemplo abaixo, será utilizado um WebService público para consulta à tabela periódica, cujo endereço do WSDL é http://www.webservicex.com/periodictable.asmx?wsdl.
Integração Com Aplicativos Externos > acesso-ws-1.png" src="http://tdn.totvs.com/download/attachments/73082260/acesso-ws-1.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/acesso-ws-1.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406793" data-linked-resource-type="attachment" data-linked-resource-default-alias="acesso-ws-1.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > acesso-ws-1.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/acesso-ws-1.png?version=1&modificationDate=1377284793000&api=v2">
Com base nestas informações, o Fluig irá extrair as informações sobre o WebService informado e finalizará o cadastro deste serviço.
Uma vez que o serviço esteja cadastrado, é possível visualizar as classes e métodos disponíveis neste serviço e que serão utilizados nos códigos JavaScript que farão uso do mesmo. A tela abaixo apresenta um exemplo de visualização de WebService.
Integração Com Aplicativos Externos > acesso-ws-2.png" src="http://tdn.totvs.com/download/attachments/73082260/acesso-ws-2.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/acesso-ws-2.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406792" data-linked-resource-type="attachment" data-linked-resource-default-alias="acesso-ws-2.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > acesso-ws-2.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/acesso-ws-2.png?version=1&modificationDate=1377284793000&api=v2">
Os serviços adicionados no Fluig podem ser instanciados e utilizados nos pontos onde o produto permite personalização utilizando-se JavaScript, como nos scripts para eventos globais, eventos de processos, eventos de definição de formulário ou Datasets. No exemplo a seguir, será criado um Dataset que fará uso deste serviço para trazer os dados da tabela periódica.
O código abaixo apresenta uma implementação de exemplo do uso de um serviço na construção de um Dataset:
function createDataset(fields, constraints, sortFields) { //Cria o dataset var dataset = DatasetBuilder.newDataset(); dataset.addColumn("elementName"); // Conecta o servico e busca os livros var periodicService = ServiceManager.getService('PeriodicTable'); var serviceHelper = periodicService.getBean(); var serviceLocator = serviceHelper.instantiate('net.webservicex.Periodictable'); var service = serviceLocator.getPeriodictableSoap(); //Invoca o serviço 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; }
O primeiro passo para invocar o serviço é solicitar ao Fluig que carregue o serviço, a partir do método ServiceManager.getService('PeriodicTable'). O valor passado como parâmetro, deve ser o código utilizado quando cadastrado o serviço.
Uma vez que o serviço tenha sido carregado, é utilizado o método getBean() para retornar um utilitário para acesso às classes do serviço, através do método instantiate. Através deste utilitário, é possível instanciar as classes disponíveis e que estão listadas no cadastro do Serviço (conforme imagem vista anteriormente).
Uma vez que se tenha instanciado o objeto utilitário do serviço, as classes que devem ser instanciadas e os métodos que devem ser invocados dependem de cada WebService utilizado, e deve-se recorrer à sua documentação para mais informações.
Para o serviço da tabela periódica é necessário realizar os seguintes passos:
var serviceLocator = serviceHelper.instantiate('net.webservicex.Periodictable'); var service = serviceLocator.getPeriodictableSoap(); var result = service.getAtoms();
Onde:
Passo 1: Instanciar a classe net.webservicex.Periodictable para ter acesso ao localizador do serviço;
- Passo 2: Invocar o método getPeriodictableSoap para instanciar o serviço;
- Passo 3: Invocar o método getAtoms para ter a lista dos elementos.
No caso deste serviço, o método getAtoms retorna uma string contendo um XML com a lista de todos os elementos, conforme o exemplo abaixo:
<NewDataSet> <Table> <ElementName>Actinium</ElementName> </Table> <Table> <ElementName>Aluminium</ElementName> </Table> ... </NewDataSet>
Para percorrer o XML e extrair o dados disponíveis, são utilizadas as funcionalidades de tratamento de XML do JavaScript que facilita a manipulação de dados deste tipo. Para mais informações sobre esta funcionalidade, acesse: http://www.ecma-international.org/publications/standards/Ecma-357.htm ou http://www.xml.com/pub/a/2007/11/28/introducing-e4x.html.
O exemplo abaixo apresenta o código utilizado para percorrer o XML retornado:
var NewDataSet = new XML(result); for each(element in NewDataSet.Table) { dataset.addRow(new Array(element.ElementName.toString())); }
Uma vez implementado o código do Dataset, é possível visualizá-lo, conforme a figura abaixo:
Integração Com Aplicativos Externos > acesso-ws-3.png" src="http://tdn.totvs.com/download/attachments/73082260/acesso-ws-3.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/acesso-ws-3.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406791" data-linked-resource-type="attachment" data-linked-resource-default-alias="acesso-ws-3.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > acesso-ws-3.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/acesso-ws-3.png?version=1&modificationDate=1377284793000&api=v2">
WebServices com Autenticação Básica
Para consumir WebServices que fazem uso de autenticação básica (WSS ou WS-Security), é necessário utilizar o método getBasicAuthenticatedClient localizado no provider do serviço (o mesmo que é obtido via ServiceManager). Este método disponibiliza um client autenticado.
Os parâmetros que devem ser informados no método seguem a ordem abaixo:
- Instância do serviço
- Nome da classe do serviço
- Usuário para a autenticação
- Senha para a autenticação
Utilizando o exemplo do serviço PeriodicTable apresentado anteriormente, o código da chamada teria as alterações abaixo:
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 com client personalizado
Atenção
Esta técnica é válida para o Fluig 1.3.7 ou superior.
Em integrações que utilizem serviços criados com o CXF com sistemas que não suportam o protocolo HTTP/1.1 (Protheus, por exemplo), é necessário utilizar este método configurando o parâmetro "disable.chunking" com o valor "true".
Para personalizar o client que acessa os serviços, é necessário utilizar o método getCustomClient, localizado no provider do serviço (o mesmo que é obtido via ServiceManager). Esta configuração exige a criação de um mapa de parâmetros com seus respectivos valores para passar ao método, conforme snippet abaixo:
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();
Os parâmetros que podem ser definidos são os seguintes:
Propriedade | Função |
---|---|
basic.authorization | Quando definido como "true", faz o mesmo que o método getBasicAuthenticatedClient, porém permite aplicar as configurações de autenticação juntamente com as demais personalizações abaixo. Para configurar a autenticação, as propriedades com "username" e "password" abaixo também precisam ser definidas. |
basic.authorization.username | Usuário a ser utilizado para autenticação básica. |
basic.authorization.password | Senha do usuário utilizado para autenticação básica. |
disable.chunking | Quando definido como "true", desabilita o envio de requisições grandes em "pedaços" menores. Pode ser útil quando o serviço chamado não suporta este tipo de requisição. |
log.soap.messages | Quando definido como "true", permite que as mensagens SOAP utilizadas nas requisições feitas aos serviços sejam apresentadas no log do servidor, facilitando a depuração em caso de falhas. |
Resolvendo conflitos utilizando arquivos de bind JAXB
Atenção
Esta técnica é válida apenas para serviços criados utilizando a API CXF.
Ao criar serviços no Fluig podem ocorrer alguns conflitos impedindo a geração dos stubs. Normalmente isso ocorre quando temos um elemento do schema do WSDL com duas ou mais propriedades com o mesmo identificador ou nome, o que impede a criação da Classe Java desse elemento.
Para resolver esse conflitos podem ser utilizados arquivos de bind JAXB, conforme a figura abaixo:
Integração Com Aplicativos Externos > Binding.png" src="http://tdn.totvs.com/download/attachments/73082260/Binding.png?version=1&modificationDate=1422607474000&api=v2" data-image-src="/download/attachments/73082260/Binding.png?version=1&modificationDate=1422607474000&api=v2" data-linked-resource-id="184844668" data-linked-resource-type="attachment" data-linked-resource-default-alias="Binding.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > Binding.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/Binding.png?version=1&modificationDate=1422607474000&api=v2">
Esse arquivo tem o propósito de personalizar a geração dos stubs alterando o nome das propriedades conflitantes.
A seguir temos dois exemplos de utilização desses arquivos:
Exemplo 1 :
Durante a criação do serviço ocorreu o seguinte erro :
Não foi possível salvar os stubs para acesso ao serviço: http://localhost:8080/pcliente/CHAMTEC.apw?WSDL [2255,11]: Two declarations cause a collision in the ObjectFactory class. http://localhost:8080/pcliente/CHAMTEC.apw?WSDL [2245,11]: (Related to above error) This is the other declaration.
O que indica que temos duas declarações de propriedades conflitantes, conforme o fragmento do WSDL abaixo:
<s:complexType name="STRUCT_SA1"> <s:sequence> ... <s:element minOccurs="1" maxOccurs="1" name="_A1COD_MUN" type="s:string"/> ... <s:element minOccurs="1" maxOccurs="1" name="_A1CODMUN" type="s:string"/> ... </s:sequence> </s:complexType>
Os identificadores "_A1COD_MUN" e "_A1CODMUN" são considerados iguais pela API de geração dos stubs, e fazem parte do mesmo tipo complexo "STRUCT_SA1".
Para resolver esse conflito, pode ser utilizado o arquivo de bind abaixo :
<?xml version="1.0" encoding="utf-8"?> <jxb:bindings version="1.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" jxb:extensionBindingPrefixes="xjc"> <jxb:bindings schemaLocation="http://localhost:8080/pcliente/CHAMTEC.apw?WSDL#types1" node="/xsd:schema"> <jxb:bindings node="//xsd:complexType[@name='STRUCT_SA1']//xsd:sequence//xsd:element[@name='_A1COD_MUN']"> <jxb:property name="_A1COD_MUN2"/> </jxb:bindings> </jxb:bindings> </jxb:bindings>
Esse arquivo faz com que o elemento "_A1COD_MUN" contido no tipo complexo "STRUCT_SA1" assuma o nome "_A1COD_MUN2", resolvendo assim o conflito na geração dos stubs.
Exemplo 2 :
Durante a criação do serviço ocorreu o seguinte erro :
Não foi possível salvar os stubs para acesso ao serviço: http://localhost:8080/tbc/wsConsultaSQL.asmx?WSDL [49,19]: Property "Any" is already defined. Use <jaxb:property> to resolve this conflict. http://localhost:8080/tbc/wsConsultaSQL.asmx?WSDL [50,19]: The following location is relevant to the above error http://localhost:8080/tbc/wsConsultaSQL.asmx?WSDL [75,19]: Property "Any" is already defined. Use <jaxb:property> to resolve this conflict. http://localhost:8080/tbc/wsConsultaSQL.asmx?WSDL [76,19]: The following location is relevant to the above error
O que indica que temos duas propriedades com a identificação "any" no nosso elemento, conforme o fragmento do arquivo WSDL abaixo:
<s:element name="RealizarConsultaSQLDataTableResponse"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="RealizarConsultaSQLDataTableResult"> <s:complexType> <s:sequence> <s:any minOccurs="0" maxOccurs="unbounded" namespace="http://www.w3.org/2001/XMLSchema" processContents="lax" /> <s:any minOccurs="1" namespace="urn:schemas-microsoft-com:xml-diffgram-v1" processContents="lax" /> </s:sequence> </s:complexType> </s:element> </s:sequence> </s:complexType> </s:element> ... <s:element name="RealizarConsultaSQLDataTableAuthResponse"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="RealizarConsultaSQLDataTableAuthResult"> <s:complexType> <s:sequence> <s:any minOccurs="0" maxOccurs="unbounded" namespace="http://www.w3.org/2001/XMLSchema" processContents="lax" /> <s:any minOccurs="1" namespace="urn:schemas-microsoft-com:xml-diffgram-v1" processContents="lax" /> </s:sequence> </s:complexType> </s:element> </s:sequence> </s:complexType> </s:element>
Essa situação ocorre em dois elementos do arquivo WSDL, o "RealizarConsultaSQLDataTableResponse" e o "RealizarConsultaSQLDataTableAuthResponse".
Para resolver esse conflito, pode ser utilizado o arquivo de bind abaixo :
<?xml version="1.0" encoding="utf-8"?> <jxb:bindings version="1.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" jxb:extensionBindingPrefixes="xjc"> <jxb:bindings schemaLocation="http://localhost:8080/tbc/wsConsultaSQL.asmx?WSDL#types1" node="/xsd:schema"> <jxb:bindings node="//xsd:element[@name='RealizarConsultaSQLDataTableResponse']//xsd:sequence//xsd:any[position()=2]"> <jxb:property name="any2"/> </jxb:bindings> <jxb:bindings node="//xsd:element[@name='RealizarConsultaSQLDataTableAuthResponse']//xsd:sequence//xsd:any[position()=2]"> <jxb:property name="any2"/> </jxb:bindings> </jxb:bindings> </jxb:bindings>
Esse arquivo faz com que a segunda propriedade "any" de cada um dos elementos assuma o nome "any2", resolvendo assim o conflito na geração dos stubs.
Maiores informações sobre personalizações via JAXB Bindings em http://docs.oracle.com/javase/tutorial/jaxb/intro/custom.html
Progress® Open AppServer
Assim como é possível invocar operações em WebServices, o Fluig também permite fazer chamadas a programas em Progress® 4GL (ou ABL) expostos via Progress® Open AppServer.
Nos exemplos a seguir, serão criados Datasets que, via camada de serviço, farão o acesso à lógicas em 4GL que farão a extração dos dados. Embora os códigos 4GL, neste exemplo, sejam muito simples, eles compreendem os casos mais comuns exigidos no dia-a-dia, uma vez que a complexidade da integração se encontra nas interfaces (parâmetros de entrada e saída) de cada procedure exposta, e não na sua lógica interna.
Observe que os exemplos aqui apresentados têm por objetivo demonstrar a dinâmica de integração entre Progress® e o Fluig sem entrar em detalhes específicos das tecnologias envolvidas. A camada de serviço Progress® do Fluig cria uma interface em JavaScript para a biblioteca Java Open AppServer Client, da Progress® e, por isso, para mais informações sobre como integrar aplicativos Java™ e Progress® consulte a documentação fornecida pela Progress®.
Caso de Uso
Os exemplos exibidos a seguir, têm por objetivo a criação de quatro Datasets 1 no Fluig:
- Tipos de Centro de Custo, que deve retornar os tipos de centro de custo existentes no aplicativo em Progress® (neste caso, o EMS2).
- Natureza dos Centros de Custo, que deve retornar os tipos possíveis de natureza, conforme o aplicativo em Progress® (neste caso, o EMS2).
- Centros de Custo, que deve retornar os registros na tabela conta 2.
Usuários Comuns, que deve gerar uma lista de usuários comuns entre o Fluig e o aplicativo em Progress® (utilizando a tabela usuar_mestre).
1 - Os exemplos utilizam uma base de dados do EMS2 para consulta de centros de custo e usuários. Entretanto, apenas duas tabelas e 6 campos são utilizados no total, o que não deve prejudicar o entendimento da lógica pelo leitor, nem impedir a criação de um esquema equivalente para testes, caso necessário.
2 - O código apresentado para extração dos centros de custo tem fins meramente didáticos e não pode ser considerado para uso em produção. Para ter mais informações sobre como extrair centros de custos do EMS2, consulte a documentação técnica do mesmo.
Para os três primeiros casos, a lógica de extração das informações desejadas será exposta em um programa com várias procedures, uma para cada necessidade aqui apresentada:
/************************************************************************** ** Utilitário que disponibiliza procedures para a extração de informações ** sobre centros de custo. **************************************************************************/ DEFINE TEMP-TABLE ttCC NO-UNDO FIELD conta LIKE conta.ct-codigo /* CHARACTER */ FIELD natureza LIKE conta.natureza /* INTEGER */ FIELD tipo LIKE conta.tipo /* INTEGER */ FIELD titulo LIKE conta.titulo. /* CHARACTER */ /*------------------------------------------------------------------- Procedure: readCostCenters Objetivo: Retorna uma temp-table com a lista de centros de custo. ----------------------------------------------------------------------*/ PROCEDURE readCostCenters: DEFINE OUTPUT PARAMETER TABLE FOR ttCC. FOR EACH conta: CREATE ttCC. ASSIGN ttCC.conta = conta.ct-codigo ttCC.natureza = conta.natureza ttCC.tipo = conta.tipo ttCC.titulo = conta.titulo. END. END. /*------------------------------------------------------------------- Procedure: readCostNatureTypes Objetivo: Retorna uma string com as naturezas dos centros de custo, separadas por vírgula. ----------------------------------------------------------------------*/ PROCEDURE readCostNatureTypes: DEFINE OUTPUT PARAMETER cNatureList AS CHARACTER NO-UNDO. cNatureList = {adinc/i01ad047.i 03}. END. /*------------------------------------------------------------------- Procedure: readCostTypes Objetivo: Retorna uma string com os tipos de centro de custo, separados por vírgula. ----------------------------------------------------------------------*/ PROCEDURE readCostTypes: DEFINE OUTPUT PARAMETER cTypeList AS CHARACTER NO-UNDO. cTypeList = {adinc/i02ad047.i 3}. END.
No caso da extração de usuários comuns aos dois produtos, será utilizado um programa único, conforme o código abaixo:
/************************************************************************** ** Utilitário que recebe um temp-table com uma lista de usuários e retorna ** outra, apenas com os usuários da lista que existam na base de dados. **************************************************************************/ 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.
Os dois códigos apresentados têm diferenças significativas na forma como são utilizados e na forma como serão expostos pelo Progress®. No primeiro, o programa é carregado de forma persistente e suas procedures podem ser executadas de forma independente. No segundo caso, o programa é executado de forma não-persistente e a lógica principal se encontra no main-block. As procedures internas, caso existam, têm por objetivo melhorar a organização do código e não podem ser utilizadas de forma isolada.
Configuração do AppServer
Algumas informações importantes na configuração do AppServer:
- O AppServer deve ser carregado no modo Stateless;
Na configuração do agente, no campo Propath, deve ser adicionado o diretório onde estão localizados os arquivos compilados (.r).
Importante: Quando utilizado um caminho relativo (\\servidor\pasta), o serviço Windows® do Progress® (AdminService) deve ser iniciado com um usuário de rede que possua permissão de acesso ao diretório informado.
Expondo códigos 4GL com ProxyGen
O primeiro passo para que seja possível executar rotinas em Progress® 4GL é criar a biblioteca cliente, o que é feito com o uso do aplicativo ProxyGen, que acompanha a instalação do Progress®, conforme o exemplo abaixo.
Utilize o passo-a-passo para visualizar o processo de criação do proxy:
- Na primeira tela do ProxyGen, o principal ponto que deve ser observado é o nome do Projeto (no exemplo, EMSProxies). A informação deste campo será utilizada pelo ProxyGen para nomear a classe de acesso ao serviço, e que será utilizada na configuração do serviço no Fluig. Nesta tela também é preciso configurar o PROPATH corretamente, para que seja possível encontrar os arquivos compilados (.r).
Integração Com Aplicativos Externos > proxygen-1.png" src="http://tdn.totvs.com/download/attachments/73082260/proxygen-1.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/proxygen-1.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406790" data-linked-resource-type="attachment" data-linked-resource-default-alias="proxygen-1.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > proxygen-1.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/proxygen-1.png?version=1&modificationDate=1377284793000&api=v2">
- O segundo passo consiste em inserir quais procedures serão expostas de forma persistente ou não-persistente. A escolha de qual opção utilizar depende da forma como cada objeto exposto foi construído. Após inseridas as procedures, clicar na opção Generate.
Integração Com Aplicativos Externos > proxygen-2.png" src="http://tdn.totvs.com/download/attachments/73082260/proxygen-2.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/proxygen-2.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406789" data-linked-resource-type="attachment" data-linked-resource-default-alias="proxygen-2.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > proxygen-2.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/proxygen-2.png?version=1&modificationDate=1377284793000&api=v2">
- Durante o processo de geração do proxy, na aba General, assinalar a opção Java em Client Proxy e informar o diretório onde o proxy será gerado em Output Dir. Observe também o campo AppService, este deve ser o nome do serviço publicado no AppServer, caso contrário não será possível conectar ao servidor.
Integração Com Aplicativos Externos > proxygen-3.png" src="http://tdn.totvs.com/download/attachments/73082260/proxygen-3.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/proxygen-3.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406788" data-linked-resource-type="attachment" data-linked-resource-default-alias="proxygen-3.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > proxygen-3.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/proxygen-3.png?version=1&modificationDate=1377284793000&api=v2">
- A última informação relevante para a geração do proxy é o nome do pacote (package) onde as classes serão criadas. Esta informação é utilizada durante a configuração do serviço Progress® no Fluig. Para finalizar clicar no botão OK.
Integração Com Aplicativos Externos > proxygen-4.png" src="http://tdn.totvs.com/download/attachments/73082260/proxygen-4.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/proxygen-4.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406787" data-linked-resource-type="attachment" data-linked-resource-default-alias="proxygen-4.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > proxygen-4.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/proxygen-4.png?version=1&modificationDate=1377284793000&api=v2">
- Uma vez criadas as classes, é preciso empacotá-las em um arquivo .JAR. Isto pode ser feito via linha de comando, utilizando-se o comando abaixo:
jar -cvf <jar_file_name> <diretorio>
Observe apenas que no arquivo gerado, é preciso que as classes estejam nos diretórios corretos. No exemplo apresentado, o diretório com deve ser incluído e estar no raiz do arquivo JAR. Por ser compatível com o formato ZIP, uma outra opção é gerar um arquivo com as classes geradas (respeitando-se os diretórios) e renomeá-lo para a extensão .JAR.
Dependendo da versão do Progress®, as telas podem sofrer alguma variação na quantidade e disposição dos campos. Consulte a documentação em caso de dúvida
Configuração do Serviço no Fluig
O cadastro de um serviço é realizado através do Fluig Studio, na view Visualização de Serviços, pela opção Incluir Serviço. A tela abaixo apresenta o assistente de novo serviço e os campos utilizados para o cadastro do serviço Progress®:
Integração Com Aplicativos Externos > servico-process-1.png" src="http://tdn.totvs.com/download/attachments/73082260/servico-process-1.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/servico-process-1.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406786" data-linked-resource-type="attachment" data-linked-resource-default-alias="servico-process-1.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > servico-process-1.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/servico-process-1.png?version=1&modificationDate=1377284793000&api=v2">
Onde:
- Servidor: Servidor do Fluig onde será adicionado o serviço;
- Código: Código único que irá identificar o serviço no sistema. Este código será utilizado nos códigos JavaScript para ter acesso a este serviço;
- Descrição: Texto que descreve o serviço de dados;
- URL: Identifica a URL de acesso ao serviço AppServer, como por exemplo AppServer://<servidor>/<nome_serviço>;
No caso de não estar utilizando o NameServer padrão deve ser informada a porta de acesso ao NameServer. Ex.: AppServer://<servidor>:<porta_NameServer>/<nome_serviço>.
Observe que dependendo da configuração do serviço e/ou da forma de conexão, a URL pode sofrer alterações. Verifique a documentação do Open AppServer para mais informações. - Tipo: Identifica o tipo de serviço (Progress ou WebService). Deve ser selecionado Progress;
- Objeto Remoto: Identifica a classe de acesso do proxy. Esta classe normalmente é formada pelo nome do pacote utilizado para a geração das classes Proxy, mais o nome do projeto no ProxyGen.
Exemplo: Nas telas que apresentam o ProxyGen, foi utilizado o pacote com.fluig.samples.ems, e o nome dado ao projeto no ProxyGen foi EMSProxies. Neste caso, a classe do Objeto remoto será com.fluig.samples.ems.EMSProxies; - Usuário: Usuário utilizado na conexão ao serviço, conforme definido nas configurações no AppServer;
- Senha: Senha utilizada na conexão ao serviço, conforme definida nas configurações no AppServer;
- Parâmetros Extras: Parâmetros extras (e opcionais) utilizados para conexão ao AppServer. Verifique a documentação do Open AppServer para verificar as opções disponíveis em cada versão do Progress®;
- Diretório do arquivo de Proxy: Arquivo .JAR contendo as classes geradas pelo ProxyGen; Deve ser utilizado o botão Selecionar Arquivo para localizar o mesmo.
Uma vez que o serviço tenha sido adicionado, é possível visualizar as classes disponíveis e os métodos existentes em cada uma delas. Estas informações são importantes para guiar o desenvolvimento dos códigos personalizados que farão uso deste serviço. Para visualizar as classes e métodos do serviço, utilize a opção Consulta Serviço na Visualização de Serviços, conforme a tela abaixo:
Integração Com Aplicativos Externos > servico-process-2.png" src="http://tdn.totvs.com/download/attachments/73082260/servico-process-2.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/servico-process-2.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406785" data-linked-resource-type="attachment" data-linked-resource-default-alias="servico-process-2.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > servico-process-2.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/servico-process-2.png?version=1&modificationDate=1377284793000&api=v2">
Visão Geral dos Objetos Envolvidos
O acesso às procedures expostas no AppServer envolve quatro elementos que interagem entre si, conforme o diagrama abaixo:
Integração Com Aplicativos Externos > diagrama objetos envolvidos.png" src="http://tdn.totvs.com/download/attachments/73082260/diagrama%20objetos%20envolvidos.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/diagrama%20objetos%20envolvidos.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406794" data-linked-resource-type="attachment" data-linked-resource-default-alias="diagrama objetos envolvidos.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > diagrama objetos envolvidos.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/diagrama%20objetos%20envolvidos.png?version=1&modificationDate=1377284793000&api=v2">
Onde:
- Script Code: é o código em JavaScript que fará uso das procedures expostas no AppServer. Como mencionado anteriormente, este JavaScript pode ser de qualquer natureza, como a implementação de um Dataset ou a personalização de um evento de processo.
- Service Provider: Objeto recuperado através do método ServiceManager.getService e que fornece o acesso às funcionalidades do serviço. Este objeto é responsável por gerenciar a conexão e recursos alocados pelo serviço durante a execução do script.
- Service Helper: Objeto recuperado via método getBean no ServiceProvider e que disponibiliza um conjunto de métodos utilitários que permitem, entre outras coisas, criar objetos específicos do Progress® (StringHolder, ResultSetHolder, etc.), ter acesso ao objeto remoto do ProxyGen e instanciar classes. Para mais informações sobre o Service Helper consultar aqui.
- ProxyGen Classes: Classes geradas pelo ProxyGen e que serão utilizadas pelo desenvolvedor para execução das rotinas em Progress®. A lista das classes disponíveis, bem como os seus métodos, podem ser visualizados na Visualização de Serviços do Fluig Studio.
Procedures Persistentes e Não-Persistentes
Quando uma procedure é adicionada ao projeto do ProxyGen, ela deve ser configurada em duas listas: Procedures Persistentes ou Não-Persistentes. Esta decisão implica diretamente na forma como estes objetos são acessados pela biblioteca gerada e, consequentemente, na forma como o desenvolvedor irá acessá-las nos códigos JavaScript.
As procedures expostas de forma não-persistente dão origem à métodos na classe configurada como Objeto Remoto (ou Proxy) no Serviço, e a sua execução é feita chamando o método correspondente, por exemplo:
serviceHelper.getProxy().verifyUsers(inputTT, holder).
As procedures expostas de forma persistente dão origem à novas classes que podem ser instanciadas via chamadas a métodos no Objeto Remoto (através da Visualização de Serviços no Fluig Studio é possível verificar os métodos disponíveis na classe), ou via o método createManagedObject. A chamada via o método createManagedObject permite que o Fluig tenha controle sobre o ciclo de vida deste objeto, liberando-o automaticamente ao fim do método. Caso o objeto seja instanciado manualmente, o desenvolvedor deve codificar a liberação do objeto (método _release()), sob pena de bloquear um novo agente do AppServer a cada invocação do método.
Parâmetros de Entrada e Saída
Outro ponto importante na invocação das rotinas em 4GL é observar quais os tipos de parâmetros de entrada e saída de cada procedure ou programa. Dependendo do tipo de dado (CHARACTER, INTEGER, TEMP-TABLE, etc.), do tipo de parâmetro (INPUT, INPUT-OUTPUT, BUFFER, etc.) e da versão utilizada do Progress®, a forma de se manipular estes parâmetros pode variar.
Para os tipos mais simples, como strings, datas ou valores inteiros, o ProxyGen utiliza um mapeamento direto para os tipos ou classes padrões da linguagem Java™. Para tipos complexos, como temp-tables e buffers, o ProxyGen utiliza classes que fazem parte da biblioteca de runtime destes serviços.
Quando os parâmetros são de entrada e saída (INPUT-OUTPUT) ou de saída (OUTPUT), os tipos primitivos precisam ser substituídos por classes Holders, que conterão o valor retornado após a execução do método. Os exemplos mais comuns são StringHolder ou ResultSetHolder.
Os tipos de dados utilizados em cada método podem ser consultado através da Visualização de Serviços no Fluig Studio. Observe que dependendo da versão do Progress® pode haver variação nos tipos de parâmetros utilizados e na forma de utilizá-los. Em caso de dúvida, consulte a documentação fornecida pela Progress®.
Criação dos Datasets
Uma vez que o serviço Progress® tenha sido adicionado no Fluig, é possível utilizá-lo nos pontos onde o produto permite persoanlização utilizando-se JavaScript, como nos scripts para eventos globais, eventos de processos, eventos de definição de formulário ou Datasets.
A forma de se invocar as rotinas expostas pelo serviço é sempre a mesma, independente de qual ponto está sendo chamado. Entretanto, para facilitar o entendimento do uso dos serviços Progress® no Fluig e facilitar a reprodução dos exemplos apresentados no ambiente do leitor, todos os exemplos abaixo utilizarão Datasets como alvo.
Conforme visto anteriormente, os Datasets que serão apresentados aqui são Tipos de Centro de Custo, Natureza dos Centros de Custo, Centros de Custo e Usuários em Comum.
Tipos de Centro de Custo
O código abaixo apresenta a implementação do Dataset de Tipos de Centro de Custo. A explicação de cada passo da implementação será apresentada após o código:
function createDataset(fields, constraints, sortFields) { //Passo 1 - Cria o dataset var dataset = DatasetBuilder.newDataset(); dataset.addColumn("id"); dataset.addColumn("descricao"); //Passo 2 - Invoca o serviço cadastrado no Fluig var servico = ServiceManager.getService("EMS2"); //Passo 3 - Carrega o objeto utilitário para integração com Progress var serviceHelper = servico.getBean(); //Passo 4 - Carrega a procedure persistente CostCenterUtils.p var remoteObj = serviceHelper.createManagedObject("CostCenterUtils"); //Passo 5 - Invoca a procedure que retorna uma string com os tipos de CC var types = serviceHelper.createStringHolder(); remoteObj.readCostTypes(types); //Passo 6 - Quebra a string em um array com cada um dos tipos var typeArray = types.getStringValue().split(","); //Passo 7 - Adiciona cada tipo retornado for(var pos = 0; pos < typeArray.length; pos++) { dataset.addRow(new Array(pos + 1, typeArray[pos])); } return dataset; }
Onde:
- Passo 1: Cria o dataset e adiciona os campos do mesmo;
- Passo 2: É feita a invocação do serviço cadastrado no Fluig, através do método ServiceManager.getService, e o valor passado como parâmetro deve ser o código do serviço. Note que neste ponto não é preciso informar qualquer parâmetro de conexão ao serviço, uma vez que isto já foi feito no seu cadastro;
- Passo 3: Utiliza o método getBean para retornar um objeto utilitário para serviços Progress®. Este utilitário disponibiliza uma série de métodos que facilitam a interação com o proxy gerado e seus métodos serão apresentados em mais detalhes adiante neste documento;
- Passo 4: Faz a carga do objeto CostCenterUtils de forma gerenciada, através do método createManagedObject do utilitário criado anteriormente;
- Passo 5: Invoca o método desejado, neste caso o readCostTypes, passando um StringHolder que irá receber o valor de saída;
- Passo 6: Transforma a String recebida por parâmetro em um array com as opções. O caractere , (vírgula) é utilizado para determinar os pontos de quebra da string;
- Passo 7: Percorre o array criado, adicionando uma linha no Dataset para cada item do array.
A tela abaixo apresenta a visualizaçào dos dados do Dataset criado:
Integração Com Aplicativos Externos > ems-dt1.png" src="http://tdn.totvs.com/download/attachments/73082260/ems-dt1.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/ems-dt1.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406784" data-linked-resource-type="attachment" data-linked-resource-default-alias="ems-dt1.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > ems-dt1.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/ems-dt1.png?version=1&modificationDate=1377284793000&api=v2">
Natureza dos Centros de Custo
O Dataset de Natureza dos Centros de Custo é muito similar ao Dataset de tipo de centro de custo. Na prática, a única alteração é a procedure que é chamada:
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; }
Após o cadastro do Dataset, é possível visualizar o seu conteúdo:
Integração Com Aplicativos Externos > ems-dt2.png" src="http://tdn.totvs.com/download/attachments/73082260/ems-dt2.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/ems-dt2.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406783" data-linked-resource-type="attachment" data-linked-resource-default-alias="ems-dt2.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > ems-dt2.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/ems-dt2.png?version=1&modificationDate=1377284793000&api=v2">
Centros de Custo
O Dataset de Centros de Custo possui uma estrutura muito semelhante aos dois Datasets vistos anteriormente. A diferença principal é que, neste caso, a procedure retorna uma temp-table com os centros de custo, o que altera a forma como os dados são manipulados.
Dependendo da versão do Progress®, os objetos utilizados podem variar. A seguir, são apresentados exemplos da codificação para Progress® 9 e OpenEdge® 10, respectivamente. Em ambos os casos, o resultado apresentado pelo Dataset será o mesmo.
Codificação Progress® 9
As temp-table no Progress® 9 são tratadas através de objetos que implementam a interface java.sql.ResultSet:
function createDataset(fields, constraints, sortFields) { //Cria a estrutura do Dataset var dataset = DatasetBuilder.newDataset(); dataset.addColumn("conta"); dataset.addColumn("titulo"); dataset.addColumn("natureza"); dataset.addColumn("tipo"); //Recupera o serviço e carrega o objeto remoto var servico = ServiceManager.getService("EMS2"); var serviceHelper = servico.getBean(); var remoteObj = serviceHelper.createManagedObject("CostCenterUtils"); //Lê as contas correntes var holder = serviceHelper.createResultSetHolder(); remoteObj.readCostCenters(holder); //Percorre os registros, carregando o Dataset com os dados var rs = holder.getResultSetValue(); while (rs.next()) { var conta = rs.getObject("conta"); var natureza = rs.getObject("natureza"); var tipo = rs.getObject("tipo"); var titulo = rs.getObject("titulo"); dataset.addRow(new Array(conta, titulo, natureza, tipo)); } return dataset; }
Codificação OpenEdge® 10
No OpenEdge® 10, as temp-tables retornadas são encapsuladas como objetos da classe ProDataGraph. Esta classe também é utilizada quando se utilizam parâmetros do tipo DATASET:
function createDataset(fields, constraints, sortFields) { //Cria a estrutura do Dataset var dataset = DatasetBuilder.newDataset(); dataset.addColumn("conta"); dataset.addColumn("titulo"); dataset.addColumn("natureza"); dataset.addColumn("tipo"); //Recupera o serviço e carrega o objeto remoto var servico = ServiceManager.getService("EMS2"); var serviceHelper = servico.getBean(); var remoteObj = serviceHelper.createManagedObject("CostCenterUtils"); //Lê as contas correntes var holder = serviceHelper.createProDataGraphHolder(); remoteObj.readCostCenters(holder); //Percorre os registros, carregando o Dataset com os dados 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("conta"), row.get("titulo"), row.get("natureza"), row.get("tipo"))); } return dataset; }
Visualização do Dataset:
Integração Com Aplicativos Externos > ems-dt3.png" src="http://tdn.totvs.com/download/attachments/73082260/ems-dt3.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/ems-dt3.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406782" data-linked-resource-type="attachment" data-linked-resource-default-alias="ems-dt3.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > ems-dt3.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/ems-dt3.png?version=1&modificationDate=1377284793000&api=v2">
Usuários em Comum
A primeira diferença entre o Dataset de usuários comuns e os exemplos anteriores, é que neste caso é preciso passar uma temp-table como parâmetro para a procedure invocada.
A segunda diferença é que o código 4GL está implementado em um programa não-persistente, o que altera a forma como a lógica é invocada a partir do código JavaScript.
A terceira diferença que pode se observar neste caso é que é possível transformar um Dataset nos tipos de dados requeridos pelo Progress® (ResultSet ou ProDataGraph).
Codificação Progress® 9
function createDataset(fields, constraints, sortFields) { //Cria o novo Dataset var dataset = DatasetBuilder.newDataset(); dataset.addColumn("usuario"); dataset.addColumn("nome"); //Recupera os usuários do Fluig var campos = new Array("colleaguePK.colleagueId", "colleagueName"); var colleaguesDataset = DatasetFactory.getDataset("colleague", campos, null, null); //Instancia o servico var servico = ServiceManager.getService("EMS2"); var serviceHelper = servico.getBean(); //Transforma o dataset em um ResultSet (v9) e cria holder para saida var inputTT = colleaguesDataset.toResultSet(); var holder = serviceHelper.createResultSetHolder(); //Invoca a procedure no 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; }
Codificação OpenEdge® 10
function createDataset(fields, constraints, sortFields) { //Cria o novo Dataset var dataset = DatasetBuilder.newDataset(); dataset.addColumn("usuario"); dataset.addColumn("nome"); //Recupera os usuários do Fluig var campos = new Array("colleaguePK.colleagueId", "colleagueName"); var colleaguesDataset = DatasetFactory.getDataset("colleague", campos, null, null); //Instancia o servico var servico = ServiceManager.getService("EMS2"); var serviceHelper = servico.getBean(); //Transforma o dataset em um ProDataGraph (v10) e cria holder para saida var inputTT = serviceHelper.toProDataGraph(colleaguesDataset); var holder = serviceHelper.createProDataGraphHolder(); //Invoca a procedure no 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; }
Visualização do Dataset:
Integração Com Aplicativos Externos > ems-dt4.png" src="http://tdn.totvs.com/download/attachments/73082260/ems-dt4.png?version=1&modificationDate=1377284793000&api=v2" data-image-src="/download/attachments/73082260/ems-dt4.png?version=1&modificationDate=1377284793000&api=v2" data-linked-resource-id="73406781" data-linked-resource-type="attachment" data-linked-resource-default-alias="ems-dt4.png" data-base-url="http://tdn.totvs.com" data-linked-resource-container-id="73082260" data-location="Fluig > Integração Com Aplicativos Externos > ems-dt4.png" data-mce-src="http://tdn.totvs.com/download/attachments/73082260/ems-dt4.png?version=1&modificationDate=1377284793000&api=v2">
Service Helper
A tabela abaixo apresenta a lista de métodos existentes na classe utilitária para serviços Progress®:
Retorno | Método e Descrição |
---|---|
java.lang.Object | createBigDecimalHolder() Cria um objeto Holder para o tipo DECIMAL |
java.lang.Object | createBooleanHolder() Cria um objeto Holder para o tipo LOGICAL |
java.lang.Object | createByteArrayHolder() Cria um objeto Holder para o tipo RAW |
java.lang.Object | createCOMHandleHolder() Cria um objeto Holder para o tipo COM-HANDLE |
java.lang.Object | createDateHolder() Cria um objeto Holder para o tipo DATE |
java.lang.Object | createHandleHolder() Cria um objeto Holder para o tipo WIDGET-HANDLE (Handle) |
java.lang.Object | createIntHolder() Cria um objeto Holder para o tipo INTEGER |
java.lang.Object | createLongHolder() Cria um objeto Holder para o tipo RECID |
java.lang.Object | createManagedObject(java.lang.String objName) Lê um arquivo .p ou .r que tenha sido exposto via AppServer de forma persistente. Através deste método o provedor do serviço pode gerenciar o ciclo de vida destes objetos, liberando-os ao final da execução do script. |
java.lang.Object | createMemptrHolder() Cria um objeto Holder para o tipo MEMPTR |
java.lang.Object | createProDataGraph(java.lang.Object metadata) Cria um objeto da classe ProDataGraph |
java.lang.Object | createProDataGraphHolder() Cria um objeto Holder para o tipo ProDataGraphHolder |
java.lang.Object | createProDataGraphMetaData(java.lang.String name) Cria um objeto da classe ProDataGraphMetadata |
java.lang.Object | createProDataObjectMetaData(java.lang.String tableName, int numFields, boolean bimageFlag, int numIndexes, java.lang.String multiIxCols, java.lang.String XMLNamespace, java.lang.String XMLPrefix) Cria um objeto da classe ProDataObjectMetadata. Cria um objeto para um dataset (Temp-table). |
java.lang.Object | createResultSetHolder() Cria um objeto Holder para o tipo TABLE |
java.lang.Object | createRowidHolder() Cria um objeto Holder para o tipo ROWID |
java.lang.Object | createStringHolder() Cria um objeto Holder para o tipo CHARACTER. |
java.lang.Object | getProxy() Retorna a instância do objeto de conexão ao AppServer, já conectado e disponível para uso. O objeto remoto é a principal classe gerada pelo ProxyGen. |
java.lang.Object | instantiate(java.lang.String className) Instancia um objeto de uma classe dentro da biblioteca do proxy. |
java.lang.Object | toProDataGraph(com.datasul.technology.webdesk.dataset.DefaultDataset d) Transforma um dataset em um ProDataGraph. |