sexta-feira, 26 de outubro de 2007

Executando código dinamicamente no Caché

...Ou Como avaliar e executar uma expressão entrada pelo usuário.

O cenário: na aplicação, o usuário pode cadastrar algumas fórmulas, fórmulas que podem conter algumas variáveis previamente cadastradas. Essas variáveis podem conter valores simples, ou ainda, podem ser dependentes de um parâmetro. Um exemplo de fórmula encontrada é a seguinte:

(0.50 * ([VAR1(I)] / [VAR1(0)])) + (0.50 * [VAR2])

onde:

  • VAR1(I) é uma variável, cujo valor depende do parâmetro "I".

  • VAR1(0) é uma variável, cujo valor depende do parâmetro "0".

  • VAR2 é a variável simples.


Notem que por convenção, na fórmula, as variáveis estão entre colchetes []. Muitas variáveis são na verdade, séries históricas com valores. Os parâmetros para elas refletem apenas qual a época/data que o valor deve ser considerado.

O problema: dado um intervalo de tempo, preciso saber do valor da fórmula. Ou seja, preciso executar a fórmula, como se fosse parte do programa, ou então fazer o parsing, etc. Em algumas outras linguagens de script, existe a função eval(), que converte um texto (ou seja, uma string) em código, executando-o. Em Caché Object Script, temos o comando XECUTE, que executa o conteúdo de uma String como se fosse código, ou seja, efetua macro substituição.

Mesmo com essa facilidade, ainda assim precisava identificar dentro da %String cada variável. Para isso, escrevi uma pequena função, que coloquei num arquivo .MAC, para ser usada.

(A função, o código-fonte está aqui.)

Para essa função, passamos a fórmula, bem como caracteres que delimitarão o que será considerado variável, tb passamos uma String que será colocada no lugar da variável na fórmula original, bem como um caracter delimitador (coisa muito usada em sistemas MUMPS-like).

O resultado da função é a fórmula, com as variáveis substituídas pela String, além de todas as variáveis encontradas, separadas na String pelo caracter delimitador. Usando como String de substituição "?", e como caracter delimitador "~", a função para fórmula do exemplo acima retorna:

(0.50 * (? / ?)) + (0.50 * ?)~VAR1(I)~VAR1(0)~VAR2

Em seguida, resolvemos o valor de cada variável, e substituimos na fórmula, e retiramos da String as variáveis. Não vou ilustrar esses passos aqui, pois são bem específicos para o modelo de dados. Neste momento, nossa String da fórmula tem algo como:

(0.50 * (12.3 / 10.88)) + (0.50 * 10)

Note que a sentença é matematicamente bem formada, e inclusive a precedência de operadores é bem explícita com o uso de parênteses, o que deve ser sempre feito, visto que o Caché não segue o padrão matemático quanto a precedência de operadores.

Bem, finalmente vamos executar a expressão. Primeiro, atribuimos o comando SET dentro de uma String, como por exemplo:

SET umaVariavel = "SET resultado = " _ stringComFormula

Depois fazemos o XECUTE:

XECUTE umaVariavel

E pronto! O valor da expressão está na variável "resultado"! Apenas tenham precaução ao usar o comando XECUTE, pois dependendo da situação (e do abuso), ele pode deixar o seu código muito ruim para manutenção.

quinta-feira, 11 de outubro de 2007

Fazendo upload de arquivos com Caché & CSP

Fazendo upload de arquivos com Caché & CSP - ou... Meu primeiro CSP

Como já mencionei por aqui (ou não), trabalho primariamente com o Ensemble da Intersystems (e consequentemente com o Caché, já que o Ensemble é construído sobre este). Portanto, praticamente não mexo com coisas que envolvam interface de usuário, mas essa semana precisei fazer uma página que desse um upload num arquivo para o servidor. Então, toca a aprender CSP, as famigeradas Caché Server Pages. Na verdade, aprender eu não aprendi não, só vi o necessário para os meus propósitos.

Como diz na página de introdução ao CSP, você pode ir por dois caminhos: escrever páginas html e imbutir tags dentro, da mesma maneira que JSP (Java Server Pages) ou PHP, gerando um arquivo .csp; ou escrever uma classe, e nesta classe você jogar outputs montado o html, a mesma abordagem dos Servlets.

Na parte de exemplos (namespace SAMPLES do Caché) já tinha um arquivo .csp com um exemplo de upload (adivinhe qual arquivo: csp/samples/upload.csp). Com base nesse arquivo, preparei o meu.



(Exemplo da página de upload em SAMPLES, e o resultado de um upload)

Bem, como não consegui colocar o código fonte de maneira legal no blogger, vou colocar uma figura, e deixar um link para o codigo fonte.


(Figura com o codigo fonte)

A idéia principal desta página pode ser resumida assim: ao chamar a página, verifica se o parâmetro 'campo' foi enviado, se não foi, nem precisa mostrar nada. Verifica se foi enviado para o csp um arquivo, se não foi, coloca um form html para dar o upload (sendo que o submit deste form vai para o próprio csp), e se já foi efetuado um upload, atribui o nome do arquivo a um campo do html que chamou este csp (este csp deve ter sido aberto de outra página), e exibe um link para fechar a janela.

A seguir, vamos detalhar alguns aspectos.

Como diria o retalhador, vamos por partes:

<CSP:CLASS INCLUDES="SOMEINCFILETOINCLUDE">

Aqui indicamos um arquivo .INC (omitindo a extensão) que vamos referenciar no código, ou seja, estamos importando o INC. Arquivos .INC no Caché geralmente são usados para definição de macros (estilo macros C/C++), tanto para código quanto para definição de constantes.

/***********************************

<csp:if condition='($data(%request.Data("campo",1)))'>

Aqui temos uma tag específica do csp, que determina uma condição, o famoso 'if'. A condição como é vista como o valor do atributo 'condition', da tag csp:if, e pode conter código COS (Caché Object Script), retornando verdadeiro ou falso. Neste caso, apenas estamos checando se a requisição http (objeto %request) contém um parâmetro de nome 'campo'. Mais informações sobre o objeto %request pode ser visto na página da classe %CSP.Request.

/***********************************

<csp:if condition='($data(%request.MimeData("FileStream",1)))'>

Outra vez um if, mas desta vez, se repararmos, não verificamos o parâmetro da requisição http com %request.Data, mas com %request.MimeData. Isso porque desta vez estamos checando se foi enviado um arquivo, usando o padrão MIME.

/***********************************


Arquivo enviado: <b>#(%request.MimeData("FileStream",1).FileName)#</b><br>
Tamanho: <b>#(%request.MimeData("FileStream",1).Size)#</b><br>
Tipo: <b>#(%request.MimeData("FileStream",1).ContentType)#</b><br>


Quando enviamos um arquivo, o %request.MimeData("NOMEPARAMETRO",1) retorna um objeto %CSP.BinaryStream. No código acima, mostramos na tela, algumas informações que podem ser obtidas desta stream. Notem que os valores que serão impressos na tela, estão entre '#(' e ')#'.

/***********************************

<script language="Cache" runat="server">

Aqui iniciamos um bloco de Caché Object Script que será executado no servidor, ou seja, neste bloco, se quisermos que algo apareça na tela, teremos que escrever na saída padrão, usando o comando Write.

/***********************************

set dname = $$$SomeDirectory

Ainda dentro do bloco aberto acima, aqui criamos e atribuimos um valor a variável 'dname'. Este valor é uma macro, indicada pelos três cifrados ($$$). Esta macro deverá estar definida em algum dos arquivos .INC, que como vimos anteriormente, podem ser importados para este CSP.

/***********************************


Set stream=##class(%FileBinaryStream).%New()
Set stream.Filename= dname _ fname

do stream.CopyFrom(%request.MimeData("FileStream",1))
do stream.Flush()
do stream.SaveStream()


Aqui criamos um stream para um arquivo (classe %FileBinaryStream), na variável 'stream', setamos o nome e caminho completo deste, e depois usamos o método CopyFrom, para copiar o conteúdo da stream que foi enviada via http, para o arquivo.

/***********************************

<csp:else>

Aqui fechamos a parte 'então' do if, e colocamos a parte 'senão'.

/***********************************

<form enctype="multipart/form-data" method="post" action="este_arquivo_csp.csp">
Arquivo: <input type=file size=30 name=FileStream>
<p>

<script language="Cache" runat="server">
New bytes
Set bytes="<input type=""hidden"" name=""campo"" value=""" _ %request.Data("campo",1) _ """ > "
Write bytes,!
</script>

<p> <ul><input type="submit" value="Upload file"></ul> <p>
</form>


A parte do 'else' contém a definição de um formulário html, destinado a receber o arquivo que será carregado no servidor. Notem que dentro da definição do form, misturamos uma parte com código Caché, destinado a colocar um campo escondido no form, a fim de preservar o valor que fora anteriormente passado, na requisição. Essa é uma solução, outra solução seria usar sessão, mas como este caso é bem simples, não fui verificar como o gerenciamento de sessão é realizado com os CSPs.

/***********************************

Bem, esse post já ficou maior do que o esperado, então terminamos por aqui. No arquivo csp existem outras funcionalidades, mas não vou comentá-las agora, tais como a parte de javascript, e as funções de diretórios do COS. E tb tirei toda a formatação, sendo um html bem simples. Além disso, como suprimi várias partes, pode ser que o csp não compile de primeira, já que eu não testei ele nessa versão 'mais light'.

Como já dizia o Pernalonga, por hoje é só, pessoal.

sexta-feira, 5 de outubro de 2007

Curiosidades do Caché

Tipagem fraca (bem, seria melhor dizer inexistente) do Caché da Intersystems as vezes produz alguns resultados curiosos, especialmente se o leitor estiver mais acostumado com matemática do que com "as coisas" que existem em TI.

Por exemplo, num terminal Caché, declaramos duas variáveis*:

USER>set x = "5.10"

USER>set y = "5.1"

USER>write x
5.10
USER>write y
5.1

Lembrando que no Caché (assim como no bom e velho C) o 1 (um) representa verdadeiro, e 0 (zero) representa falso, e o separador padrão de casas decimais é o ponto ".", vemos o resultado de algumas comparações*:

USER>write x = y
0
USER>write x > y
0
USER>write x >= y
1

Ou seja, (x = y) é falso, (x > y) tb é falso, mas (x >= y) é verdadeiro. Em bom português, "x" não é igual a "y", "x" também não é maior que "y", mas "x" é maior ou igual a "y", huhuhuhu. 8-)



Bem, vamos as explicações:

  • A primeira sentença (x = y) é falsa, pois neste caso, o Caché testa a igualdade das Strings, e a String "5.10" é diferente da String "5.1".

  • A segunda sentença (x > y) é falsa, mas não porque o Caché compara tamanhos de Strings (x < y também retorna falso), mas porque ao se usar o operador ">", o Caché dinamicamente converte os valores para numéricos, e como "x" NÃO É MAIOR que "y" (numericamente, "x" É IGUAL a "y", já que 5,1 = 5,10), temos o resultado falso.

  • Agora já dá pra entender porque (x >= y) retorna verdadeiro, não? Ao usar operadores "<", "<=", ">", ">=", o Caché tenta converter as variáveis para tipos numéricos, e como numericamente (lembrando que no Caché o separador decimal é o ponto ".") "5.1" É IGUAL a "5.10", a expressão (x >= y) é verdadeira.

Hehehe, não mostrem isso para não iniciados, isso deve confundir pra caramba, assim como a precedência de operadores no Caché, que eu já comentei.

* A saber: o comando "set" é usado para inicializar/atribuir um valor a uma variável, e o comando "write" escreve na saída padrão (no caso, o próprio terminal) um valor.

sexta-feira, 28 de setembro de 2007

Ensemble melhor software de integração do mercado? Não exatamente...

Outro dia estava pesquisando algo no Big G (não lembro exatamente o quê), e me deparo com essa notícia: Pesquisa reconhece Ensemble como o melhor software de integração do mercado. Fiquei atônito! Não que o Ensemble seja de todo ruim, mas o fato é que ele está muuuuuito longe do que eu gostaria (OK, ele tem partes realmente horríveis).

Segui o link, e lendo a página, descubro que como muitas chamadas de imprensa, essa aí tb é no mínimo incompleta. Vamos aos fatos: Realmente, uma pesquisa de mercado apontou isso. Entretanto, faltou mencionar que esse 'mercado' é bem restrito, mais especificamente o mercado de empresas de TI ligadas a saúde (Healthcare IT), nos States. Nada de mal nisso, mas a chamada ficou meio apelativa, já que a expressão 'do mercado' remete a uma idéia muito mais ampla.

A empresa que fez a pesquisa é a KLAS, empresa "especializada em monitoramento e relatórios de performance de vendedores e empresas de serviços profissionais de TI voltadas a área de saúde" (tradução livre que consta no relatório). A pesquisa propriamente dita não está disponível completa (pelo menos não gratuitamente no site), mas um pequeno "trailer" dela vc pode baixar aqui.

O resultado dessa pesquisa nem me surpreendeu muito (depois de eu ler a página). O Caché (e por tabela, o Ensemble, que é construído sobre este) é muito forte na área médica (TI em hospitais, etc) nos EUA, graças a herança do MUMPS. Apesar da Intersystems não gostar muito, o Caché é uma implementação do MUMPS, algo que por questões de marketing eu acho (e não sei porquê), eles não gostam de alardear por aí.

Na minha opinião, se fosse realmente uma pesquisa no mercado geral, o Ensemble não se sairia muito bem. Não tenho dados que confirmem isso, então se algum leitor que porventura tenha caído aqui souber de dados confiáveis, me avise! (por favor, sem aqueles 'white papers' da própria empresa, que o pessoal de marketing deve elaborar).

sexta-feira, 21 de setembro de 2007

Paralisação na Celesc - Tempo

Depois da ameaça (e efetiva paralisação hoje de manhã), a diretoria e o presidente Pinho Moreira (da Celesc)se sentaram para negociar. Por isso, a paralisação terminou. Vamos ver se não acontece mais nenhum perrengue, que obrigue a chegar a esse ponto (ou pontos mais fundos ainda).

quinta-feira, 20 de setembro de 2007

Paralisação na Celesc - Prólogo

Como já disse, trabalho na Celesc.

Bem, amanhã (dia 21/09) talvez haja uma paralisação, em decorrência da diretoria ficar enrolando (e pior, ficar enrolando de uma maneira que diz: "vamos te enrolar pq vcs são trouxas"), alguns assuntos pro acordo coletivo de trabalho.

Não sei se vou aderir, afinal, não pretendo continuar por muito tempo na empresa, e não quero ficar tendo dor de cabeça por causa de outras pessoas, muitas delas, mesmo tendo interesse no assunto, vão ficar só esperando pelos outros. (Isso sim é ser brasileiro: eu sou brasileiro e não desisto nunca. Não desisto de esperar alguém fazer por mim...)

Se ainda não notou, já perdi as esperanças nessa humanidade...

terça-feira, 4 de setembro de 2007

Web Services no Caché/Ensemble - A Saga - parte 3

Finalizando a trilogia...

Como havia dito no fim do post parte 2, escrevendo o SOAP-Body na mão, eu consegui que fosse enviada a mensagem de requisição corretamente para o Web Service. Entretanto, por algum motivo que desconhecia, a mensagem de resposta não estava chegando corretamente. Primeiro pensei que não havia funcionado, mas depois analisando o tráfego da máquina, olhando a stream TCP, vi que o serviço estava retornando corretamente. O problema é que o Caché/Ensemble não entendia a resposta.

Testei com as classes cliente geradas automaticamente, e mesmo eu não mudando uma vírgula, o erro continuava. Conclusão: de nada adiantara toda a minha ginástica até agora. Analisando o código fonte, e pelas poucas pistas que o erro retornava, cheguei a conclusão de que o Caché não estava conseguindo instanciar uma classe com base na mensagem de resposta do Web Service:


(exemplo de uma resposta a uma requisição bem sucedida)

A esta altura, já cansado de tanto fuçar tanto a documentação quanto o código fonte das classes do pacote %SOAP do Caché, parti para o baixo nível: já que o pacote %SOAP não conseguia entender o Web Service, então desci na pilha de protocolos pra implementar de uma maneira que desse certo. O Caché puro não tem classes para lidar direto com o HTTP (pelo menos eu não achei), mas o Ensemble tem um "Adapter" HTTP. Lá vou fazer testes com ele, e descubro que não consigo usar no Web Service específico, porque esse Adapter não me deixa setar no cabeçalho um campo, e o serviço exige que contenha no cabeçalho da requisição HTTP o campo SOAPAction.

(Um parêntese: Adapters na linguagem do Ensemble são pedaços de softwares que têm a função de conectar o Ensemble a outros sistemas/tecnologias. A documentação desses Adapters deixa muuuito a desejar, pra ter uma idéia, na página que lista os Adapters nem está listada o Adapter HTTP, e muitos dos Adapters ali listados não consta do que eu tenho aqui disponível. Estranho, muito estranho)

Não conseguindo usar o HTTP, resolvi descer mais um nível na pilha, e usar direto o TCP. O Adapter TCP do Ensemble nada mais é do que um "wrapper" para os Sockets do Caché, um assunto que eu já dei um pitáculo neste dois posts anteriores aqui e aqui.


(pilha de protocolos... quase que chego no IP =P )

O bom de usar TCP é que eu tenho o controle de tudo, dos cabeçalhos do HTTP, do conteúdo da mensagem XML SOAP, etc. O ruim é que você pra usar isso, além de entender boa parte de toda essa parafernália, tem que fazer tudo no braço. Mas como as ferramentas prontas do Caché/Ensemble estavam só me dando erro, não tive muita escolha. XD

Olhem como ficou (mais ou menos, aqui tá simplificado):



E assim, apesar de ficar com um código horrível, usando mais anti-patterns possível, finalmente funcionou. E com tudo isso chamam o Ensemble de "Integrador"... Imagina se não fosse...

segunda-feira, 3 de setembro de 2007

Web Services no Caché/Ensemble - A Saga - parte 2

Continuando a saga...

Eu havia me baseado na versão anterior do Web Service/Wsdl que me foi passado, pra implementar todas as estruturas internas no Ensemble (a saber, o BPEL e as "Business Operations", na linguagem deste).

Bem, na versão anterior, o wizard gerava todas as estruturas como classes, ou seja, se eu tivesse que atualizar uma estrutura "Pessoa", eu teria uma classe "Pessoa" com as propriedades "Nome", "Idade", etc... e o método receberia uma instância desta classe. Entretanto, agora o wizard gerou o método recebendo cada propriedade da estrutura como um parâmetro, ou seja, antes eu tinha:


atualizaPessoa(p as Pessoa)


E agora ele me gerou:


atualizaPessoa(nome as %String, idade as %Integer, ...)


E é CLARO que isso quebrou tudo o que eu havia feito. Para não ter que retrabalhar todas as outras classe, resolvi que seria melhor alterar na mão as classes que o wizard havia gerado, para se adaptarem aos objetos com que eu já trabalhava.

Então lá fui eu alterar a classe Web Client gerada:


(antes)


(depois)

Pensei que só isso já bastaria para funcionar. Compilei e mandei rodar. Não funcionou... Resumindo a luta, descobri que desse modo ele não estava escrevendo o Body do envelope SOAP conforme o Web Service esperava, contendo os namespaces corretamente declarados.

Procurando uma solução, me deparei com a propriedade "WriteSOAPBodyMethod" da classe %SOAP.WebBase, que é superclasse de %SOAP.WebClient. Essa propriedade permite definir um método que escreva de maneira personalizada o body de uma mensagem SOAP.

Usando essa propriedade, temos:


(escrevendo um elemento SOAP-Body personalizado)

Com isso, finalmente consegui preservar todo o código que eu havia feito anteriormente, e ainda acionava corretamente o Web Service. Entretanto, a saga não acaba aqui...

Continua...

Web Services no Caché/Ensemble - A Saga - parte 1

Há um tempo atrás postei um pequeno elogio ao Caché/Ensemble no que concerne a suporte a Xml. Seguindo o pensamento, poderíamos crer que o suporte a SOAP e Web Services seria igualmente bom. Infelizmente, acabei encontrando problemas por aí. Neste post estou começando uma série, que deve ilustrar toda uma saga XD.

Inicialmente, eu tinha um Wsdl descrevendo alguns serviços de um software que a Celesc comprou, e que precisaríamos usar para integrar com os nossos sistemas (a saga para conseguir esse Web Service tb é longa, mas muito chata, não devo postá-la aqui).

Bem, já tendo realizado alguns testes criando clientes para outros Web Services, pensei: "moleza". Ledo engano. O Wizard que gera um cliente SOAP de cara me jogou um erro. Infelizmente, como pode-se ver no print screen abaixo, o Caché/Ensemble não é muito informativo, então fiquei na dúvida: será que me enviaram um Wsdl bixado, ou o Caché não entende certas coisas? (Neste ponto, ambas as hipóteses eram igualmente prováveis).


(Tela com o erro do wizard)

Resolvi testar o Web Service utilizando o soapUI, um programa feito em Java, muito bom, que permite vc testar Web Services. Eu falei dele no post anterior.

Usando o soapUI, eu consegui não só montar o esqueleto correto das mensagens SOAP, como também testar o serviço. Isso me levou a conclusão de que havia alguma coisa errada com o wizard do Caché. Mas como curioso que eu sou, fui procurar o que nesse Wsdl específico estava causando erro. Depois de perdido meio dia, comparando Wsdls, e fuçando nas especificações, finalmente encontrei o problema: a mensagem de requisição estava com dois elementos, quando o normal é ter apenas um:


(pedaço do Wsdl que estava me causando problema)

Comentando a tag do segundo elemento da mensagem, o wizard funcionou. Um alívio que durou pouco...

Numa versão antiga deste Web Service (que haviam me passado anteriormente), o wizard havia funcionado corretamente, e eu havia baseado a implementação toda nele. Claro que agora haviam algumas mudanças, mas achei que não era muitas. Eu estava totalmente errado...

Continua...

SoapUI

Hoje vou falar sobre um programa muito útil que eu uso aqui, especialmente quando se trata de testar Web Services SOAP. O programa se chama soapUI, e é open-source, mas com uma opção paga com suporte. A instalação desse programa é simples, e está bem documentada no site.

Depois de instalado, primeiramente criamos um projeto, (File -> New WSDL Project). Este projeto pode conter quantas referências a web services você quiser, é mais uma maneira de se organizar logicamente do que uma imposição.

Suponhamos que eu tenha criado um projeto chamado 'zzz'. Para adicionar um Wsdl, basta clicar com o botão direito em cima do projeto, e ir na opção de adicionar um Wsdl de uma Url ou de um arquivo.



Responda sim a questão de criar requisições padrão para todas as operações e pronto. Basta você ir abrindo na árvore a esquerda, e vai ver as operações listadas, bem como uma requisição padrão para cada uma delas. Na figura, vemos apenas uma operação, com uma requisição (mas você pode criar várias mensagens de requisição, e deixar todas ali registradas, facilitando os seus testes):



Agora, basta você preencher as tags com os valores que quiser testar, e clicar no botão de submeter requisição (o botão verde), e ver na no quadro a direita, o Xml com o resultado:



Agora é só instalar aí na sua máquina e testar um pouco.