Macroexecução em AdvPL – Parte 01

Introdução

Em um tópico anterior, foi abordado um recurso do AdvPL conhecido por CodeBlock, ou bloco de código (https://siga0984.wordpress.com/2014/12/12/codeblocks-em-advpl-parte-01/). No tópico de hoje, vamos abordar outro recurso muito interessante da linguagem, orientado a execução de expressões dinâmicas, compiladas em memória e executadas. A leitura do post anterior sobre Blocos de Código é interessante e recomendável, pois podemos usar a macroexecução de uma forma bem similar aos blocos de código, porém com características diferenciadas.

Macro-execução ( ou macro-substituição )

Na TDN, existe uma documentação superficial sobre o operador de macroexecução “&” da linguagem AdvPL, disponível em http://tdn.totvs.com/pages/viewpage.action?pageId=6063087. Vamos extender um pouco este conceito e explicar isso com um pouco mais de profundidade.

O operador de macroexecução permite executar uma expressão AdvPL contida dentro de uma variável do tipo “C” Caractere, em qualquer ponto do código. Esta expressão AdvPL pode ser uma constante, uma atribuição, uma chamada de função básica da linguagem, e pode conter mais de uma expressão, separada por vírgula.

Exemplo 01 – Chamada de função

User Function Macro01()
Local cMacro, cResult
cMacro := "time()" 
cResult := &(cMacro)
MsgInfo("Hora atual no servidor = "+cResult)
Return

O programa acima armazena dentro da variável cMacro a string “Time()”, para chamar a função básica do AdvPL Time(), que retorna a hora atual do sistema operacional da máquina onde o TOTVS Application Server está sendo executado, armazenando o resultado da execução da função na variável cResult, e mostrando seu conteúdo em uma caixa de diálogo na interface.

Exemplo 02 – Chamada com parâmetros constantes

Podemos chamar uma função via macro-execução informando parâmetros de três formas diferentes. No primeiro exemplo, vamos chamar a função Str(), informando dois parâmetros constantes:

User Function Macro02()
Local cMacro, cResult
cMacro := "str(10,10,2)" 
cResult := &(cMacro)
MsgInfo("Resultado = "+cResult)
Return

O resultado do programa acima será a exibição em tela a string com o tamanho de 10 bytes, com espaços a esquerda, e o valor 10 com duas casas decimais (” 10.00″) .

Exemplo 03 – Mais de uma expressão

Podemos separar as expressões com vírgula. Neste caso, todas as expressões serão executadas da esquerda para a direita, e o retorno da macroexecução será a última expressão da macro.

User Function Macro03()
Local cMacro, cResult
cMacro := "x := time(),conout(x),x" 
cResult := &(cMacro)
MsgInfo("Hora atual no servidor = "+cResult)
Return

Reparem que a expressão fez primeiro uma atribuição em linha criando a variavel X dentro da macro, depois executou a função conout() informando X como parâmetro, e por fim retornou X.

Exemplo 04 – Passagem de variáveis como parâmetro

Sim, é possível passar variáveis como parâmetro para chamadas em macro-execução, porém existe uma restrição: Apenas variáveis PRIVATE ou STATIC são visíveis dentro da expressão de macro-execução. Veja o exemplo abaixo:

User Function Macro04()
Local cMacro, cResult
PRIVATE cMSg := "Olá Mundo"
cMacro := "MsgInfo(cMSg)" 
&(cMacro)
Return

A variável PRIVATE cMsg contém a string “Olá Mundo”, e a variável de macroexecução contém a chamada da função MagInfo() informando a variável cMsg como parâmetro. Na linha seguinte, realizamos a macro-execução da variável cMacro.

Exemplo 05 – Usando variáveis locais

No exemplo anterior, eu comentei que somente variáveis STATIC ou PRIVATE são visíveis dentro de uma macro-execução. Porém, existe uma sintaxe especial, que pode ser usada apenas quando queremos executar uma única função através da macroexecução, onde todos os parâmetros vão ser informados fora da string a ser macroexecutada. Neste exemplo, vamos adaptar o fonte MACRO02():

User Function Macro05()
Local cMacro, cResult
Local nNumero := 10 , nTamanho := 10 , nDec := 2
cMacro := "str" 
cResult := &cMacro.(nNumero,nTamanho,nDec)
MsgInfo("Resultado = "+cResult)
Return

Colocamos em cMacro apenas o nome da função a ser executada, e na chamada desta função, usamos o operador de macroexecução sem os parênteses para cercar a macroexecução, seguido de um “.()”, onde dentro dos parênteses colocamos constantes ou variáveis, inclusive locais, para serem informadas como parâmetro para a função.

Observação: Caso exista somente um parâmetro a ser informado para a função usando esta sintaxe, o compilador do AdvPL não entende corretamente a intenção de chamada, e considera a função especificada em cMAcro como sendo uma variável, causando erro de execução. Neste caso, é necessário informar um segundo parâmetro na chamada, mesmo que seja NIL, para que o compilador entenda que a intenção é fazer a chamada de uma função com parâmetros via macro. Vejamos o exemplo abaixo:

User Function Macro06()
Local cMacro
cMacro := "MsgInfo" 
&cMacro.(time(),NIL)
Return

Desempenho

Qualquer chamada feita diretamente na linguagem AdvPL em tempo de compilação vai ser mais rápida do que a macroexecução. Deste modo, somente usamos a macroexecução quando dependemos de um conteúdo dinâmico, normalmente armazenado na base de dados ou fornecido pelo usuário durante a execução do programa. A diferença de tempo será mais significativa (pior) na macro-execução quanto maior e mais complexa for a expressão a ser executada. Por isso, quando usamos múltiplas expressões ou muitas operações dentro de uma macroexecução, devemos tomar cuidado de não fazer uma string muito grande.

Outro ponto a ser considerado é a execução em loop de processamento. Como já vimos anteriormente, diferenças de centésimos de segundo podem transformar-se em segundos e minutos dependendo de quantas iterações são realizadas em uma parte do código. Este post termina aqui, mas ele serve de base para a próxima abordagem, onde vamos ver as possibilidades de uso para chamadas dinâmicas usando macroexecução junto com Codeblocks (ou blocos de código no AdvPL)

Até o próximo post, pessoal 😀

Acelerando o AdvPL – Parte 01

Introdução

Como já foi visto e frisado nos tópicos anteriores sobre escalabilidade e desempenho, a premissa de “fazer mais com menos” é uma boa pratica em qualquer linguagem de programação. Existem várias formas de escrever um algoritmo, e todas serão corretas se chegarem ao resultado esperado. Porém, para cada caso existe a maneira “ótima” de escrever o algoritmo, usando a lógica e as funções da linguagem AdvPL da forma adequada para chegarmos ao mesmo resultado usando uma quantidade menor de recursos, o que normalmente pode ser traduzido em economia de tempo.

Introdução aos Estudos de Caso

Cada questão requer um estudo de caso, e normalmente alguns testes. Normalmente uma aplicação AdvPL possui um código que engloba I/O (leitura ou gravação de dados do disco ou do Banco de Dados), e o processamento destes dados pelo programa. Em determinados momentos existe um uso maior de disco, em outros um uso maior de CPU, ou de rede, ou do Banco de Dados.

A abordagem acertiva acerca do desempenho de uma rotina é identificar quais são as operações que correspondem ao consumo de tempo de pelo menos 70 % da rotina (os maiores tempos), e verificar se estas etapas de processo podem ser melhoradas. Devemos sempre ter em mente a lei de Amdahl: “O ganho de desempenho que pode ser obtido melhorando uma determinada parte do sistema é limitado pela fração de tempo que essa parte é utilizada pelo sistema durante a sua operação – Gene Amdahl”. Logo, quanto maior forem as optimizações nas partes do código relevantes no processamento, mais serão percebidos os ganhos da otimização.

Estudo de caso – Processamento de arquivo TXT

Recentemente analizei um caso de processamento, onde um programa AdvPL precisava fazer a leitura de um arquivo TXT para uma integração, onde o arquivo poderia conter linhas maiores que 1 KB. Normalmente utilizamos as funções ft_fuse() e ft_freadln() para ler linhas de um arquivo TXT, porém estas funções são limitadas a ler linhas de tamanho máximo de 1 KB. E, para cada linha lida, também era necessário identificar as colunas de dados, que eram separadas por vírgula (formato CSV).

A parte de leitura de dados foi resolvida de forma simples, usando fOpen() com fREad() ou fReadStr(), lendo um pedaço do arquivo maior para a memória, e verificando neste pedaço lido se havia uma quebra de linha (CRLF / sequência de caracteres chr(13) + Chr(10) ).

Uma vez lida a linha, ela era submetida para uma função que separava os valores das colunas encontradas considerando a virgula como separador, usando uma função mais ou menos assim:

STATIC Function MYStrTok1( cBuffer, cDelimit )
Local cTexto := ''
Local nX := 0
Local aLinha := {}
If Len(cBuffer) > 0
  cTexto := ""
  For nX := 1 to Len(cBuffer)
    If Substr(cBuffer,nX,1) == cDelimit
      aAdd(aLinha, cTexto )
      cTexto := "" 
    Else
      cTexto += Substr(cBuffer,nX,1)
    Endif
  Next nX
  If !Empty(cTexto)
    aAdd(aLinha, cTexto )
  Else
    aAdd(aLinha, " " )
  Endif
Endif
Return( aLinha )

A função é relativamente simples. Criamos uma variável de uso temporária chamada cTexto, inicializada com uma string vazia (“”), e verificamos cada posição da string informada como parâmetro. Cada valor diferente do separador é inserido em cTexto, e no momento que o separador é encontrado, o valor de cTexto é inserido no array de retorno, e cTexto é inicializado novamente para uma string vazia.

Ao fazer um teste com um arquivo texto de 200 MB, contendo 50 mil linhas de texto, onde cada linha possuía 11 palavras de tamanho variável (entre 150 e 250 caracteres) separadas por vírgula. Considerando apenas o tempo de identificação dos dados e separação das colunas, a rotina demorava aproximadamente 55 segundos para processar o arquivo inteiro, com 550 mil palavras. A lógica da rotina não está errada, ela chega ao resultado esperado em 55 segundos.

Se dividirmos a quantidade de palavras processadas pelo tempo de processamento ( 550000 / 55 ), chegamos ao número estrelar de 10 mil palavras por segundo. Isso e bem rápido, certo ?

Analisando com uma lupa

Vamos olhar mais de perto a função lida com as informações: Cada letra da string é extraída usando a função substr(), atuando na linha inteira, caractere por caractere. Cada caractere é comparado com o delimitador, e caso seja igual, o conteúdo de cTexto é acrescentado no array e a variável é limpa, caso contrário o mesmo caractere é extraído novamente, e acrescentado na variável cTexto, que vai crescendo na memoria um caractere por vez.

Refatorando e otimizando

E, se ao invés de extrairmos os caracteres um a um, usarmos uma função que busca dentro da string a próxima ocorrência do delimitador (“,”), e caso seja encontrado, nós acrescentamos no array o primeiro pedaço da string até a posição encontrada, e removemos este pedaço do texto já identificado, inclusive com a vírgula, da variável cBuffer… O fonte ficaria assim:

STATIC Function MYStrTok2( cBuffer, cDelimit )
Local nX := 0
Local aLinha := {}
If Len(cBuffer) > 0
  while ( ( nX := At(cDelimit,cBuffer) ) > 0 )
    aAdd(aLinha, left(cBuffer,nX-1) )
    cBuffer := substr(cBuffer,nX+1)
  Enddo
  If !Empty(cBuffer)
    aAdd(aLinha, cBuffer )
  Endif
Endif
Return( aLinha )

Na instrução while, usamos a variável nX, fazendo uma atribuição em linha do resultado da função AT(), que retorna a primeira posição de cBuffer que contém o delimitador cDelimit, ou 0 caso o delimitador não seja encontrado. Enquanto houver um delimitador encontrado, acrescentamos no array aLinha a parte esquerda da string, da primeira posição até a posição do delimitador – 1 ( desconsiderando o delimitador ), e na linha de baixo reatribuimos o valor de cBuffer, usando a função Substr(), considerando como inicio do novo buffer o próximo caractere após o delimitador até o final da String. No final do loop, caso o buffer não esteja vazio, a linha não terminou com um delimitador, então o que sobrou no buffer é acrescentado no array de retorno.

Testando a velocidade

Repetindo o mesmo teste, com o mesmo arquivo, mas usando a nova função, o tempo cair de 55 segundos, para 4,5 s. (quatro segundos e meio). Com a função original, 10 mil palavras eram identificadas por segundo. Agora, com a nova função, aproximadamente 122 mil palavras são identificadas por segundo 😀

Isso apresentou um ganho de aproximadamente 12x de desempenho.

Em se tratando de uma rotina de transformação de dados, onde a entrada é um TXT, e a saída é um XML, por exemplo, considerando que o tempo de escrita dos dados identificados em um arquivo no disco gaste uns 20 segundos, a rotina original demorava 55 + 20 = 75 segundos, pouco mais de um minuto. Ao otimizar a identificação de palavras, a nova rotina passa a demorar 5 + 20 = 25 segundos, pouco menos que meio minuto.

De 75 segundos, esta etapa de processo foi reduzida para 25 segundos, apresentando um ganho de 3x, ou executando em apenas 1/3 do tempo da rotina original. Isto sim reflete em um ganho de tempo sensível e perceptível na operação.

A relatividade de Amdahl

Considerando apenas a etapa de processo de ler um TXT e gravar um XML, onde ler e identificar as palavras levava 55 segundos, o ganho nesta etapa foi de 12 vezes, ou seja, passou a ser executada em uma fração de 1/12 do tempo original. Quando consideramos o processo inteiro, inclusive a gravação — onde não houve nenhuma otimização — o ganho de desempenho foi de 3 vezes. Se este processamento tivesse mais etapas, que demoram mais tempo, o ganho continua existindo, mas ele não vai ser facilmente percebido, pois se as etapas posteriores representam mais 20 minutos de processamento, e você otimizou apenas um pedaço que consumia 1 minuto, no final das contas o tempo total do processo caiu de 20 para 19 minutos. A otimização realizada agiu sobre uma fatia de tempo muito curta em relação ao processo inteiro (apenas 5% do tempo total do processo foi otimizado).

Agradecimentos

Meus agradecimentos ao nobre leitor e desenvolvedor Eurai Rapelli, que me procurou para tirar uma dúvida, e contribuiu com um caso de uso interessante e sob medida para esta publicação 😀

Conclusão

Quanto mais etapas de processo você conseguir otimizar, mais perceptível será o ganho, ou em tempo, ou em carga do equipamento. E é preciso tomar cuidado com o excesso de otimizações, pois elas tendem a especializar as rotinas, e normalmente elas ficam mais complexas, e isso pode aumentar o custo de manutenção. Por isso, o foco em desempenho e performance deve ser direcionado para os casos onde realmente ele é necessário.

Espero que vocês gostem e tirem bom proveito deste post, comentem, tirem suas dúvidas, e para sugerir um assunto a ser abordado no Blog, é só mandar um e-mail para “siga0984@gmail.com”, com o assunto “Sugestão para o Blog” 😀

Até o próximo post, pessoal !!!

Web Services em AdvPL – Parte 02

Introdução

No tópico anterior sobre Web Services em AdvPL, foi apresentado um overview dos Web Services, e dois exemplos de código AdvPL para atuar como client de Web Services, utilizando um serviço publico na Internet para previsão do tempo.

Hoje, vamos criar um exemplo de um Web Service SERVER em AdvPL, que receberá como parâmetro uma estrutura complexa (ComplexType), e dentro dele um array de estruturas complexas. A idéia inicial era criar um exemplo completo de envio e retorno, mas o tópico ficaria muito grande, então optei por dividí-lo.

Web Service SERVER em AdvPL

Existe um tópico na TDN, que engloba todos os aspectos de criação de um Web Services SERVER em AdvPL, desde a configuração até o deploy de um serviço. Ele está disponível no link http://tdn.totvs.com/pages/viewpage.action?pageId=6064936 . O conteúdo do post atual no blog complementa algumas definições e parte para um exemplo onde é recomendável que você já tenha lido a TDN.

Resumindo em poucas linhas, um Web Service SERVER em AdvPL consiste na declaração de uma classe diferenciada (WSSERVICE), onde configuramos uma interface de publicação para Web Services no TOTVS Application Server, que a partir das classes WSSERVICE compiladas no RPO, consegue disponibilizar uma interface de chamada, consulta e testes via HTTP, bem como a definição (WSDL) da classe para que outras linguagens que trabalham nativamente com Web Services sejam capazes de gerar uma classe Client para consumir este Web Service.

Parâmetros e retornos

Na declaração de um Web Services SERVER em AdvPL, os parâmetros e retornos dos métodos devem ser declarados como propriedades da classe. O tipo destes parâmetros podem ser tipos simples ( http://tdn.totvs.com/pages/viewpage.action?pageId=6064941 ) ou tipos complexos ( http://tdn.totvs.com/display/tec/05.+Estruturas+-+Tipos+complexos ). Quando a quantidade de elementos de um parametro (simples ou compexo) for maior que 1, podemos declarar a propriedade como um ARRAY OF , onde o tipo pode ser simples ou complexo.

Na prática, um tipo complexo não passa de um agrupamento de um ou mais tipos simples ou complexos, como se fosse uma estrutura (classe sem métodos). Em um Web Service AdvPL, é possível declarar metodos para as estruturas, mas os métodos das estruturas não são publicados no WSDL, podem ser usados apenas dentro da classe de Web Service SERVER.

Projeto Web Services TORNEIOS

Vamos partir de um exemplo didático da criação de um WebServices para cadastrar um torneio esportivo. Os dados relevantes do torneio são as datas de inicio e final, uma descrição, e uma lista de atletas participantes. Cada atleta deve informar seu CPF, nome e data de nascimento. Opcionalmente, o atleta pode informar seu peso (em Kg) e altura (em metros).

Logo, precisamos de uma estrutura para os dados de um torneio e outra para os dados do atleta. Como um torneio pode ter vários atletas, a estrutura do atleta deve ser declarada como uma propriedade do tipo ARRAY OF ATLETA na estrutura de torneios. Vamos à primeira parte do código:

( Fonte WSSRV01.PRW )

#include "protheus.ch"
#include "apwebsrv.ch"
/* -------------------------------------------------------------
Fonte WSSRV01.PRW 
Autor Julio Wittwer
Data 10/06/2015
Descrição Fonte de exemplo de Web Services Server com 
 recebimento e retorno de tipos complexos 
 e array de tipos complexos
------------------------------------------------------------- */
// Estutura de dados de um atleta
// CPF, Nome, Aniversário ( obrigatorios ) 
// peso e altura ( opcionais )
WSSTRUCT ATLETA
 
 WSDATA CPF as STRING
 WSDATA Nome as STRING
 WSDATA Nascim as DATE 
 WSDATA PesoKg as FLOAT OPTIONAL
 WSDATA Altura as FLOAT OPTIONAL
ENDWSSTRUCT
// Estrutura de dados de um torneio 
// Descrição, data de inicio e término, e array de atletas participantes
WSSTRUCt TORNEIO
WSDATA Descricao as STRING
 WSDATA Inicio as DATE
 WSDATA Final as DATE 
 WSDATA Atletas as ARRAY OF ATLETA
 
ENDWSSTRUCT

Todas as propriedades declaradas nas estruturas (WSDATA) por default são obrigatórias. Para tornar uma propriedade opcional, usamos a palavra reservada “OPTIONAL” após o tipo da propriedade. Feita a declaração das estruturas, vamos agora para a prototipação da classe.

// Prototipação do WebServices
// Serviço de cadastro de torneios esportivos
WSSERVICE TORNEIOS DESCRIPTION "Torneios Esportivos"
 
 // propriedades de entrada e retorno, usados 
 // como parametros ou retornos nos metodos
 
 WSDATA TorneioIn AS TORNEIO 
 WSDATA Status as INTEGER 
 
 // Metodos do WebService
 WSMETHOD Incluir
ENDWSSERVICE

Por enquanto o WebService vai apenas receber os dados completos de um torneio. O foco do exemplo é justamente como os dados serão recebidos, e como você poderá enviá-los através de um Web Service client em AdvPL criado pela geração de classe client do TDS ou pela nova classe tWSDLManager. O exemplo não abrange a persistência das informações em base de dados. Agora, vamos implementar o método “Incluir” neste fonte:

// Metodo de inclusao. Recebe um Torneio com todos os dados 
// e o array de atletas, e retorna um status numerico ( 0 = sucesso )
WSMETHOD Incluir WSRECEIVE TorneioIn WSSEND Status WSSERVICE TORNEIOS 
 
// Vamos ver tudo o que chegou como parametro
// varinfo("TorneioIn",::TorneioIn) 
 
/*
TorneioIn -> OBJECT ( 4) [...]
 TorneioIn:ATLETAS -> ARRAY ( 2) [...]
 TorneioIn:ATLETAS[1] -> OBJECT ( 5) [...]
 TorneioIn:ATLETAS[1]:ALTURA -> U ( 1) [ ]
 TorneioIn:ATLETAS[1]:CPF -> C ( 12) [123456789-09]
 TorneioIn:ATLETAS[1]:NASCIM -> D ( 8) [02/02/81]
 TorneioIn:ATLETAS[1]:NOME -> C ( 13) [Joao da Silva]
 TorneioIn:ATLETAS[1]:PESOKG -> N ( 15) [ 72.5000]
 TorneioIn:ATLETAS[2] -> OBJECT ( 5) [...]
 TorneioIn:ATLETAS[2]:ALTURA -> U ( 1) [ ]
 TorneioIn:ATLETAS[2]:CPF -> C ( 12) [111111111-11]
 TorneioIn:ATLETAS[2]:NASCIM -> D ( 8) [03/03/80]
 TorneioIn:ATLETAS[2]:NOME -> C ( 13) [Jose da Silva]
 TorneioIn:ATLETAS[2]:PESOKG -> N ( 15) [ 75.2000]
 TorneioIn:DESCRICAO -> C ( 16) [Torneio de Teste]
 TorneioIn:FINAL -> D ( 8) [06/29/15]
 TorneioIn:INICIO -> D ( 8) [06/28/15]
*/
 
// com isso conseguimos montar facilmente o codigo AdvPl para tratar os dados 
// recebidos nesta propriedade. Logo abaixo, eu mostro na tela de console do 
// TOTVS App server a descricao do torneio recebida, e o nome dos atletas recebidos
conout("Torneio : "+TorneioIn:DESCRICAO)
conout("Atletas : "+cValToChar( len(TorneioIn:ATLETAS) ))
For nI := 1 to len(TorneioIn:ATLETAS)
 conout(TorneioIn:ATLETAS[nI]:Nome)
Next
// Caso o processamento seja concluido com sucesso, 
// alimentamos a propriedade de retorno com 0
// em caso de falha, podemos usar um ou mais numeros negativos para indicar
// uma impossibilidade de processamento, como um torneio na mesma data....
::Status := 0
Return .T.

Uma vez criado o fonte e compilado no RPO, e configurado adequadamente o APPServer para ser um servidor de HTTP para WEB Services (vide documentação da TDN), podemos partir para a geração do fonte client em AdvPL.

Exemplo gerando o fonte client de Web Services

Primeiro, vamos gerar um fonte client AdvPL usando o IDE ou TDS. Usando o endereço HTTP onde foi disponibilizado o WSDL deste serviço, o código fonte client gerado deve ficar assim:

( Fonte WSCLI01.PRW )

#INCLUDE "PROTHEUS.CH"
#INCLUDE "APWEBSRV.CH"
/* ===============================================================================
WSDL Location http://localhost/ws/TORNEIOS.apw?WSDL
Gerado em 06/28/15 15:31:26
Observações Código-Fonte gerado por ADVPL WSDL Client 1.120703
 Alterações neste arquivo podem causar funcionamento incorreto
 e serão perdidas caso o código-fonte seja gerado novamente.
=============================================================================== */
User Function _SSLXTLW ; Return // "dummy" function - Internal Use
/* -------------------------------------------------------------------------------
WSDL Service WSTORNEIOS
------------------------------------------------------------------------------- */
WSCLIENT WSTORNEIOS
WSMETHOD NEW
 WSMETHOD INIT
 WSMETHOD RESET
 WSMETHOD CLONE
 WSMETHOD INCLUIR
WSDATA _URL AS String
 WSDATA _HEADOUT AS Array of String
 WSDATA _COOKIES AS Array of String
 WSDATA oWSTORNEIOIN AS TORNEIOS_TORNEIO
 WSDATA nINCLUIRRESULT AS integer
// Estruturas mantidas por compatibilidade - NÃO USAR
 WSDATA oWSTORNEIO AS TORNEIOS_TORNEIO
ENDWSCLIENT
WSMETHOD NEW WSCLIENT WSTORNEIOS
::Init()
If !FindFunction("XMLCHILDEX")
 UserException("O Código-Fonte Client atual requer os executáveis do Protheus Build [7.00.131227A-20150620] ou superior. Atualize o Protheus ou gere o Código-Fonte novamente utilizando o Build atual.")
EndIf
Return Self
WSMETHOD INIT WSCLIENT WSTORNEIOS
 ::oWSTORNEIOIN := TORNEIOS_TORNEIO():New()
// Estruturas mantidas por compatibilidade - NÃO USAR
 ::oWSTORNEIO := ::oWSTORNEIOIN
Return
WSMETHOD RESET WSCLIENT WSTORNEIOS
 ::oWSTORNEIOIN := NIL 
 ::nINCLUIRRESULT := NIL
// Estruturas mantidas por compatibilidade - NÃO USAR
 ::oWSTORNEIO := NIL
 ::Init()
Return
WSMETHOD CLONE WSCLIENT WSTORNEIOS
Local oClone := WSTORNEIOS():New()
 oClone:_URL := ::_URL 
 oClone:oWSTORNEIOIN := IIF(::oWSTORNEIOIN = NIL , NIL ,::oWSTORNEIOIN:Clone() )
 oClone:nINCLUIRRESULT := ::nINCLUIRRESULT
// Estruturas mantidas por compatibilidade - NÃO USAR
 oClone:oWSTORNEIO := oClone:oWSTORNEIOIN
Return oClone
// WSDL Method INCLUIR of Service WSTORNEIOS
WSMETHOD INCLUIR WSSEND oWSTORNEIOIN WSRECEIVE nINCLUIRRESULT WSCLIENT WSTORNEIOS
Local cSoap := "" , oXmlRet
BEGIN WSMETHOD
cSoap += 'xmlns="http://localhost/">'
cSoap += WSSoapValue("TORNEIOIN", ::oWSTORNEIOIN, oWSTORNEIOIN , "TORNEIO", .T. , .F., 0 , NIL, .F.) 
cSoap += "</INCLUIR>"
oXmlRet := SvcSoapCall( Self,cSoap,; 
 "http://localhost/INCLUIR",; 
 "DOCUMENT","http://localhost/",,"1.031217",; 
 "http://localhost/ws/TORNEIOS.apw")
::Init()
::nINCLUIRRESULT := WSAdvValue( oXmlRet,"_INCLUIRRESPONSE:_INCLUIRRESULT:TEXT","integer",NIL,NIL,NIL,NIL,NIL,NIL)
END WSMETHOD
oXmlRet := NIL
Return .T.
// WSDL Data Structure TORNEIO
WSSTRUCT TORNEIOS_TORNEIO
 WSDATA oWSATLETAS AS TORNEIOS_ARRAYOFATLETA
 WSDATA cDESCRICAO AS string
 WSDATA dFINAL AS date
 WSDATA dINICIO AS date
 WSMETHOD NEW
 WSMETHOD INIT
 WSMETHOD CLONE
 WSMETHOD SOAPSEND
 WSMETHOD SOAPRECV
ENDWSSTRUCT
WSMETHOD NEW WSCLIENT TORNEIOS_TORNEIO
 ::Init()
Return Self
WSMETHOD INIT WSCLIENT TORNEIOS_TORNEIO
Return
WSMETHOD CLONE WSCLIENT TORNEIOS_TORNEIO
 Local oClone := TORNEIOS_TORNEIO():NEW()
 oClone:oWSATLETAS := IIF(::oWSATLETAS = NIL , NIL , ::oWSATLETAS:Clone() )
 oClone:cDESCRICAO := ::cDESCRICAO
 oClone:dFINAL := ::dFINAL
 oClone:dINICIO := ::dINICIO
Return oClone
WSMETHOD SOAPSEND WSCLIENT TORNEIOS_TORNEIO
 Local cSoap := ""
 cSoap += WSSoapValue("ATLETAS", ::oWSATLETAS, ::oWSATLETAS , "ARRAYOFATLETA", .T. , .F., 0 , NIL, .F.) 
 cSoap += WSSoapValue("DESCRICAO", ::cDESCRICAO, ::cDESCRICAO , "string", .T. , .F., 0 , NIL, .F.) 
 cSoap += WSSoapValue("FINAL", ::dFINAL, ::dFINAL , "date", .T. , .F., 0 , NIL, .F.) 
 cSoap += WSSoapValue("INICIO", ::dINICIO, ::dINICIO , "date", .T. , .F., 0 , NIL, .F.) 
Return cSoap
WSMETHOD SOAPRECV WSSEND oResponse WSCLIENT TORNEIOS_TORNEIO
 Local oNode1
 ::Init()
 If oResponse = NIL ; Return ; Endif 
 oNode1 := WSAdvValue( oResponse,"_ATLETAS","ARRAYOFATLETA",NIL,"Property oWSATLETAS as s0:ARRAYOFATLETA on SOAP Response not found.",NIL,"O",NIL,NIL) 
 If oNode1 != NIL
 ::oWSATLETAS := TORNEIOS_ARRAYOFATLETA():New()
 ::oWSATLETAS:SoapRecv(oNode1)
 EndIf
 ::cDESCRICAO := WSAdvValue( oResponse,"_DESCRICAO","string",NIL,"Property cDESCRICAO as s:string on SOAP Response not found.",NIL,"S",NIL,NIL) 
 ::dFINAL := WSAdvValue( oResponse,"_FINAL","date",NIL,"Property dFINAL as s:date on SOAP Response not found.",NIL,"D",NIL,NIL) 
 ::dINICIO := WSAdvValue( oResponse,"_INICIO","date",NIL,"Property dINICIO as s:date on SOAP Response not found.",NIL,"D",NIL,NIL) 
Return
// WSDL Data Structure ARRAYOFATLETA
WSSTRUCT TORNEIOS_ARRAYOFATLETA
 WSDATA oWSATLETA AS TORNEIOS_ATLETA OPTIONAL
 WSMETHOD NEW
 WSMETHOD INIT
 WSMETHOD CLONE
 WSMETHOD SOAPSEND
 WSMETHOD SOAPRECV
ENDWSSTRUCT
WSMETHOD NEW WSCLIENT TORNEIOS_ARRAYOFATLETA
 ::Init()
Return Self
WSMETHOD INIT WSCLIENT TORNEIOS_ARRAYOFATLETA
 ::oWSATLETA := {} // Array Of TORNEIOS_ATLETA():New()
Return
WSMETHOD CLONE WSCLIENT TORNEIOS_ARRAYOFATLETA
 Local oClone := TORNEIOS_ARRAYOFATLETA():NEW()
 oClone:oWSATLETA := NIL
 If ::oWSATLETA <> NIL 
 oClone:oWSATLETA := {}
 aEval( ::oWSATLETA , { |x| aadd( oClone:oWSATLETA , x:Clone() ) } )
 Endif 
Return oClone
WSMETHOD SOAPSEND WSCLIENT TORNEIOS_ARRAYOFATLETA
 Local cSoap := ""
 aEval( ::oWSATLETA , {|x| cSoap := cSoap + WSSoapValue("ATLETA", x , x , "ATLETA", .F. , .F., 0 , NIL, .F.) } ) 
Return cSoap
WSMETHOD SOAPRECV WSSEND oResponse WSCLIENT TORNEIOS_ARRAYOFATLETA
 Local nRElem1, oNodes1, nTElem1
 ::Init()
 If oResponse = NIL ; Return ; Endif 
 oNodes1 := WSAdvValue( oResponse,"_ATLETA","ATLETA",{},NIL,.T.,"O",NIL,NIL) 
 nTElem1 := len(oNodes1)
 For nRElem1 := 1 to nTElem1 
 If !WSIsNilNode( oNodes1[nRElem1] )
 aadd(::oWSATLETA , TORNEIOS_ATLETA():New() )
 ::oWSATLETA[len(::oWSATLETA)]:SoapRecv(oNodes1[nRElem1])
 Endif
 Next
Return
// WSDL Data Structure ATLETA
WSSTRUCT TORNEIOS_ATLETA
 WSDATA nALTURA AS float OPTIONAL
 WSDATA cCPF AS string
 WSDATA dNASCIM AS date
 WSDATA cNOME AS string
 WSDATA nPESOKG AS float OPTIONAL
 WSMETHOD NEW
 WSMETHOD INIT
 WSMETHOD CLONE
 WSMETHOD SOAPSEND
 WSMETHOD SOAPRECV
ENDWSSTRUCT
WSMETHOD NEW WSCLIENT TORNEIOS_ATLETA
 ::Init()
Return Self
WSMETHOD INIT WSCLIENT TORNEIOS_ATLETA
Return
WSMETHOD CLONE WSCLIENT TORNEIOS_ATLETA
 Local oClone := TORNEIOS_ATLETA():NEW()
 oClone:nALTURA := ::nALTURA
 oClone:cCPF := ::cCPF
 oClone:dNASCIM := ::dNASCIM
 oClone:cNOME := ::cNOME
 oClone:nPESOKG := ::nPESOKG
Return oClone
WSMETHOD SOAPSEND WSCLIENT TORNEIOS_ATLETA
 Local cSoap := ""
 cSoap += WSSoapValue("ALTURA", ::nALTURA, ::nALTURA , "float", .F. , .F., 0 , NIL, .F.) 
 cSoap += WSSoapValue("CPF", ::cCPF, ::cCPF , "string", .T. , .F., 0 , NIL, .F.) 
 cSoap += WSSoapValue("NASCIM", ::dNASCIM, ::dNASCIM , "date", .T. , .F., 0 , NIL, .F.) 
 cSoap += WSSoapValue("NOME", ::cNOME, ::cNOME , "string", .T. , .F., 0 , NIL, .F.) 
 cSoap += WSSoapValue("PESOKG", ::nPESOKG, ::nPESOKG , "float", .F. , .F., 0 , NIL, .F.) 
Return cSoap
WSMETHOD SOAPRECV WSSEND oResponse WSCLIENT TORNEIOS_ATLETA
 ::Init()
 If oResponse = NIL ; Return ; Endif 
 ::nALTURA := WSAdvValue( oResponse,"_ALTURA","float",NIL,NIL,NIL,"N",NIL,NIL) 
 ::cCPF := WSAdvValue( oResponse,"_CPF","string",NIL,"Property cCPF as s:string on SOAP Response not found.",NIL,"S",NIL,NIL) 
 ::dNASCIM := WSAdvValue( oResponse,"_NASCIM","date",NIL,"Property dNASCIM as s:date on SOAP Response not found.",NIL,"D",NIL,NIL) 
 ::cNOME := WSAdvValue( oResponse,"_NOME","string",NIL,"Property cNOME as s:string on SOAP Response not found.",NIL,"S",NIL,NIL) 
 ::nPESOKG := WSAdvValue( oResponse,"_PESOKG","float",NIL,NIL,NIL,"N",NIL,NIL) 
Return

Agora, podemos montar um novo fonte ( WSTST01.PRW ), que vai usar a classe client gerada no AdvPL para interagir com este serviço. No fonte abaixo, todos os detalhes da implementação e o passo a passo utilizado para entender como usar as estruturas e métodos definidos na classe client do Web Service.

#include "protheus.ch"
/* ===========================================================================
Fonte WSTST01.PRW
Funcoes U_TST01I()
Autor Julio Wittwer
Data 10/06/2015
Descricao Fonte de testes da classe client do WebService "TORNEIOS" em AddPl
=========================================================================== */
User Function Tst01I()
Local oWSClient 
Local oAtleta
SET DATE BRITISH
SET CENTURY ON 
SET EPOCH TO 1960
 
// Cria a instância da classe Client do WebService de Torneios
oWSClient := WSTORNEIOS():New()
/*
Vamos chamar a inclusao de torneios. para isso, primeiro vemos qual foi o metodo 
de inclusao gerado na classe client Advpl
WSMETHOD INCLUIR WSSEND oWSTORNEIOIN WSRECEIVE nINCLUIRRESULT WSCLIENT WSTORNEIOS
*/
// o parametro oWSTorneioIn é uma estrutura nomeada de TORNEIOS_TORNEIO
// Como esta estrutura é um parâmetro de um método, ela já vem 
// criada, mas seus valores nao estao preenchidos 
// vamos preencher as propriedades desta estrutura
// consultamos as propriedades da estrutura no fonte Client Advpl gerado
/*
WSSTRUCT TORNEIOS_TORNEIO
 WSDATA oWSATLETAS AS TORNEIOS_ARRAYOFATLETA
 WSDATA cDESCRICAO AS string
 WSDATA dFINAL AS date
 WSDATA dINICIO AS date
(...)
*/
oWSClient:oWSTORNEIOIN:cDESCRICAO := 'Torneio de Teste'
oWSClient:oWSTORNEIOIN:dInicio := date()+1
oWSClient:oWSTORNEIOIN:dFinal := date()+2
// Dentro da estrutura de torneio, temos tambem a propriedade oWSAtletas
// Como ela nao vem criada, vamos criar uma instância dela
oWSClient:oWSTORNEIOIN:oWSATLETAS := TORNEIOS_ARRAYOFATLETA():New()
// Como a estrutura é um encapsulamento de array de objetos, 
// a propriedade oWSATLETA desta estrutura vem com {} um array vazio 
// ::oWSATLETA := {} // Array Of TORNEIOS_ATLETA():New()
// Para cada atleta a acrescentar, criamos uma nova instancia da estrutura 
// TORNEIOS_ATLETA . Devemos ver a declaração da estrutura para saber 
// quais propriedades devem ser preenchidas
 
/*
WSSTRUCT TORNEIOS_ATLETA
 WSDATA nALTURA AS float OPTIONAL
 WSDATA cCPF AS string
 WSDATA dNASCIM AS date
 WSDATA cNOME AS string
 WSDATA nPESOKG AS float OPTIONAL
(...)
*/
oAtleta := TORNEIOS_ATLETA():New()
oAtleta:cNome := "Joao da Silva"
oAtleta:cCPF := "123456789-09"
oAtleta:dNASCIM := ctod('02/02/1981')
oAtleta:nPESOKG := 72.5
// agora acrescentamos o atleta da propriedade correta
aadd( oWSClient:oWSTORNEIOIN:oWSATLETAS:oWSATLETA , oAtleta ) 
 
// Criamos um novo atleta
oAtleta := TORNEIOS_ATLETA():New()
oAtleta:cNome := "Jose da Silva"
oAtleta:cCPF := "111111111-11"
oAtleta:dNASCIM := ctod('03/03/1980')
oAtleta:nPESOKG := 75.2
// agora acrescentamos o segundo atleta da propriedade correta
aadd( oWSClient:oWSTORNEIOIN:oWSATLETAS:oWSATLETA , oAtleta )
// vamos ver como ficou a estrutura 
// varinfo("oWSClient:oWSTORNEIOIN",oWSClient:oWSTORNEIOIN)
// agora vamos chamar o metodo de inclusao
If oWSClient:Incluir() 
 
 // Chamada realizada com sucesso. 
 // Vamos ver o que chegou na propriedade de retorno
 msgInfo("Codigo de retorno = "+cValToChar(oWSClient:nINCLUIRRESULT),"Chamada realizada.")
Else
// houve algum erro na chamada
 // ou no processamento do webservice 
 MsgStop(getwscerror(3))
Endif
Return

A função de testes de inclusão ( U_TST01I ) pode ser chamada diretamente pelo SmartClient, não precisa colocar ela dentro do Menu do ERP. Os dados utilizados para os atletas estão “chumbados” no fonte. Ao ser executado, o Web Services SERVER deve receber e aceitar o XML enviado por HTTP, e mostrar no LOG de console a descrição do torneio e o nome dos dois atletas informados.

Exemplo usando tWSDLManager

Agora sim, vamos ver como é possível usar a casse nova de Web Services para informar estruturas complexas, e arrays de estruturas complexas.

A classe tWSDLManager permite mapear, a partir do WSDL, as estruturas complexas e tipos simples que precisam ser enviados para uma requisição de WebServices. Como um tipo complexo na verdade é um agrupamento de tipos simples, ao usar o método SimpleInput(), vamos obter um array contendo os identificadores de todos os dados que podem ser enviados, porem a classe considera por default que voce vai enviar apenas uma ocorrencia de um dado ou estrutura. Como nós sabemos que vamos inserir mais de um valor, precisamos dizer para a classe quantos valores serão informados, e somente com estes valores definidos podemos usar o método SimpleInput() para ver como e onde os dados serão informados. Toda a sequencia de identificação está documentada no proprio fonte, em linhas de comentário.

#include "protheus.ch"
/* ===========================================================================
Fonte WSTST02.PRW
Funcoes U_TST02I()
Autor Julio Wittwer
Data 28/06/2015
Descricao Fonte de testes do webService de Torneios, usando a nova classe
 client de websservices "tWSDLManager"
http://tdn.totvs.com/display/tec/Classe+TWsdlManager
=========================================================================== */
User Function TST02I()
Local oWSDL
Local lOk, cResp, aElem, nPos
oWSDL := tWSDLManager():New()
// Seta o modo de trabalho da classe para "verbose"
// apenas para fins de desenvolvimento e depuracao
// oWSDL:lVerbose := .T.
// Primeiro faz o parser do WSDL a partir da URL
lOk := oWsdl:ParseURL( "http://localhost/ws/TORNEIOS.apw?WSDL" )
if !lOk
 MsgStop( oWsdl:cError , "ParseURL() ERROR")
 Return
endif
// Lista as operações disponíveis
// aOps := oWsdl:ListOperations()
// varinfo( "aOps", aOps )
/*
aOps -> ARRAY ( 1) [...]
aOps[1] -> ARRAY ( 2) [...]
aOps[1][1] -> C ( 7) [INCLUIR]
aOps[1][2] -> C ( 0) []
*/
// Seta a operação a ser utilizada
lOk := oWsdl:SetOperation( "INCLUIR" )
if !lOk
 MsgStop( oWsdl:cError , "SetOperation(INCLUIR) ERROR")
 Return
endif
// Agora vamos ver os parametros de chamada da inclusao
// primeiro, devemos verificar os complex types de
// tamanho variavel.
// aParC := oWsdl:ComplexInput()
// varinfo("aParC",aParC)
/*
aParC -> ARRAY ( 1) [...]
aParC[1] -> ARRAY ( 5) [...]
aParC[1][1] -> N ( 15) [ 3.0000] // ID da estrutura complexa variavel
aParC[1][2] -> C ( 6) [ATLETA]
aParC[1][3] -> N ( 15) [ 0.0000] // numero minimo de ocorrencias
aParC[1][4] -> N ( 15) [2147483647.0000] // numero maximo de ocorrencias
aParC[1][5] -> C ( 31) [INCLUIR#1.TORNEIOIN#1.ATLETAS#1] // lista de elementos "pai" desta estrutura
*/
// Como sabemos que vamos enviar 2 atletas,
// definimos esta quantidade usando o metodo SetComplexOccurs
oWSDL:SetComplexOccurs(3,2) // Id do complex type, quantidade a ser enviada
// Apenas depois de definirmos quantos tipos complexos queremos enviar,
// podemos listar os tipos simples.
// aParS := oWsdl:SimpleInput()
// varinfo("aParS",aParS)
/*
aParS -> ARRAY ( 13) [...]
aParS[1] -> ARRAY ( 5) [...]
aParS[1][1] -> N ( 15) [ 0.0000]
aParS[1][2] -> C ( 6) [ALTURA]
aParS[1][3] -> N ( 15) [ 0.0000]
aParS[1][4] -> N ( 15) [ 1.0000]
aParS[1][5] -> C ( 40) [INCLUIR#1.TORNEIOIN#1.ATLETAS#1.ATLETA#1]
aParS[2] -> ARRAY ( 5) [...]
aParS[2][1] -> N ( 15) [ 1.0000]
aParS[2][2] -> C ( 3) [CPF]
aParS[2][3] -> N ( 15) [ 1.0000]
aParS[2][4] -> N ( 15) [ 1.0000]
aParS[2][5] -> C ( 40) [INCLUIR#1.TORNEIOIN#1.ATLETAS#1.ATLETA#1]
aParS[3] -> ARRAY ( 5) [...]
aParS[3][1] -> N ( 15) [ 2.0000]
aParS[3][2] -> C ( 6) [NASCIM]
aParS[3][3] -> N ( 15) [ 1.0000]
aParS[3][4] -> N ( 15) [ 1.0000]
aParS[3][5] -> C ( 40) [INCLUIR#1.TORNEIOIN#1.ATLETAS#1.ATLETA#1]
aParS[4] -> ARRAY ( 5) [...]
aParS[4][1] -> N ( 15) [ 3.0000]
aParS[4][2] -> C ( 4) [NOME]
aParS[4][3] -> N ( 15) [ 1.0000]
aParS[4][4] -> N ( 15) [ 1.0000]
aParS[4][5] -> C ( 40) [INCLUIR#1.TORNEIOIN#1.ATLETAS#1.ATLETA#1]
aParS[5] -> ARRAY ( 5) [...]
aParS[5][1] -> N ( 15) [ 4.0000]
aParS[5][2] -> C ( 6) [PESOKG]
aParS[5][3] -> N ( 15) [ 0.0000]
aParS[5][4] -> N ( 15) [ 1.0000]
aParS[5][5] -> C ( 40) [INCLUIR#1.TORNEIOIN#1.ATLETAS#1.ATLETA#1]
aParS[6] -> ARRAY ( 5) [...]
aParS[6][1] -> N ( 15) [ 5.0000]
aParS[6][2] -> C ( 6) [ALTURA]
aParS[6][3] -> N ( 15) [ 0.0000]
aParS[6][4] -> N ( 15) [ 1.0000]
aParS[6][5] -> C ( 40) [INCLUIR#1.TORNEIOIN#1.ATLETAS#1.ATLETA#2]
aParS[7] -> ARRAY ( 5) [...]
aParS[7][1] -> N ( 15) [ 6.0000]
aParS[7][2] -> C ( 3) [CPF]
aParS[7][3] -> N ( 15) [ 1.0000]
aParS[7][4] -> N ( 15) [ 1.0000]
aParS[7][5] -> C ( 40) [INCLUIR#1.TORNEIOIN#1.ATLETAS#1.ATLETA#2]
aParS[8] -> ARRAY ( 5) [...]
aParS[8][1] -> N ( 15) [ 7.0000]
aParS[8][2] -> C ( 6) [NASCIM]
aParS[8][3] -> N ( 15) [ 1.0000]
aParS[8][4] -> N ( 15) [ 1.0000]
aParS[8][5] -> C ( 40) [INCLUIR#1.TORNEIOIN#1.ATLETAS#1.ATLETA#2]
aParS[9] -> ARRAY ( 5) [...]
aParS[9][1] -> N ( 15) [ 8.0000]
aParS[9][2] -> C ( 4) [NOME]
aParS[9][3] -> N ( 15) [ 1.0000]
aParS[9][4] -> N ( 15) [ 1.0000]
aParS[9][5] -> C ( 40) [INCLUIR#1.TORNEIOIN#1.ATLETAS#1.ATLETA#2]
aParS[10] -> ARRAY ( 5) [...]
aParS[10][1] -> N ( 15) [ 9.0000]
aParS[10][2] -> C ( 6) [PESOKG]
aParS[10][3] -> N ( 15) [ 0.0000]
aParS[10][4] -> N ( 15) [ 1.0000]
aParS[10][5] -> C ( 40) [INCLUIR#1.TORNEIOIN#1.ATLETAS#1.ATLETA#2]
aParS[11] -> ARRAY ( 5) [...]
aParS[11][1] -> N ( 15) [ 10.0000]
aParS[11][2] -> C ( 9) [DESCRICAO]
aParS[11][3] -> N ( 15) [ 1.0000]
aParS[11][4] -> N ( 15) [ 1.0000]
aParS[11][5] -> C ( 21) [INCLUIR#1.TORNEIOIN#1]
aParS[12] -> ARRAY ( 5) [...]
aParS[12][1] -> N ( 15) [ 11.0000]
aParS[12][2] -> C ( 5) [FINAL]
aParS[12][3] -> N ( 15) [ 1.0000]
aParS[12][4] -> N ( 15) [ 1.0000]
aParS[12][5] -> C ( 21) [INCLUIR#1.TORNEIOIN#1]
aParS[13] -> ARRAY ( 5) [...]
aParS[13][1] -> N ( 15) [ 12.0000]
aParS[13][2] -> C ( 6) [INICIO]
aParS[13][3] -> N ( 15) [ 1.0000]
aParS[13][4] -> N ( 15) [ 1.0000]
aParS[13][5] -> C ( 21) [INCLUIR#1.TORNEIOIN#1]
*/
// Analisando o array acima, vemos que os parametros de primeiro nivel
// sao INICIO, FINAL e DESCRICAO. Logo, podemos alimentar estes valores
// diretamente, inclusive pelo nome. Porem precisamos informar tambem o array contendo 
// a sequencia de elementos "pai" de cada valor. Na pratica, pasta pegar a 
// lista de elementos pai identificada no 5o elemento do array de simpleInput
// e criar um array com ele.
lOk := oWsdl:SetValPar("DESCRICAO",{"INCLUIR#1","TORNEIOIN#1"}, "Torneio de Testes")
if !lOk
 MsgStop( oWsdl:cError , procline())
 Return
endif
lOk := oWsdl:SetValPar("INICIO",{"INCLUIR#1","TORNEIOIN#1"}, '20150701')
if !lOk
 MsgStop( oWsdl:cError , procline())
 Return
endif
lOk := oWsdl:SetValPar("FINAL",{"INCLUIR#1","TORNEIOIN#1"}, '20150702')
if !lOk
 MsgStop( oWsdl:cError , procline())
 Return
endif
// Podemos ver tambem que o array de tipos simples foi criado para 2 atletas
// e agora temos IDs para cada um deles. Isto somente aconteceu pois 
// usamos o metodo SetComplexOccurs(). Caso nao tivessemos utilizado 
// este metodo, o array de estruturas simples viria apenas com os 
// identificadores para inserir um altleta.
// Para definir os tipos complexos, uma vez que seus valores
// já estão enumerados na lista de tipos simples, podemos fazer assim :
aParent := {"INCLUIR#1","TORNEIOIN#1","ATLETAS#1","ATLETA#1"}
oWsdl:SetValPar("NOME",aParent,"Romualdo da Silva")
oWsdl:SetValPar("CPF",aParent,"123456789-01")
oWsdl:SetValPar("NASCIM",aParent,"1974-05-08")
oWsdl:SetValPar("PESOKG",aParent,"77,5")
oWsdl:SetValPar("ALTURA",aParent,"1,85")
// Como os atletas são inseridos em sequencia, basta informarmos
// a que alteta a informação se destina, variando o ultimo
// elemento do array dos "pais" de cada informação.
aParent := {"INCLUIR#1","TORNEIOIN#1","ATLETAS#1","ATLETA#2"}
oWsdl:SetValPar("NOME",aParent,"Sinfronio da Silva")
oWsdl:SetValPar("CPF",aParent,"555555555-55")
oWsdl:SetValPar("NASCIM",aParent,"1976-03-12")
oWsdl:SetValPar("PESOKG",aParent,"66,5")
oWsdl:SetValPar("ALTURA",aParent,"1,65")
// Agora executamos a requisição ao WebService
lOk := oWsdl:SendSoapMsg()
if !lOk
 MsgStop( oWsdl:cError , "SendSoapMsg() ERROR")
 Return
endif
// mostra no console o XML Soap devolvido
conout( "--- SOAP RESPONSE ---" , oWsdl:GetSoapResponse() , "-------------------")
// Recupera os elementos de retorno, já parseados
cResp := oWsdl:GetParsedResponse()
/*
Como o retorno é apenas um numero, podemos obtelo diretamente 
parseando a string retornada em cResp
*/
varinfo("cResp",cResp)
Return

Conclusão

A classe tWSDLManager tem uma sequência de operações que precisa ser realizada antes de começar a alimentar as propriedades de envio de dados. Caso existam mais informações que possuem quantidades variáveis, é necessário definir a quantidade de cada uma delas antes de começar a popular os valores para realizar a requisição / chamada do WebService.

Para o próximo post sobre Web Services, eu vou pegar este exemplo didático, e fazer um método de consulta, onde o retorno do Web SErvices server vai ser as informações de um torneio. Esta etapa de tratamento de retorno de tipos complexos é muito simples usando a geração do fonte client em AdvPL, mas é um pouco mais complicada quando usamos a classe tWSDLManager(), e realmente tudo isso de uma vez dentro de apenas uma postagem, ficaria muito grande !

Até a próxima pessoal, e bons Web Services 😀

O profissional de TI

Introdução

Este post é dedicado a todos os profissionais da área de Tecnologia da Informação, inclusive aos futuros candidatos e interessados em ingressar neste mundo. Espero que estes poucos parágrafos sirvam de inspiração para todos que com ele tomarem contato.

Definição da Tecnologia da Informação

A Tecnologia da Informação (TI) pode ser definida como o conjunto de todas as atividades e soluções providas por recursos de computação que visam permitir a produção, armazenamento, transmissão, acesso, segurança e o uso das informações. (*Wikipedia). Por tão ampla definição, um profissional da área precisa ter uma boa base de conhecimentos gerais sobre a aplicação da tecnologia, e optar por especializar-se em uma ou mais áreas relacionadas ao uso da tecnologia.

Competências

O sucesso na carreira é determinado pelas competências e “conjuntos de habilidades”, ou seja, toda a gama de ativos intelectuais e comportamentais, particularmente aqueles que permitem os relacionamentos dentro da organização e são canalizados para os clientes. (*Engineer Top Management)

Traduzindo pro bom português: Amigo, tudo o que você sabe e é capaz de fazer têm peso. Não apenas o que você sabe em Hardware ou Software, conhecimento acadêmico ou prático, mas também todas as demais habilidades. Relacionamento, apresentação pessoal, gerenciamento de conflitos e expectativas, resolução de problemas, boa oratória, correta aplicação do idioma (falado e escrito), etc…

Cultivando suas competências

Muitas pessoas têm um talento “nato” para alguma coisa. E normalmente pessoas de sucesso usam deste talento para conquistar seu lugar ao sol. O que nem todos se dão conta, é que não basta ser extremamente bom em uma coisa. Você também pode desenvolver outras capacidades, além daquelas que você têm mais facilidade ou predisposição em aprender. Basta você se interessar por isso, e dedicar-se.

Ninguém nasceu sabendo falar, amarrar os sapatos, escovar os dentes, fazer operações matemáticas, dirigir um automóvel, entre muitas outras coisas. Aquilo que não se sabe, se aprende. Pode levar mais ou menos tempo, pode ser mais fácil para uns e mais difícil para outros. Tem medo de falar em público ? Existe curso de oratória. Têm dificuldade em se organizar, têm curso pra isso também. Para quem têm facilidade em ser auto-didata, a Internet é um prato cheio de recursos e fontes de pesquisa, livros, manuais e cursos inteiros gratuitos. Basta priorizar seu tempo para fazer os cursos do seu interesse ou necessidade. Caso você não seja tão autodidata assim, existem cursos presenciais ou mesmo instrutores particulares. E, mesmo sendo um pouco mais difícil fazer um curso a distância, a capacidade de mídia na Internet permite cursos tão interativos, com tutores com atendimento via chat e vídeo, a sala de aula é virtual, mas com tanta interação que parece uma sala real.

O esforço e seu mérito

Ao cursar os últimos anos do ensino médio, uma colega de sala destacava-se por suas notas altas em todas as matérias e atividades, uma jovem muito inteligente. Uma aula da grade de ensino, Desenho Geométrico e Trigonometria, era ministrada por uma freira, que apesar de ter docência nesta matéria, não se preocupava em explicar alguns fundamentos dos assuntos abordados, deixando muitas dúvidas e lacunas entre uma aula e outra.

Como eu tinha muita facilidade em absorver o conhecimento da matéria, e conseguia entender com relativa facilidade o material didático, após o resultado da primeira prova do bimestre, alguns amigos da minha sala me perguntaram se eu não poderia explicar a matéria novamente e tirar essas dúvidas. Eu pedi autorização à diretora para usar a sala de aula no primeiro horário da tarde uma vez por semana, inicialmente para 3 ou 4 colegas.

Na terceira ou quarta semana, dos 40 alunos da sala, mais de 30 estavam vindo às aulas de reforço. Para meu espanto, a aluna mais inteligente da sala estava presente. Eu não entendi … por que a aluna mais inteligente da sala precisa de aula de reforço? Após a aula, eu fui conversar com ela, e lhe perguntei exatamente isso. O que ela me respondeu eu nunca mais esqueci:

“— Eu não sou a aluna mais inteligente da sala. Eu sou esforçada. Mesmo assistindo a todas as aulas na primeira carteira, e prestando atenção na matéria, boa parte do que foi explicado eu não entendo logo de primeira. Todos os dias eu chego em casa, e preciso pegar os livros, e ler tudo de novo. As vezes eu preciso ler duas ou três vezes, fazer e refazer os exercícios, e mesmo assim na próxima aula eu vou na mesa do professor tirar uma dúvida ou pedir uma explicação mais detalhada sobre alguns pontos que eu não entendi da matéria. Todos me consideram inteligente por que eu tiro boas notas nas provas, mas para conseguir isso, eu preciso dedicar várias horas do dia para estudar e rever as matérias. Se eu fosse mesmo tão inteligente, eu não precisaria fazer todo esse esforço pra entender as coisas. O que faz a diferença, é que eu sei das minhas limitações, mas tenho vontade de aprender. Então eu me dedico, o quanto for necessário, para aprender e entender o que está sendo ensinado.” ( Rebeca de Campos Leite )

Aprendendo a aprender

A cada dia novos conhecimentos são gerados, e outros são reciclados, em qualquer área de atuação. Acredito que a área de TI seja uma das mais voláteis e em constante mutação, então elas exigem que o profissional da área esteja sempre tomando conhecimento e contato com as novas tecnologias e abordagens para as soluções existentes e em desenvolvimento para lidar com os problemas contemporâneos e constantes necessidades que se apresentam a cada novo instante. É importante gostar de estudar e aprender a absorver novos conhecimentos, valendo-se da experiência adquirida com os conhecimentos já adquiridos.

Não se prenda a uma tecnologia ou a um conceito. Não existe uma solução mágica universal que sirva para todos os casos, normalmente quebramos um problema muito grande em problemas menores, e várias alternativas podem ser apresentadas para cada um. Todas trazem implicitamente um custo/benefício, e têm seus prós e contras. Entender isso e saber escolher é um diferencial muito importante, e isto se aplica não somente a um sistema informatizado, mas também se aplica a decisões de natureza pessoal.

Conclusão

Não basta saber e aprender, é preciso utilizar e entender a melhor forma de utilizar seu conhecimento e sua capacidade, bem como manter-se atualizado, e expandir suas capacitações. Se você realmente quer avançar na sua vida pessoal e profissional, estes são os melhores conselhos que eu posso te dar 😉

Até a próxima, pessoal 😀

Referências

TECNOLOGIA DA INFORMAÇÃO. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2015. Disponível em: <https://pt.wikipedia.org/w/index.php?title=Tecnologia_da_informa%C3%A7%C3%A3o&oldid=42589522> Acesso em: 24 jun. 2015.

Dicionário – Enginheer Top Management. Artigo sobre Capacidade Intelectual. Disponível em <http://www.eigenheer.com.br/candidatos/dic2.html> Acesso em: 24 jun. 2015.