sexta-feira, 24 de outubro de 2008

CIO

- Qual é a sua posição na empresa?
- C.I.O.
- Quer dizer que você fica na "posição de cio"?
- Isso não soou legal... ¬¬

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

C.I.O - Chief Information Officer

quarta-feira, 15 de outubro de 2008

Aprender banco de dados? É com mangás!

Ah, se já tivesse isso nos meus tempos de faculdade...



Mais no outro blog.

quinta-feira, 28 de agosto de 2008

Programador também é gente

Por isso, abrace o seu hoje.

Claro que eu prefiro abraçar uma colega programadora. =P



Via WTFBrasil.

quinta-feira, 26 de junho de 2008

Javascript é power, também com imagens

Que Javascript é poderoso, todo mundo que usa os aplicativos do Google deve saber (o Gmail é um bom exemplo), afinal, praticamente todos esses novos aplicativos Web 2.0, que exibem grande interação, usam AJAX e consequentemente, Javascript.

Mas e que tal aplicações menos... sérias? Já imaginou jogar no seu browser, totalmente feito em Javascript, clássicos como Wolfenstein 3D, Super Mario Bros. ou mesmo Super Mario Kart?

Jacob Seidelin, que tem o blog Nihilogic, não só pensou nisso, mas colocou as mãos na massa. Tá certo que os jogos não estão completos, mas só pelo fato de terem sido totalmente escritos em Javascript, é um feito e tanto!

Veja algumas imagens:

Super Mario Kart em Javascript:



Wolfenstein 3D, em Javascript:



Super Mario Bros. em apenas 14 Kb, em Javascript:




Além desses jogos, o cara, que deve ser fera em manipulação de imagens com Javascript, tem muitos outros projetos em seu blog. Como por exemplo, um pequeno editor de imagens: Pixastic : Photo editing with JavaScript and canvas.

Por causa das diferenças entre browsers, em especial o suporte ao canvas, alguns projetos do cara podem não rodar muito bem no Internet Explorer. Se bem que, com o Firefox 3, quem é que precisa do IE? =)

Post originalmente feito para o meu blog pessoal.

sexta-feira, 16 de maio de 2008

Hackeando o Caché - parte 2

No post anterior eu alertei sobre um bug em como o Caché lida com requisições HTTP para as suas páginas CSP (Caché Server Pages), bug este que pode ser explorado em um ataque de negação de Serviço (DoS, em inglês).

Neste post, detalharei não somente o bug, mas todas as circunstâncias que levaram a descoberta deste bug, suas consequências e detalhes técnicos.

Como tudo começou

Num dia como qualquer outro, de repente começam a me dizer que o Ensemble (o "ESB" construído sobre o Caché) estava com problemas, que os Web Services estavam retornando com erro, e que a página na Internet que fazia uso deste Web Service (é uma página em PHP) estava com caracteres malucos por causa dos erros.

Estranhei, não porque o Ensemble/Caché seja tão livre de bugs assim (porque o que tem de bugs e WTFs dentro dele não é nada desprezível), mas porque eu não havia colocado nada de novo em produção, nem mexido em configuração alguma. E também tinha certeza de que mais ninguém havia alterado nada no servidor há pelo menos dois dias.

Resolvi testar o Web Service usando o soapUI (versão 1.6), um programa open-source feito em Java que é uma mão na roda para se testar Web Services padrão SOAP (e do qual eu já até falei neste outro post). Tudo pareceu OK, o Web Service estava retornando sem erro, e com os dados corretos. Fui falar com a pessoa que havia me alertado, e disse que estava tudo bem.

Logo mais, esta pessoa volta e me diz que ainda está ocorrendo erro. Desta vez resolvo abrir a página web que utiliza o Web Service, e realmente estava com erro. Pensei que talvez estivesse exibindo algum cache, então testei novamente com o soapUI, e mais uma vez, o resultado veio correto. WTF, pensei... Perguntei para a pessoa que havia me dito do erro, EXATAMENTE o que havia acontecido, como ela havia descoberto o erro. Então me disse que havia tentado invocar o Web Service utilizando o Netbeans, e como neste retornava erro, checava depois a página web, a qual também mostrava erro.

Fiquei intrigado, e como tenho o Netbeans 6 instalado aqui, resolvi fazer os mesmos passos. E não é que usando o Netbeans para testar o Web Service, dava erro mesmo? E pior, depois de usar o Netbeans, as chamadas ao Web Service, pela página web, começavam a dar erro também. Tentei então entrar no Portal de Administração do Caché/Ensemble, e até estas páginas estavam retornando erro. Nem mesmo a página de login funcionava! Só o que eu via era a página padrão de erro do CSP.


Pronto, era o que faltava, o Netbeans derrubava o Caché/Ensemble! =P

Reiniciei o Caché/Ensemble, já que aquele ali era o servidor de produção, e pedi pra que ninguém mais acessasse ele pra fazer testes, pra não prejudicar os outros sistemas em produção, que usavam o infeliz. Fui então para o servidor de desenvolvimento e testes, a fim de descobrir o que raios estava acontecendo.

Fazendo trabalho de detetive

A primeira dica para a resolução do caso foi que no Netbeans ocorria erro, mas no soapUI não. Nos testes, mesmo depois de "derrubar" o Caché/Ensemble com o Netbeans, acessando via soapUI não ocorria erro. Resolvi investigar bem a fundo, e coloquei o Ethereal pra rodar, um bom programa sniffer, open-source, pra analisar todo o tráfego que chegava no Caché/Ensemble, no nível de stream TCP.

Como a página de erro (na figura acima) mostrava um erro relativo ao CharSet, fui verificar como o soapUI e o Netbeans enviavam as suas requisições. O CharSet é enviado junto com a requisição HTTP, fazendo parte dos cabeçalhos (Headers) HTTP, e serve para identificar qual o conjunto de caracteres que está sendo usado para enviar dados.

Abaixo uma requisição que não gerava erro, vinda do soapUI:

POST /csp/ensemble/pacote.Classe.cls HTTP/1.1
Content-Type: text/xml;charset=UTF-8
SOAPAction: "http://namespace/pacote.Classe.Metodo"
User-Agent: Jakarta Commons-HttpClient/3.0.1


E uma requisição que gerava erro, vinda do Netbeans 6.0:

POST /csp/ensemble/pacote.Classe.cls HTTP/1.0
SOAPAction: "http://namespace/pacote.Classe.Metodo"
Content-Type: text/xml;charset="utf-8"
User-Agent: JAX-WS RI 2.1.2-b05-RC1


(Modifiquei as informações referentes às classes reais e endereços, por questões de privacidade; também retirei o corpo xml pelo mesmo motivo.)

Aparentemente, as duas chamadas são bem parecidas, apesar do Netbeans enviar alguns cabeçalhos HTTP a mais (que eu retirei do exemplo acima, pra não ficar poluído demais), mas que nada influem. A versão do HTTP neste caso, também não influenciava no problema.

Analisando o Content-Type, a primeira vista não parece que haja uma grande diferença entre eles. O fato do conteúdo do CharSet ser utf-8 ou UTF-8 não influi. Entretanto, na requisição gerada pelo Netbeans, o valor do CharSet é enviado entre aspas. E isso fez TODA A DIFERENÇA.

<?xml version='1.0' encoding='UTF-8' standalone='no' ?>
<SOAP-ENV:Envelope>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>Internal Server Error</faultstring>
<detail>
<error xmlns='http://tempuri.org' >
<text>ERROR #5911: Character Set "utf-8" not installed, unable to perform character set translation</text>
</error>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>


Se repararmos na tela de erro, ou na mensagem SOAP-Fault acima, veremos que a mensagem é Character Set '"utf-8"' not installed.... Ou seja, ele considerava o CharSet com ASPAS!

Apesar de ser desejável o servidor aceitar o CharSet entre aspas normalmente, retirando as aspas para "entender" qual o CharSet usado, não encontrei nada que diga que isso seja uma obrigação ou mesmo uma recomendação da especificação do HTTP.

O erro propagado

O erro ocorrer na resposta da requisição "defeituosa" é o procedimento esperado. Entretanto, depois de ter chamado o Web Service e ter recebido o erro, depois de alguns momentos, ao acessar o portal de Administração do Caché/Ensemble, este também retorna erro, e logo tudo que depende de CSPs é inutilizado, pois para qualquer requisição via browser a página de erro é mostrada. Tipicamente, os browsers não enviam o cabeçalho Content-Type, o que torna a mensagem de erro bem peculiar.

Fiz um pequeno programa em Java, para poder manipular livremente todos os cabeçalhos HTTP e enviar ao servidor. Modificando o valor de CharSet para qualquer valor, este apareceria na tela, junto com a mensagem de erro.

Além disso, comecei a testar fazendo não requisições SOAP, usando o método POST do HTTP, mas usando o método GET, que é o método usado geralmente pelos navegadores para pegar páginas Web. E não para minha surpresa, colocando-se um endereço de página CSP válido, e usando o cabeçalho com o CharSet alterado, o erro também acontecia.

Por exemplo, enviando a requisição HTTP abaixo:

GET /csp/ensemble/pacote.Classe.cls HTTP/1.0
Content-Type: text/xml;charset=Uma Mensagem Qualquer Aqui


Depois de alguns instantes, qualquer chamada a qualquer página CSP retornaria a seguinte tela de erro:


Aparentemente, o Caché mantém algumas estruturas/objetos que ele reutiliza entre as chamadas, o que causa a propagação do erro. Entretanto, se o servidor for altamente requisitado, no início o Caché já mantém vários objetos instanciados, e a chamada defeituosa vai afetar apenas aquele objeto que for usado na chamada, deixando os outros normais. Por isso, num servidor muito acessado, que mantém vários objetos instanciados, o erro fica intermitente entre as requisições HTTP posteriores, ora retornando com problema (porque usou nesta chamada o objeto com erro), ora retornando OK (porque usou outro objeto já instanciado, que não fora afetado).

Pude constatar isso ao fazer o 'ataque' na página de documentação do Caché 2007, no site da Intersystems. Ali, depois de fazer uma requisição HTTP defeituosa com o programinha que desenvolvi, algumas requisições via browser retornavam erro, enquanto outras retornavam OK, independente do endereço requisitado.

O bug provavelmente se encontra no objeto que representa uma requisição CSP, o %CSP.Request. Notem que este objeto é reutilizado, como podemos ver pela existência do método Reset, onde provavelmente reside o erro.

Explorando o bug

Alguém mal intencionado pode explorar o bug, realizando um ataque de negação de serviço em um site que use CSP. Para tal, nem é preciso realizar um imenso número de requisições, basta umas poucas requisições de tempos em tempos. Se o site tiver pouco movimento, uma ou duas requisições a cada 10 minutos seria suficiente para torná-lo inoperável.

Durante os testes, fiz uma pequena classe em Java para facilitar os testes, abrindo um Socket e escrevendo direto na stream TCP, ou seja, enviando os comandos e cabeçalhos HTTP como eu quisesse. Este programa acaba sendo uma Prova de Conceito ou do inglês, Proof of Concept.

Abaixo, listo a classe, que é bem simples (e com comentários dentro do código):


package testes;

import java.io.*;
import java.net.*;

/**
 @author Emilio, o ráqui.
 */
public class Main {

   /**
    * Uso: java testes.Main
    */
   public static void main(String[] argsthrows Exception {
       try {
           //Criação e abertura de um Socket. 
           //O primeiro parâmetro é um endereço ou IP, no exemplo, o host da
           //Intersystems que contém a documentação do Caché em CSP. Pode ser
           //alterado para qualquer endereço.
           //O segundo parâmetro é a porta TCP, como estamos lidando com HTTP,
           //o padrão é a porta 80. A não ser que o site use outra porta não-
           //padrão, esse parâmetro não precisa ser alterado.
           Socket s = new Socket("docs.intersystems.com"80);

           //Monta uma String com o comando e os cabeçalhos HTTP 
           //Primeiro, colocamos o comando GET, apontando para uma página CSP
           //válida, no caso, a página gerada com a documentação do Caché
           String cabecalho = "GET /cache20071/csp/docbook/DocBook.UI.Page.cls HTTP/1.0 \r\n"+
           //Este cabeçalho, Content-Type, que pode ser alterado para causar 
           //o bug. Basta alterar o charset para um valor inválido (ou uma
           "Content-Type: text/xml;charset=Uma Mensagem Qualquer Aqui\r\n"+
           //outros cabeçalhos HTTP podem ser colocados.
           "Connection: keep-alive \r\n"+"\r\n";

           //Cria e abre objetos para escrever e ler na stream de dados TCP
           DataOutputStream dos = new DataOutputStream(s.getOutputStream());
           DataInputStream dis = new DataInputStream(s.getInputStream());

           //envia a requisição previamente montada
           dos.writeBytes(cabecalho);

           System.out.println("Enviado");
           
           //Código para ver na saída padrão o que foi retornado pelo servidor.
           byte[] buff = new byte[32000];
           dis.read(buff);
           System.out.println("Recebido:\n" new String(buff));
           buff = new byte[32000];
           dis.read(buff);
           System.out.println(new String(buff));

       }
       catch(Exception e) {
           e.printStackTrace();
       }
   }
}


Providências

Se você roda aplicações CSP, especialmente sites na Internet, e que rodem em versões do Caché afetadas (anteriores a 2008), é altamente aconselhável que procurem a Intersystems para obter um patch.

Como aqui na empresa este patch já está instalado, e aparentemente o erro foi corrigido, o processo de obtenção do patch deve ser razoavelmente rápido (ao contrário da maioria das requisições de suporte que tivemos até agora), já que ele já foi feito.

Agora, se você preferir dar uma de 'hacker', pode fuçar e mexer no %CSP.Request. Claro que para isso, você deve tirar a base de dados CACHELIB do modo somente leitura, que é o padrão. Nesta classe, no método CSPGatewayReset (que é um método gerador de código), você pode incluir comandos para "zerar" o CharSet. Em alguns testes, essa solução também funcionou, mas como não me aprofundei mais no assunto, não aconselho a fazerem isso em um ambiente de produção.

segunda-feira, 5 de maio de 2008

Hackeando o Caché

Bem, o título chamativo acima não é verdadeiro, pois não se trata de um hack no sentido original da palavra, nem no sentido de invasão de sistemas. Se trata mais de um ataque (isso sim) de Negação de Serviço, ou como é conhecido em inglês, DoS (Denial of Service), que pode comprometer sistemas desenvolvidos usando a tecnologia CSP (Caché Server Pages) do Caché da Intersystems.

O bug que torna possível o ataque afeta as versões anteriores a versão 2008 - nos meus testes pessoais, com máquinas que eu tinha controle, testei no Caché 5.0 e 5.2. No próprio site da Intersystems, a documentação online (que é gerada em CSP) da versão 2007 também é afetada por este bug.

Como pode acontecer o ataque?

Um bug faz com que, depois de uma requisição HTTP a um endereço legítimo que use CSP (e isso inclui chamadas a Web Services do Caché), as páginas CSP chamadas depois, gradualmente comecem a retornar erro. Este erro persiste por algum tempo, até que depois de alguns minutos, que nos meus testes pareceu aleatório (mas que ficou em torno de 30 minutos), as páginas CSP retornam à normalidade.

Um hacker 'do mal' pode aproveitar este bug para tornar um site construído em CSP inoperável, simplesmente, fazendo com que de tempos em tempos, uma requisição HTTP especialmente preparada, seja enviada. Ou seja, você pode causar um ataque de Negação de Serviço (DoS) enviando uns poucos pacotes a um servidor, de tempos em tempos.

Este bug já foi reportado a Intersystems, por meio da parceira regional, e apesar de não haver encontrado nenhuma menção a este bug (nem na documentação nem na web, via google), parece que o pessoal já sabia dele, pois a versão 2008 do Caché aparentemente não apresenta este problema.

Que providências tomar?

Se você identificar que seu sistema pode ser afetado, você tem duas alternativas: ou migrar para a versão 2008, ou solicitar um "patch" para a Intersystems, para a sua versão de Caché.

Apesar de eu ser um entusiasta dos upgrades, nem tudo são flores: uma migração de versão, especialmente se for uma versão mais antiga, pode se tornar muito trabalhosa, inserir novos bugs, quebrar funcionalidades, etc... Se for este o caso, sugiro que peça emergencialmente um patch para a sua versão do Caché.

Detalhes Técnicos

Por enquanto, como ainda não obtive resposta da Intersytems, não vou liberar todos os detalhes técnicos do bug aqui no blog, nem o código que eu usei pra explorar o bug (a chamada prova de conceito). Esperarei mais algum tempo, e se o suporte (que conosco, não tem sido dos melhores) não responder satisfatoriamente, divulgarei assim mesmo o código.

sexta-feira, 25 de abril de 2008

Sincronizando Business Objects e Business Processes no Ensemble

O cenário: temos algumas informações que devem ser replicadas em diferentes bases de dados. Esta escolha de se replicar os dados, se deve a diversos fatores, que não convém mencionar aqui.

No Ensemble da Intersystems, temos um serviço de atualização de dados em duas bases de dados distintas, inclusive com estruturas diferentes. A primeira das bases de dados, em Oracle, é acessada diretamente via ODBC, pelo Ensemble. A segunda, também em Oracle, não permite o acesso direto (pois como é usada por um software de terceiros, perderia-se a garantia de performance caso o acesso fosse liberado). Para tanto, a empresa que desenvolveu este software criou uma camada utilizando Web Services (WS) padrão SOAP, para a atualização dos dados.

Além de ter uma estrutura diferente, a segunda base de dados possui algumas restrições (constraints), o que faz com que alguns dados possam ser válidos na primeira base, mas não na segunda. Essas restrições se devem a interações que alguns dados possuem dentro do segundo banco, especialmente em se tratando da codificação das entidades dentro dele (isto é, do formato/geração das chaves). Isto ocasiona que para alguns dados, a segunda base rejeita os dados, mas a primeira base os aceita.

Pois então, com estes requisitos em mente, construímos um Business Process (BP) no Ensemble, mostrado na figura abaixo simplificadamente (exclui alguns passos do processo que são irrelevantes para o entendimento deste post):



Notem que a primeira chamada é para um Business Object (BO) que lida com a chamada para o WS. O Studio, a IDE integrada do Caché/Ensemble tem um wizard para a criação de toda a estrutura de invocação de um Web Service SOAP, bastando ter o WSDL correspondente. Além de montar as classes de dados e a classe cliente do Web Service, no Ensemble você pode gerar já a estrutura de BO e mensagens de Request/Response.

O Web Service retorna como resposta uma String. Se a atualização se deu sem erros, é retornado "OK". Caso contrário, é retornado uma breve descrição do erro. Bem, o segundo elemento do BP é justamente uma checagem para ver se aconteceu algum erro. Se aconteceu algum erro, retorna imediatamente. Caso contrário, faz a chamada para dois BOs que cuidarão da atualização na primeira base, via ODBC. Note como as estruturas são diferentes: uma única estrutura de dados da segunda base (atualizada via WS) equivale a duas estruturas na primeira base (atualizada via SQL-ODBC).

Rodando diversos testes, com usuários inclusive, surgiu um problema. De vez em quando, mais frequentemente do que eu gostaria, por alguma razão, o Adapter SQL perde a conexão e/ou sessão com o Oracle, gerando exceções. Falarei mais sobre isso em um outro post, mas fica registrado que esses problemas podem ser tão graves, que necessitam até de shutdown de todo o Caché, para o Caché/Ensemble se recuperar do erro.

Acontecendo isso, você, astuto leitor, deve ter percebido que o processo no BP tem um grave problema: ele pode atualizar a segunda base via WS, e não atualizar a primeira, causando inconsistências (lembrando que o WS é simples, não tem nada de WS-Transactions, por exemplo). A solução é colocar a chamada do Web Service dentro do contexto da transação da primeira base, e se o WS falhar, executar um rollback nos SQLs já executados. Mas... como fazer isso?

Uma solução é jogar todo o código dentro de um mesmo BO (que já iria ficar enorme, já que pra colocar tudo numa mesma transação, as duas BOs que usam ODBC-SQL se tornariam uma só). Então, pra evitar mais código-macarrão dentro de um único BO, resolvi aproveitar a estrutura do BO que faz a chamada do Web Service, e manter Business Objects separados para atualizar via WS e via ODBC.

Então, eu deveria: executar as atualizações no banco via SQL, mas não dar Commit; chamar o Web Service, e caso de sucesso na atualização do segundo banco, executar o Commit, senão dar um Rollback.

Como fazer isso, usando dois BOs separados, sendo que ao terminar a execução de uma chamada, o BO com o Adapter SQL pode não necessariamente manter a sessão (pode ser até outra instância do BO a ser invocado)? A solução é deixar tudo dentro de uma única chamada do BO. Então, surge a necessidade de comunicar/sincronizar o BO com o BP, sem terminar a execução do BO. Para isso, decidi fazer uso de globais.

Simplificadamente, a solução adotada foi:

- No BP, fazer um "fork", ou seja, abrir dois caminhos de execução paralelos.
- No primeiro caminho, colocar o BO com o Adaptador SQL.

  • Neste BO, setar controle de Commit como manual.
  • Executar os comandos SQL.
  • Se ocorrer algum erro, pode sair do BP e retornar o erro.
  • Se não ocorreu erro, sinaliza usando a global, e espera pelo sinal de retorno do WS (também sinalizado via global).
  • Se o WS retornou "OK", executa o Commit.

- No segundo caminho:
  • Coloca um código de espera, até o BO com o Adaptador SQL sinalizar.
  • Fazer a chamada do BO que invoca o Web Service.
  • Sinaliza para o BO com o SQL Adapter, o resultado da invocação do WS.


Todas as sinalizações foram feitas usando uma global. Como podemos ter mais de uma instância, usamos como controle uma chave de sessão simples, para acessar a global, e evitar conflitos.

Toda esta mega-gambiarra pode ser vista no BP abaixo:



A primeira parte do código responsável pela sincronização é mostrada abaixo. Nele, que está na BP como "Seta chave sessão para global", configuramos uma chave que identificará a "sessão", usando um timestamp ($h ou $horolog, que retorna o dia/hora atual), mais um número aleatório. Usando esta chave, acessamos uma global que será a área de dados compartilhados entre a BO e a BP.


set context.chaveGlobal = $h _ $random($piece($h,",",1))
set ^globalSync(context.chaveGlobal) = 0


Ainda na BP, colocamos na parte de código "Espera por Oracle", o código abaixo, que é basicamente um loop, que de tempos em tempos, checa se o valor na global mudou. Note que o comando HANG faz com que a execução do código seja interrompida por algum tempo, mais especificamente o tempo passado como parâmetro do comando, em segundos. No código abaixo, 200 ms, ou seja, o código checa se houve mudança na variável global de 200 em 200 milisegundos.


while (^globalSync(context.chaveGlobal) < 1) {
hang 0.2
}


Dentro da BO, depois de realizar os comandos SQL, mas antes de efetuar o COMMIT, usa-se o código abaixo. Nele, a variável global muda de valor, sinalizando para o código na BP que ela pode continuar a execução. Além disso, também temos um loop de espera, pois o COMMIT só pode ser realizado depois que o Web Service retornar. Note que enquanto no código da BP a chave global está dentro de context, na BO ela está dentro da mensagem pRequest enviada à BO.


set ^globalSync(pRequest.chaveGlobal) = 1

while (^globalSync(pRequest.chaveGlobal) = 1) {
hang .2
}


Voltando a BP, depois que a BO que invoca o Web Service retorna, verificamos se este retornou OK ou se aconteceu algum erro. Em ambos os casos, sinalizamos o resultado na variável global, como mostra o código abaixo:


if (context.statusWS.StringValue = "OK") {
set ^globalSync(context.chaveGlobal) = 2
}
else {
set ^globalSync(context.chaveGlobal) = 3
}


Por fim, nos últimos passos da BO, depois de passar pelo loop de espera, temos certeza de que o Web Service retornou (ou ocorreu um erro de timeout, por exemplo). Só resta então checar o status na variável global, e realizar o COMMIT ou ROLLBACK, como mostra (simplificadamente) o código abaixo:


if (^globalSync(pRequest.chaveGlobal) = 2) {
set st = ..Adapter.Commit()
}
else {
do ..Adapter.Rollback()
}

terça-feira, 25 de março de 2008

Um dos segredos do Google: Cerveja, churrascos e muitos sushis - tudo de graça!

O ex-chef no Google, Charlie Ayes, fora contratado pelo Google antes da companhia se tornar se tornar o monstro que é hoje.

No seu livro, In Eat Yourself Smart, o chef relata as suas experiências ao ser contratado pelos dois fundadores do Google, Sergey Brin e Larry Page, e como a política do Google de recursos humanos, passando pela comida, tornou a empresa o que ela é.

Para atrair as mentes brilhantes que são o insumo da empresa, comida e bebida grátis (sempre!), e de qualidade. Inclusive cerveja e churrasco, yeah!


O chef ainda diz que um dos motores do crescimento do google foram as altas doses de sushi (!!), pois a gordura presente no peixe ajudaria as células do cérebro.

A matéria completa, do The Sunday Morning Herald (em inglês, claro), você encontra aí no link.

Via TechCrunch.

Não dá uma inveja do povo que trabalha lá?

quarta-feira, 5 de março de 2008

Coisas esquecidas pelo caminho

Tentando descobrir uma solução para alguns problemas no Caché/Ensemble da Intersystems, me deparo com o seguinte pedaço de código:

i ..sqlcode'=-0 q


Para os não-iniciados em Caché Object Script (a linguagem do Caché):

i - abreviação do comando if!!! Oh yeah baby, em Caché ou você escreve if ou abrevia para um singelo i. Só isso já mereceria um WTF pra ele.

.. - indica que o elemento a seguir pertence a classe. Em Java seria this, em Pascal, self

sqlcode - apenas o identificador de uma variável local

'= - operador de diferença. Em Java seria !=, em Pascal, <>

q - abreviação do comando quit. O comando quit, ou q, como queiram, serve para: sair de um loop ou sair de um método. Em Java seria um break ou um return, respectivamente.

Agora o que não tem explicação é o "-0".

Por que alguém compara alguma coisa com menos zero, e não com zero? Tá certo que o Caché não é muito fã de matemática (vide a precedência de operadores), mas ele sabe que menos zero e zero são a mesma coisa. Ou será que não, para nossos intrépidos colegas da Intersystems?

Eu acho que alguém acabou esquecendo o sinal lá...

quinta-feira, 28 de fevereiro de 2008

Nova propaganda para o Caché

Olhem a imagem da propaganda do Caché...


E você, meu caro desenvolvedor, cansado das tabajarices que encontra no Caché? Cansou de ouvir o pessoal de marketing dizendo 'bla bla bla', e você encontrar só bug? Não, não é bug. É feature.

Pois esta é a imagem que reflete melhor o que eu tenho encontrado:



Imagem via UsuárioCompulsivo, neste post: Não é bug... É recurso.

terça-feira, 26 de fevereiro de 2008

Argumentos variáveis (varargs) são para os fracos - ou Como o Ensemble lida com isso

Em Caché Object Script (a linguagem padrão pra se desenvolver no Caché da Intersystems) não conseguimos definir métodos que podem ter um número arbitrário de parâmetros, como as VarArgs do Java, ou mesmo as funções com argumentos variáveis do C/C++.

Entretanto, ainda podemos passar como parâmetro, um array ou uma List.

Então, quando me deparo com uma definição de método como esta abaixo... (consta no Adapter EnsLib.SOAP.OutboundAdapter, do Ensemble):

Method InvokeMethod(pMethodName As %String, Output pResult As %RegisteredObject, ByRef pArg1, ByRef pArg2, ByRef pArg3, ByRef pArg4, ByRef pArg5, ByRef pArg6, ByRef pArg7, ByRef pArg8, ByRef pArg9, ByRef pArg10, ByRef pArg11, ByRef pArg12) As %Status


E não estando satisfeito, fui olhar como é que este método estava implementado. Uma pequena amostra abaixo:

        If $D(pArg12)    { Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1,.pArg2,.pArg3,.pArg4,.pArg5,.pArg6,.pArg7,.pArg8,.pArg9,.pArg10,.pArg11,.pArg12) }
elseif $D(pArg11){ Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1,.pArg2,.pArg3,.pArg4,.pArg5,.pArg6,.pArg7,.pArg8,.pArg9,.pArg10,.pArg11) }
elseif $D(pArg10){ Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1,.pArg2,.pArg3,.pArg4,.pArg5,.pArg6,.pArg7,.pArg8,.pArg9,.pArg10) }
elseif $D(pArg9) { Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1,.pArg2,.pArg3,.pArg4,.pArg5,.pArg6,.pArg7,.pArg8,.pArg9) }
elseif $D(pArg8) { Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1,.pArg2,.pArg3,.pArg4,.pArg5,.pArg6,.pArg7,.pArg8) }
elseif $D(pArg7) { Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1,.pArg2,.pArg3,.pArg4,.pArg5,.pArg6,.pArg7) }
elseif $D(pArg6) { Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1,.pArg2,.pArg3,.pArg4,.pArg5,.pArg6) }
elseif $D(pArg5) { Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1,.pArg2,.pArg3,.pArg4,.pArg5) }
elseif $D(pArg4) { Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1,.pArg2,.pArg3,.pArg4) }
elseif $D(pArg3) { Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1,.pArg2,.pArg3) }
elseif $D(pArg2) { Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1,.pArg2) }
elseif $D(pArg1) { Set pResult = $zobjmethod(..%Client,pMethodName,.pArg1) }
else { Set pResult = $zobjmethod(..%Client,pMethodName) }
$$$sysTRACE("Got Result "_pResult)


Tá certo que pela maneira como foi concebida, essa solução nem é tão ruim assim. Isso porque no caso acima, os parâmetros estão sendo sempre passados por referência.

O que me incomoda nem são os trocentos elseifs, ou a "beleza" do código. É a limitação do número de parâmetros. Tá certo que dificilmente são usados 12 argumentos, mas sempre surgem aquelas situações-limite, e um décimo terceiro argumento pode ser necessário. E aí?

Uma solução é ficar alterando a própria API do Ensemble. Como foi feito neste outro post, sobre um problema no Adapter de SQL.

Uma outra solução é largar mão, e procurar outra ferramenta. =P

Eu acho que essa merecia ir pro The Daily WTF.

segunda-feira, 11 de fevereiro de 2008

Variáveis de contexto no Ensemble BPL - Business Process Language

Trabalhando com o Ensemble, a ferramenta de EAI da Intersystems, as vezes trabalhamos com o que é denominado Business Process, ou processos de negócios.

Business Process (abreviados para BP, de agora em diante) dentro do Ensemble são componentes que encapsulam regras de negócios, modelados visualmente, e que para executarem código real, devem chamar outros módulos "menores". Você pode traçar um paralelo da abordagem dos BPs no Ensemble com o WS-BPEL, substituindo os Web Services por outras construções internas do Ensemble, em especial, os componentes que ele chama de Business Operations (ou BOs, pra encurtar).

De fato, o pessoal marketeiro da Intersystems até mesmo batizou a "linguagem" utilizada no Ensemble de Ensemble BPL (ou E-BPL), uma clara alusão ao WS-BPEL. Apesar do E-BPL ser bem menos complexo e completo do que o WS-BPEL (o "E" faz muita diferença, já que um Executa, e outro não), ele inclui em sua definição a notação de elementos gráficos. Neste aspecto, ele se aproxima do Business Process Modeling Notation, o BPMN.

Qualquer dia falo mais sobre a BPEL/BPMN e o Ensemble BPL, mas hoje quero me focar em algo que acontece especificamente com o BPL do Ensemble, no qual eu trabalho.

(Um BPL de exemplo, na tela de edição do Ensemble)

Como já disse diversas vezes, o Ensemble foi construído sobre o Caché (o que os marketeiros da Intersystems chamam de banco de dados pós-relacional e blá-blá), e é todo baseado nele. Por isso, herda do Caché também algumas definições, nem sempre muito explícitas. E é isso o que estava ocasionando uns erros aqui...

No BPL, podemos criar algumas variáveis no "contexto" do Business Process (BP). Essas variáveis podem ser tanto objetos como tipos básicos, e podem ser usadas para qualquer coisa.

Pois bem, um dia desses, um BP começou a dar erros, depois de eu ter adicionado um Business Operation (BO). Entretanto, nada relacionado ao BO, ele estava correto. Estava dando erro no BP mesmo.

Olhei no log de eventos do Ensemble, e logo uma linha vermelha dizia o motivo: uma variável de contexto, definida como %String, estava acusando erro de tamanho; o tamanho máximo estava definido como 50, e a string tinha tamanho superior a isso.

Como disse num post anterior, ao se definir algo como %String, o Caché/Ensemble acaba automaticamente estipulando um limite máximo pra sua string, e até nesta versão que uso (Caché 5.2, Ensemble 4.0), esse tamanho é 50.

Entretanto, isso não explicava porque o erro começara a aparecer somente depois da adição de um novo BO ao BP... Mas alguns testes depois, e tudo começou a fazer sentido.

Vejamos: Com apenas uma BO, podemos atribuir à variável um String extremamente grande, sem nenhum erro. Se você adicionar outra BO, vai dar erro. Por que?

(Assim não dá erro.)

Porque quando se usa apenas uma BO, todo o processo da BP permanece em memória. E em memória, você pode fazer quase tudo o que quiser. Até violar alguma constraint da classe, como o tamanho de um campo. Ou atribuir uma String a um campo %Date

(Assim dá erro.)

Quando você adiciona uma segunda BO, parte do processamento é gravado em disco (não sei porquê, mas suspeito que seja para "pausar" e recuperar o estado depois...). E quando se grava em disco, o Caché antes faz as checagens nos campos das classes. E isso inclui o tamanho de uma %String...

A solução? Colocar um (MAXLEN=32000) na definição da variável no contexto.


Infelizmente, no meu BP "real", esta tática não funcionou. Ao fazer isso e mandar compilar, o Caché simplesmente entrava num loop infinito e travava o Studio (pra quem não sabe, o Studio é a "IDE" do Caché/Ensemble). Não descobri o motivo ali, e como estava com pressa, contornei o bug com outra solução (ou seja, gambiarra, para os íntimos).

Entretanto, no meu BP de testes, funcionou perfeitamente... Vai entender...

sexta-feira, 25 de janeiro de 2008

Java Gateway no Ensemble e alternativas via Web Services

Já mencionei num post anterior sobre Criptografia em Caché/Ensemble, que precisei usar criptografia. Como a API do Caché/Ensemble não contemplava nem de longe o que eu precisava (e mesmo a versão mais nova - que eu não uso - contempla, apesar de ter melhorado), fui em busca de outras soluções.

Como já havia trabalhado com Java, e sabia que tinha uma boa e vasta API, pensei porque não usá-la? Vasculhando na documentação, achei o recurso do Java Gateway, do Ensemble, que nas próprias palavras da Intersystems é:

"... um jeito fácil do Ensemble interoperar com componentes Java. O Java Gateway pode instanciar objetos java externos e manipulá-los como se fossem objetos nativos do Ensemble."

Como isso acontece? Bem, o Ensemble gera alguns "proxies" para as classes Java que você quer manipular. Estes proxies são classes dentro do Ensemble, em Caché Object Script (COS), que fazem chamadas ao Java Gateway. O Java Gateway nada mais é que um conjunto de classes/objetos que fazem a comunicação entre uma JVM (máquina virtual Java) e as classes do Ensemble. Dê uma olhada na figura abaixo, que mostra como é a arquitetura dessa solução:



Se você já estudou Sistemas Distribuídos, ou tem uma noção de como EJBs trabalham, deve reconhecer o design desta solução. O que o Java Gateway faz é nada menos que um pequeno "framework" (Middleware, na realidade) de chamadas remotas. Neste caso, o cliente é dentro do Ensemble e o objeto remoto, dentro da JVM. Simplificadamente, o processo é o seguinte:

- No Ensemble, você efetua uma chamada a um método da classe proxy.
- A classe proxy serializa/transforma os parâmetros para serem enviados via TCP/IP para a instância do Java Gateway que deve estar rodando em uma JVM (não necessariamente na mesma máquina).
- As classes do Java Gateway dentro da JVM recebem uma conexão TCP/IP, identificam que é uma chamada de um método, e reconstroem os parâmetros para serem reconhecidos em Java.
- Através de reflection, fazem a chamada ao objeto Java.
- O objeto Java retorna do método, possivelmente com algum valor.
- Esse valor passa pelo processo de serialização/transformação, e é enviado via TCP/IP para o Ensemble.
- A classe proxy recebe o valor pela conexão TCP/IP, transforma o(s) valor(es) para o formato usado no Caché e retorna para quem efetuou a chamada.

Nota: o processo de formatar/transformar/serializar os dados para serem enviados via rede, e o seu inverso, na literatura de Sistemas Distribuídos é geralmente chamado de marshaling/unmarshaling.

Pois bem, a descrição de todo o processo é muito legal, muito bonita, mas a implementação de qualquer middleware não é trivial, mesmo num ambiente restrito, como é o Java Gateway (só um tipo de cliente, um tipo de objeto servidor, e outros). Por isso, a ocorrência de bugs não é de todo inesperada.

E é exatamente isso o que aconteceu: bugs, bugs e mais bugs. Utilizando o Java Gateway na mesma máquina que rodava o Ensemble (não testei com uma JVM remota), a maioria das vezes, travava tudo. Depois de uma ou duas chamadas, a máquina parava de responder. Algumas vezes, só o Ensemble, mas em outras, congelava todo o computador, de maneira que só reiniciando no dedão.

Conclusão: desisti de usar o Java Gateway. Depois de dois dias investigando sem sucesso, e sem pista do que poderia estar acontecendo, desisti (note que a classe em Java era bem simples, servindo de proxy para umas funções de criptografia, e funcionava perfeitamente bem numa JVM stand-alone). No meu caso particular, achei melhor implementar o algoritmo de criptografia nativamente em Caché Object Script.

Mas nem sempre isso é possível, ou mesmo vantajoso. Imagine implementar em COS tudo o que já foi implementado em Java ou PHP, por exemplo, em termos de bibliotecas. Impraticável.

Eis que uns tempos atrás, na lista de discussão de Caché, o Rob Tweed compartilhou uma idéia que ele teve, e que está na thread Extending Cache using PHP.

O que ele fez? Simplesmente montou uma página em PHP simples, que retornava algo do tipo:

retorno=valor

Usando a classe %Net.HttpRequest, ele estabeleceu uma conexão para a página PHP, obteve o retorno, e simplesmente fez um parsing simples de "retorno=valor" usando a função $PIECE. Um esquema bem simples.

Se você anda por dentro das últimas notícias, deve ter notado que esse esquema é bem parecido (na verdade, praticamente uma simplificação) de arquiteturas Web Services utilizando chamadas estilo REST. Implementando-se um padrão para a troca de valores (em vez de uma String customizada, um XML ou JSON), você estaria construindo a sua primeira arquitetura Web Service REST =P.

Veja que esta abordagem não se limita a utilizar PHP, podendo implementar a página web com qualquer linguagem/plataforma, passando por Java e Ruby.

E apesar de haver algumas implicações não mostradas, por exemplo, falhas na conexão, re-tentativas de acesso, segurança, etc., pelo menos você tem liberdade para cobrir quais casos se aplicam ao seu ambiente. Por exemplo, se o servidor que roda o PHP estiver na mesma máquina que o Caché/Ensemble, não precisará lidar com falhas de conexão. No caso de uso do Java Gateway, você provavelmente também lidará com essas implicações, a diferença é o nível de controle que você terá, bem menor. E claro, estará sujeito a bugs que você provavelmente não terá como de lidar/corrigir.

Ainda no espírito de Web Services, o Caché provê algum suporte ao que se convencionou chamar de estilo ou stack WS-*, que utiliza padrões mais robustos, e consequentemente mais complexos (WSDL, UDDI, WS-Security, entre outros). Para problemas simples, talvez seja pedir demais, mas a idéia permanece a mesma: extender as capacidades do Caché/Ensemble usando invocações de provedores externos.

Conclusão: para extender as capacidades do Ensemble, prefira outras alternativas, como a utilização de Web Services, do que o uso do Java Gateway. Como o Java Gateway também cria um processo separado para a execução da JVM, não existem ganhos significativos em relação ao uso de um outro servidor leve, como por exemplo, um Tomcat. Em suma, talvez o Java Gateway tenha servido há algum tempo atrás, mas hoje em dia, deve ser usado somente em caso de código legado que faça uso deste.

Em tempo: a versão nova do Ensemble parece não ter novidades em relação ao Java Gateway, pelo menos não encontrei nada olhando a documentação da nova versão.

quinta-feira, 10 de janeiro de 2008

SQL Adapter no Ensemble - probleminha

Faz tempo que não posto nada, mas hoje vi algo que me fez postar.

Até agora, usando o Ensemble da Intersystems, não havia ainda precisado usar um adaptador de entrada SQL (ou no original em inglês, um SQL Inbound Adapter). OK, estou indo muito rápido no assunto. Se você já conhece a estrutura do Ensemble, pule os dois parágrafos abaixo.

O Ensemble é uma ferramenta que serve como várias coisas, entre elas Enterprise Service Bus (ESB) (assim ele é vendido pelo pessoal de marketing, mas você deve conhecer o pessoal de marketing...), mas é mais tipicamente um Enterprise Application Integration (EAI) (uma abordagem mais realista do produto). O que ele basicamente faz é servir de "ponte" para que dois ou mais sistemas possam se comunicar. E eles podem se comunicar de diferentes maneiras, desde troca de arquivos e tabelas de banco de dados, até interfaces mais "modernas", como mensagens SOAP (com Web Services), etc... Como cada sistema tem uma interface de comunicação, o Ensemble provê adaptadores (ou Adapters no original), que são interfaces entre os diferentes tipo de protocolos/estilos de comunicação e o ambiente interno do Ensemble.

Por exemplo, você pode ter uma aplicação legada que produza somente arquivos texto como saída, e quer fazer uma espécie de "mashup" desses arquivos com dados de outro sistema, que persiste as suas informações num banco de dados, e exibir a saída dessa "mistura" num XML via Web Service. Neste caso, pode-se usar o Ensemble com um Adapter de arquivo, para conversar com a aplicação legada, um Adapter SQL para conversar com o banco de dados, e um Adapter SOAP para exibir o resultado em XML.

Voltando ao assunto principal do post...

O Adapter SQL permite que se coloque uma consulta SQL, que executa de tempos em tempos, e ele "aciona" outro componente do Ensemble (um Business Service), enviando cada linha da consulta retornada.

É uma boa idéia, mas como de costume, alguns problemas apareceram...

Pra começar, vamos ver uma parte da classe do Adapter SQL:

Class EnsLib.SQL.InboundAdapter 
Extends (Ens.InboundAdapter, EnsLib.SQL.Common)
[ ClassType = "", ProcedureBlock ]
{

/// The Base query string that will
/// be repeatedly executed to look for new rows
Property Query As %String [ Required ];

...


A propriedade Query, um %String, guarda a consulta SQL que será executada. Entretanto, não é qualquer consulta que pode ser realizada. Veja a consulta abaixo:

SELECT id_tabela, campo1, campo2 FROM grande_tabela WHERE algum_campo <= 10

Supondo que a tabela mencionada exista, bem como os campos, essa consulta deveria funcionar. Entretanto, observe o que acontece quando se tenta colocar a consulta na configuração do Adapter:


Depois de pesquisar um pouco, vi que o problema estava na classe do Adapter SQL. Note que a propriedade Query foi definida como %String. Entretanto, o que pode não ser tão visível é a restrição que isso impõe. Ao se definir uma propriedade como %String, automaticamente é estipulado também um tamanho máximo que aquela String poderá conter. Caso você não defina manualmente, o Caché/Ensemble usa um valor padrão, no caso, 50. Como a consulta acima tem mais de 50 caracteres, acontece o erro mostrado na figura.

Para reparar esse "erro", abri a classe, alterei o código fonte (só acrescentei um parâmetro na propriedade, mostrado abaixo) e recompilei. E pronto!

Class EnsLib.SQL.InboundAdapter 
Extends (Ens.InboundAdapter, EnsLib.SQL.Common)
[ ClassType = "", ProcedureBlock ]
{

/// The Base query string that will
/// be repeatedly executed to look for new rows
Property Query As %String(MAXLEN = 500) [ Required ];

...


UPDATE1: Esqueci de mencionar que isso foi feito na versão 4 do Ensemble, a que utiliza o Caché 5.2. Não tenho, nem testei a versão mais nova (2007.1) pra ver como se comporta.

UPDATE2: Aqui vai o link para um pdf sobre SQL Adapters. Apesar de ser para a versão mais nova do Ensemble, muita coisa vale para a versão mais antiga: Using SQL Adapters with Ensemble - version 2007.1.2