Agradecimentos
Este artigo, igual aos programas que escrevi em 4D, se baseiam em uma tecnologia chamada GenRoTools.
Sem a ajuda de Bags, a implementação de soluções via Serviços Web podem ser realmente complicadas, quero agradecer a Jeff Edwards e a Giovanni Porcari por ter produzido esta ferramenta de grande qualidade. A licença é gratuita, tanto no nível de desenvolvimento como de instalação. A página de descarga será pública proximamente, mais, no entanto, Jeff e Giovanni me permitiram distribuir GenRoTools junto com os exemplos incluídos neste artigo.
Pré-requisitos
Para poder tirar partido ao artigo, o leitor deveria ler meu artigo anterior.
Primeiro exemplo: Serviço Web que facilita uma lista de estados nos E.E.U.U. em que se encontram os doutores
A intenção deste exemplo é a de criar um Serviço Web que retorne um Bag com os estados americanos codificados da seguinte maneira:
O método do servidor que gera esta lista é “WS_ListaDeEstados”:
`Declaração do tipo de variável que retornaremos ao cliente
SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")
`Variável temporal utilizada para recolher as listas de estados
ARRAY STRING(2;EstadosDeEEUU;0)
DEFAULT TABLE([Doutores])
ALL RECORDS
DISTINCT VALUES([Doutores]Estado;EstadosDeEEUU)
C_LONGINT($i;$Records)
$Records:=Size of array(EstadosDeEEUU)
For ($i;1;$Records)
GNT_Bags ("SetItem";->SOAPBLOB;"Estado."+EstadosDeEEUU{$i};->EstadosDeEEUU{$i})
End for
A linha de código mais importante é:
GNT_Bags ("SetItem";->SOAPBLOB;"Estado."+EstadosDeEEUU{$i};
->EstadosDeEEUU{$i})
Acrescentamos um elemento ao Bag mediante o primeiro parâmetro, “SetItem”.
Nota: GenRoTools substituirá a etiqueta e o seu valor correspondente pela especificada mediante “SetItem”. Dito de outra maneira se deseja acrescentar ao Bag todos os valores selecionados, terá que se assegurar de que a etiqueta é única.
O método do cliente que obtém o Bag com os Estados é “Provar_ListaDeEstados”:
C_BLOB(vDebugBag)
vDebugBag:=WS_ListaDeEstados
C_STRING(75;vEtiquetaDeLista)
vEtiquetaDeLista:="Lista de Estados em E.E.U.U. disponíveis:"
If (BLOB size(vDebugBag)#0)
C_LONGINT($width;$height)
GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)
OpenCenteredWindow ($width;$height;Plain window ;"Estados")
DIALOG([Table 1];"BagDebugList")
CLOSE WINDOW
Else
ALERT("Não pôde obter-se os Estados do servidor.")
End if
- O método WS_ListaDeEstados criou-se automaticamente mediante o “Assistente Serviços Web” do 4D.
- O formulário “BagDebugList” contém os seguintes elementos:

No método de objeto “hBagDebugList” vamos pôr o seguinte método:
Case of
: (Form event=On Load )
C_LONGINT(hBagDebugList)
GNT_Bags ("BuildList";->vDebugBag;"hBagDebugList")
If (Is a list(hBagDebugList))
REDRAW LIST(hBagDebugList)
End if
: (Form event=On Clicked )
C_LONGINT($vlItemPos;$vlItemRef;$hSublist)
C_STRING(255;$vsItemText)
C_BOOLEAN($vbExpanded)
$vlItemPos:=Selected list item(hBagDebugList)
If ($vlItemPos>0)
C_STRING(2;vEstado)
GET LIST ITEM(hBagDebugList;$vlItemPos;$vlItemRef;$vsItemText;$hSublist;
$vbExpanded)
If (Is a list($hSublist))
DISABLE BUTTON(vOK)
Else
vEstado:=$vsItemText
ENABLE BUTTON(vOK)
End if
End if
: (Form event=On Unload )
If (Is a list(hBagDebugList))
CLEAR LIST(hBagDebugList;*)
End if
End case
GenRoTools oferece-nos um método muito útil que converte um Bag em uma lista hierárquica:
GNT_Bags ("BuildList";->vDebugBag;"hBagDebugList")
Se tudo sai corretamente, veremos o seguinte resultado:

Segundo exemplo: Serviço Web que facilita uma lista de doutores localizado em uma zona
Baseando-nos no primeiro exemplo, solicitaremos aos doutores pertencentes a um Estado. A vantagem de utilizar a lista gerada pelo servidor no primeiro exemplo é que o usuário não há de adivinhar que Estados estão disponíveis. Isso facilita o trabalho ao usuário e melhora a sua forma de trabalhar. O método é o seguinte:
C_BLOB(vDebugBag)
vDebugBag:=WS_ListaDeEstados
C_STRING(75;vEtiquetaDeLista)
vEtiquetaDeLista:="Lista de Estados em E.E.U.U. disponíveis:"
If (BLOB size(vDebugBag)#0)
C_LONGINT($width;$height)
GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)
OpenCenteredWindow ($width;$height;Plain window ;"Estados")
DIALOG([Table 1];"BagDebugList")
CLOSE WINDOW
If (OK=1)
vDebugBag:=WS_ObterDoutores (vEstado)
C_STRING(75;vEtiquetaDeLista)
vEtiquetaDeLista:="Doutores no estado de: "+vEstado
If (BLOB size(vDebugBag)#0)
C_LONGINT($width;$height)
GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)
OpenCenteredWindow ($width;$height;Plain window ;"Doutores")
DIALOG([Table 1];"BagDebugList")
CLOSE WINDOW
Else
ALERT("Não pôde obter-se os doutores do estado “"+vEstado+"”.")
End if
End if
Else
ALERT("Não pôde obter-se os Estados do servidor.")
End if
- Na primeira instância, obtemos a lista de Estados disponíveis. O método do objeto BagDebugList detecta o clique no valor (não a etiqueta), no qual nos permite guardar o valor na variável vEstado.
- Se fechamos a janela mediante um clique em OK, procedemos a obter os doutores, passando o valor de vEstado como parâmetro. Igual que no exemplo 1, utilizamos o Assistente Serviços Web para gerar o método WS_ObterDoutores. Este chama ao método com o mesmo nome no servidor, que contêm o seguinte:
COMPILER_WEB
SOAP DECLARATION(SOAPString5;Is String Var ;SOAP Input ;"SOAPString5")
SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")
DEFAULT TABLE([Doutores])
QUERY([Doutores]Estado=SOAPString5)
C_LONGINT($i;$Records)
$Records:=Records in selection
If ($Records>0)
FIRST RECORD
For ($i;1;$Records)
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".ID";
->[Doutores]ID)
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".Nome";
->[Doutores]Nome)
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".Sobrenome";
->[Doutores]Sobrenome)
GNT_Bags "SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)
+".Especialidade";->[Doutores]Especialidade)
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".Endereço";
->[Doutores]Endereço)
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".Cidade";
->[Doutores]Cidade)
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".Estado";
->[Doutores]Estado)
NEXT RECORD
End for
End if
- O método COMPILER_WEB contém as seguintes declarações:
ARRAY LONGINT(aSOAPLongint;0)
ARRAY TEXT(aSOAPText;0)
C_LONGINT(SOAPLongint)
C_STRING(5;SOAPString5)
C_BLOB(SOAPBLOB)
- Selecionamos os doutores pertencentes ao Estado passado na variável SOAPString5 (o valor da variável vEstado enviada pelo cliente) e acrescentamos os valores de cada cliente ao Bag.
Nota: é importante destacar que a etiqueta é comum por cada grupo de valores, enquanto isso a etiqueta de cada valor é única, o que permite agrupá-las em um mesmo modo. Por exemplo:
Doutores. 5000063.ID --> 5000063
Doutores. 5000063.Nome --> Kevin
Doutores. 5000063.Sobrenome --> Nguyen
Doutores. 5000063.Especialidade --> Podiatry
Doutores. 5000063.Endereço --> 28785 Via Passa Tempo
Doutores. 5000063.Cidade --> Laguna Niguel
Doutores. 5000063.Estado --> CA
Resulta na seguinte estrutura hierárquica:
- Em cada iteração, o código do doutor muda, no qual gera outro grupo de valores, e assim sucessivamente.
Terceiro exemplo: Serviço Web que facilita uma lista de doutores localizado em uma zona (versão otimizada)
O método “WS_ObterDoutores” do servidor realiza a sua função, mais gera muito tráfico já que utiliza NEXT RECORD por cada iteração. Se há poucos clientes não a um problema, porém, se a seleção é muito maior, a perdida de velocidade pode ser importante. A versão melhorada contém as seguintes mudanças:
COMPILER_WEB
SOAP DECLARATION(SOAPString5;Is String Var ;SOAP Input ;"SOAPString5")
SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")
DEFAULT TABLE([Doutores])
QUERY([Doutores]Estado=SOAPString5)
ARRAY LONGINT($aDoutorID;0)
ARRAY STRING(75;$aDoutorNome;0)
ARRAY STRING(75;$aDoutorSobrenome;0)
ARRAY STRING(75;$aDoutorEspecialidade;0)
ARRAY STRING(75;$aDoutorEndereço;0)
ARRAY STRING(75;$aDoutorCidade;0)
ARRAY STRING(5;$aDoutorEstado;0)
SELECTION TO ARRAY([Doutores]ID;$aDoutorID;[Doutores]Nome;$aDoutorNome;
[Doutores]Sobrenome;$aDoutorSobrenome;[Doutores]Especialidade;$aDoutorEspecialidade;
[Doutores]EndereçoDireccion;$aDoutorEndereço;[Doutores]Cidade;$aDoutorCidade;
[Doutores]Estado;$aDoutorEstado)
C_LONGINT($i;$Records)
$Records:=Records in selection
For ($i;1;$Records)
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".ID";
->$aDoutorID{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Nome";
->$aDoutorNome{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Sobrenome";
->$aDoutorSobrenome{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Especialidade;
->$aDoutorEspecialidade{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Endereço";
->$aDoutorEndereço{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Cidade";
->$aDoutorCidade{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Estado";
->$aDoutorEstado{$i})
End for
- A cópia de valores a arrays está otimizado em 4D Server, o qual permite gerar um Bag muito mais rápido, que mediante iterações via NEXT RECORD.
Quarto exemplo: Serviço Web que codifica informação relacionada com um doutor
Neste exemplo recolhemos em um Bag toda a informação relacionada com um doutor: dados pessoais e visitas realizadas. O método “Provar_InfoDeUmDoutor” no cliente é o seguinte:
C_BLOB(vDebugBag)
vDebugBag:=WS_InfoDeUnDoctor (12226)
C_STRING(75;vEtiquetaDeLista)
vEtiquetaDeLista:="Informação do doutor 12226:"
If (BLOB size(vDebugBag)#0)
C_LONGINT($width;$height)
GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)
OpenCenteredWindow ($width;$height;Plain window ;"Doutor 12226")
DIALOG([Table 1];"BagDebugList")
CLOSE WINDOW
Else
ALERT("Não pôde obter-se informação do doutor 12226.")
End if
- O código do doutor passa-se diretamente como parâmetro para simplificar o exemplo. Idealmente, o usuário deveria poder selecionar um doutor da lista e obter toda a informação. Deixo esse exercício para o leitor.
o método “WS_InfoDeUnDoctor” do servidor contém o seguinte:
COMPILER_WEB
SOAP DECLARATION(SOAPLongint;Is LongInt ;SOAP Input ;"aSOAPLongint")
SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")
DEFAULT TABLE([Doutores])
QUERY([Doutores]ID=SOAPLongint)
ARRAY LONGINT(aDoutorID;0)
ARRAY STRING(75;aDoutorNome;0)
ARRAY STRING(75;aDoutorSobrenome;0)
ARRAY STRING(75;aDoutorEspecialidade;0)
ARRAY STRING(75;aDoutorEndereço;0)
ARRAY STRING(75;aDoutorCidade;0)
ARRAY STRING(5;aDoutorEstado;0)
SELECTION TO ARRAY([Doutores]ID;aDoutorID;[Doutores]Nome;aDoutorNome;
[Doutores]Sobrenome;aDoutorSobrenome;[Doutores]Especialidade;aDoutorEspecialidade;
[Doutores]Endereço;aDoutorEndereço;[Doutores]Cidade;aDoutorCidade;
[Doutores]Estado;aDoutorEstado)
C_LONGINT($i;$Records)
$Records:=Records in selection
For ($i;1;$Records)
GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+ f+".ID";
->aDoutorID{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Nome";
->aDoutorNome{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Sobrenome";
->aDoutorSobrenome{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Especialidade";
->aDoutorEspecialidade{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Endereço";
->aDoutorEndereço{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Cidade";
->aDoutorCidade{$i})
GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Estado";
->aDoutorEstado{$i})
ARRAY DATE(aVisitaData;0)
ARRAY LONGINT(aVisitaHora;0)
ARRAY TEXT(aVisitaPropósito;0)
ARRAY BOOLEAN(aVisitaPago;0)
DEFAULT TABLE([Visitas])
QUERY([Visitas]ID=aDoutorID{$i})
SELECTION TO ARRAY([Visitas]Data;aVisitaData;[Visitas]Hora;aVisitaHora;
[Visitas]Propósito;aVisitaPropósito;[Visitas]Pago;aVisitaPago)
C_LONGINT($j;$Visitas)
$Visitas:=Records in selection
C_BLOB(BagVisitas)
For ($j;1;$Visitas)
C_TIME(vTime)
vTime:=aVisitaHora{$j}
C_STRING(25;vTimeString)
vTimeString:=String(vTime;HH MM SS )
GNT_Bags ("SetItem";->BagVisitas;String($j)+".data";->aVisitaData{$j})
GNT_Bags ("SetItem";->BagVisitas;String($j)+".Hora";->vTimeString)
GNT_Bags ("SetItem";->BagVisitas;String($j)+".Propósito";->aVisitaPropósito{$j})
GNT_Bags ("SetItem";->BagVisitas;String($j)+".Pago";->aVisitaPago{$j})
End for
GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Visitas";
->BagVisitas)
End for
- Este método está baseado no terceiro exemplo.
- Cabe destacar a inclusão das visitas em um Bag secundário (BagVisitas) que logo procedemos a acrescentar no Bag que contém o resto da informação pertencente ao doutor.
Se tudo sai corretamente, veremos o resultado seguinte:

Obtendo valores de um Bag
A utilidade de GenRoTools que permite-nos converter um Bag em uma lista hierárquica é de ajuda fenomenal, já que nos permite estudar o Bag que recebemos do servidor. Na hora de depurar, esta opção pode-nos poupar muito tempo.
Agora bem, uma vez temos os dados que queremos codificados em um Bag, como podemos extraí-los? Mediante o seletor “GetItem”:
GNT_Bags ("GetItem";Bag;Etiqueta;VariávelDeDestino)
Bag -> Ponteiro ao Bag
Etiqueta -> Referência da que queremos obter o valor
VariávelDeDestino <- Valor associado a etiqueta
Centrem no Bag retornado pelo servidor no quarto exemplo.
Para extrair o valor de “Nome”:
C_TEXT(vNome)
GNT_Bags ("GetItem";->vDebugBag;”Doutor.12226.Nome”;->vNome)
Para extrair o Bag “Visitas” que contém as visitas:
C_BLOB(vVisitas)
GNT_Bags ("GetItem";->vDebugBag;”Doutor.12226.Visitas”;->vVisitas)
Para obter o número de visitas:
C_TEXT(vNumeroItems)
GNT_Bags ("CountItems";->vDebugBag;”Doutor.12226. Visitas”;->vNumeroItems)
Para obter o número de valores pertencentes ao doutor:
C_TEXT(vNumeroItems)
GNT_Bags ("CountItems";->vDebugBag;”Doutor.12226”;->vNúmeroItems)
GenRoTools automaticamente converte-se o valor guardado no Bag ao tipo da variável de destino. Por exemplo, o valor da etiqueta “ID” é Longint, mais podemos convertê-lo a String facilmente mediante:
C_TEXT(vStringID)
GNT_Bags ("GetItem";->vDebugBag;”Doutor.12226.ID”;->vStringID)
Conclusão
A utilização de Bags simplifica enormemente a codificação de dados para ser transpassados mediante Serviços Web. Além do mais, o código é de leitura fácil e a transmissão é ótima já que se enviam tudo de uma vez. A extração dos valores é simples e limpa, já que os dados estão organizados hierarquicamente. GenRoTools provém de uma ferramenta muito útil na hora de depurar: a conversão de um Bag em uma lista hierárquica permite analisar o conteúdo do Bag sem ter que escrever rotinas de extração.
Eu gostaria de comentar um pequeno detalhe: a conversão de um Bag com muitos registros a uma lista hierárquica é lenta. Durante o desenvolvimento isto não é talvez muito importante, já que no fim e a cabo trata-se de um elemento de depuração. No entanto, se deseja programar esta opção na versão final que o usuário manipulará, recomenda-se compilar o programa.
Um último comentário. A documentação distribuída atualmente não é muito boa, mais, com um pouco de paciência pode-se sair adiante. No meu caso, decidi escrever uns métodos próprios de apoio, para não ter que memorizar a ordem de parâmetros. Por exemplo, o método “GetBagWithLabelFromBag”:
C_POINTER($1;$DestinationBagPtr;$3;$SourceBagPtr)
C_TEXT($2;$LabelToExtract)
$DestinationBagPtr:=$1
$LabelToExtract:=$2
$SourceBagPtr:=$3
`Obtain the desired nested bag to the destination BLOB
GNT_Bags ("GetItem";$SourceBagPtr;$LabelToExtract;$DestinationBagPtr)
Este método permite obter um Bag dentro de outro Bag. O utilizo da seguinte maneira:
C_BLOB(vVisitas)
GetBagWithLabelFromBag(->vVisitas;”Doutor.12226.Visitas”;->vDebugBag)
Uma última nota sobre a instalação: GenRoTools há de instalar-se mediante 4D Insider, então, trata-se de um componente. Logo, na estrutura onde deseja utilizar há de se colocar a seguinte linha em método do banco de dados On Startup:
GNT_GenRoTools ("OnDatabaseStart")
Mesmo que tenha tentado cobrir às partes mais significativas, é impossível cobrir tudo. Espero que este artigo tenha servido como exemplo e permita ao leitor adentrar-se no mundo genial dos Bags. Vá em frente e ânimo!
Tito Ciuro
Miami – Junho de 2005
Artigo geral
O suave perfume dos serviços Web Anexo 1
Serviços Web em prática