sexta-feira, 23 de julho de 2010

Conversão Semi-automática de Classes Java para AS

Esses dias eu estava brincando com uma aplicação e me entendiou o fato de ter que ficar duplicando as classes VO do Java para o Flex na mão. Comecei a pesquisar e vi a solução GAS do Granite. Contudo achei a geração das classes em ActionScript muito verbosa. Eu queria algo mais simples, que o nome da classe, o alias, e os atributos fossem copiados. Acabei não encontrando algo simples assim, aí acabei fazendo uma solução caseira rapidamente usando o próprio Flex. Não me importei com testes unitários, mas o fato é que a aplicação funcionou bem para as classes que eu estava fazendo. Para usar, basta colar o código da classe Java no Browser. Estou postando também o código fonte, apesar  de ele ser bem porquinho J, para quem kiser adaptar a solução para seus propósitos.

public class J2ASConverter {
         public function convertToAS(javaClass:String):String {
              var str:String='[RemoteClass(alias="';
              str+=buildAlias(javaClass)+'")]';
              str+="\npublic class "+getClassName(javaClass)+"{";
              str+=buildFields(javaClass);
              return str+"\n}";
         }

         private function buildFields(javaClass:String):String {
              var fieldsAS:String="";
              for each(var field:Object in getFields(javaClass)) {
                   fieldsAS+="\n\tpublic var "+field.fieldName+":"+field.fieldType+";";
              }
              return fieldsAS;
         }

         private function getFields(javaClass:String):Array {
              var classSearchIndex:int=javaClass.indexOf("class");
              if(classSearchIndex>=0) {
                   do {
                        ++classSearchIndex;
                   } while(classSearchIndex"{");
                   ++classSearchIndex;
                   var fields:Array=[];
                   var abreChave:int=0;
                   while(classSearchIndex
                        if(abreChave==0) {
                            var line:String="";
                             while(classSearchIndex";") {
                                 line+=javaClass.charAt(classSearchIndex);
                                 ++classSearchIndex;
                            }
                            line=removePossibleJavaAnnotations(line);
                            if(isField(line)) {
                                 if(line.indexOf("}")==-1) {
                                      fields.push(extractTypeAndName(line));
                                 }
                            } else {
                                 ++abreChave;
                            }
                        } else if(javaClass.charAt(classSearchIndex)=="}") {
                            --abreChave;
                        } else if(javaClass.charAt(classSearchIndex)=="{") {
                            ++abreChave;
                        }
                        ++classSearchIndex;
                   }
                   return fields;
              }
              return [];
         }

         private function removePossibleJavaAnnotations(line:String):String {
              var searchIndex:int=line.indexOf("@");
              if(searchIndex!=-1) {
                   ++searchIndex;
                   while(searchIndex"(") {
                        ++searchIndex;
                   }
                   var parentesiNumber:int=1;
                   ++searchIndex;
                   while(searchIndex
                        if(line.charAt(searchIndex)=="(") {
                            ++parentesiNumber;
                        } else if(line.charAt(searchIndex)==")") {
                            --parentesiNumber;
                        }
                        ++searchIndex;
                   }
                   if(searchIndex
                        return line.substring(++searchIndex);
              }
              return line;
         }

         private function extractTypeAndName(line:String):Object {
              line=line.split("=")[0];
              var searchIndex:int=line.length-1;
              while(searchIndex!=-1&&line.charAt(searchIndex)==" ") {
                   --searchIndex;
              }
              var javaFieldName:String="";
              while(searchIndex!=-1&&line.charAt(searchIndex)!=" ") {
                   javaFieldName=line.charAt(searchIndex)+javaFieldName;
                   --searchIndex;
              }
              while(searchIndex!=-1&&line.charAt(searchIndex)==" ") {
                   --searchIndex;
              }
              var javaFieldType:String="";
              while(searchIndex!=-1&&line.charAt(searchIndex)!=" ") {
                   javaFieldType=line.charAt(searchIndex)+javaFieldType;
                   --searchIndex;
              }
              return { fieldType:java2ASType(javaFieldType),fieldName:javaFieldName };
         }

         private function java2ASType(javaType:String):String {
              javaType=javaType.replace("<.*>","");
              javaType=javaType.replace(" ","");
              if(javaType=="Set"||javaType=="List") {
                   return "ArrayCollection";
              } else if(javaType=="int"||javaType=="Integer"||javaType=="short"||javaType=="Short"||javaType=="long"||javaType=="Long"||javaType=="float"||javaType=="Float"||javaType=="double"||javaType=="Double") {
                   return "Number";
              } else if(javaType=="Calendar") {
return "Date";
}
              return javaType;
         }

         private function isField(line:String):Boolean {
              var firstLinePart:String=line.split("=")[0];
              return firstLinePart.indexOf("{")==-1;
         }

         private function buildAlias(javaClass:String):String {
              var packageIndex:int=javaClass.indexOf("package");
              var packageNameBegin:int=0;
              var alias:String="";
              if(packageIndex>=0) {
                   for(packageNameBegin=packageIndex+7;javaClass.charAt(packageNameBegin)==" ";++packageNameBegin) {
                   }
                   if(javaClass.indexOf(";")>packageNameBegin) {
                        var char:String=javaClass.charAt(packageNameBegin);
                        while(char!=";") {
                            alias+=char;
                            char=javaClass.charAt(++packageNameBegin);
                        }
                        alias+="."+getClassName(javaClass);
                   }
              }
              return alias;
         }

         private function getClassName(javaClass:String):String {
              var classWordIndex:int=javaClass.indexOf("class");
              if(classWordIndex>=0) {
                   classWordIndex+=5;
                   while(javaClass.charAt(classWordIndex)==" ") {
                        ++classWordIndex;
                   }
                   var className:String="";
                   do {
                        className+=javaClass.charAt(classWordIndex);
                        ++classWordIndex;
                   } while(classWordIndex" "&&javaClass.charAt(classWordIndex)!="{");
                   return className;
              }
              return "";
         }
     }

Se a classe completa do Java for utilizada, incluindo a linha com o package, a mini-aplicação monta o metadado RemoteClass com o respectivo alias.
Espero que a aplicação ajude mais alguém.

sexta-feira, 2 de julho de 2010

Google App Engine + Adobe Flex - Parte 3

Olá pessoal, vou continuar com a epopéia GAE + FLEX focando um pouco do Spring.

No trabalho aprendi a usar o Spring para basicamente 4 coisas:
  1. Injeção de Dependências
  2. Segurança
  3. Transações
  4. Comunicação com Flex via BlazeDS
Como já citei em posts anteriores, eu simplesmente baixei o plugin do GAE e fui tentar colocar tudo isso para funcionar com o que eu já sabia até então. Depois de trocar o BlazeDS pelo GraniteDS (Post 2) e seguir o tutorial desse último framework, consegui fazer a comunicação remota com o Flex de forma muito parecida de como fazia com o Blaze, mudando apenas a anotação para exportar os serviços de acesso remoto.
Foi então que surgiu a primeira dor de cabeça: a maioria dos meus serviços eram anotados com @Transaction, já que eu queria que o Spring se encarregasse das transações para mim. Contudo, recebi algumas exceções em alguns desses métodos anotados. Pesquisei um pouco e achei a explicação na própria documentação do GAE sobre transações. 
As transações do serviço Google são limitadas a entidades pertencentes a um mesmo grupo. Para exemplificar, eu estava tentando salvar todos Estados Brasileiros em um serviço, mas ele não deixava porque cada estado pertencia a um grupo diferente. Isso me levou a não mais utilizar o controle de transação do Spring, então retirei todas dependências desse controle de transação, bem como da parte de controle de ORM do Spring. Passei então a utilizar o Próprio PersistenceManager do JDO para realizar as operações de persistência.
Feito isso, o servidor passou a funcionar, executando minhas chamadas remotas com sucesso, inclusive utilizando a segurança de ROLES básica fornecida pelo Spring Security, segurança essa suportada pelo Granite. Passei a ler a documentação do Spring Security sobre segurança para aprender a fazer casos mais granulares de segurança, como por exemplo, deixar um administrador de uma loja alterar somente os recursos de sua loja e não de outras. Não sei se por preguiça minha ler ou se porque realmente a documentação do Spring Security é extensa, eu definitivamente não consegui encontrar uma resposta simples para esse problema. Se fosse só para utilizar o Spring Security para autenticação básica de Roles, a verdade é que seria melhor eu fazer tudo na mão. Então também removi a parte de segurança do Framework da minha aplicação.
Com as eliminação dos dois módulos do Spring, eu efetivamente usaria o framework apenas para injeção de dependências e pretendia inclusive gerar Wrapers das interfaces do GAE que são acessadas por métodos estáticos, como por exemplo o UserService e o PersistenceManagerFactory, de forma a facilitar a confecção de meus testes unitários. Na hora de executar alguns testes com chamadas remotas no servidor, percebi pelos logs que a contagem de CPU estava muito alta, isso porque por várias vezes ele subia o contexto do Spring.
Constatado o problema, mais uma vez fui pesquisar para encontrar uma resposta. Em alguns fóruns encontrei o X da questão. Quando sua aplicação não recebe requisições por certo tempo, o GAE simplesmente "desativa" sua aplicação, inicializando a mesma só quando uma nova requisição ocorre. Isso até pode fazer sentido em termos de uso de recursos, mas uma vez que a inicialização do contexto do Spring é custosa em termos de CPU, eu acabaria excedendo minha cota desse recurso em pouco tempo.
Uma solução alternativa que encontrei na internet foi utilizar "tarefas agendadas" para chamar um serviço que não fazia nada, só para "enganar" o GAE e fazê-lo não desativar minha App por inatividade. Pessoalmente achei isso uma grande gambiarra e tomei uma decisão radical: eliminei todo o Spring do meu projeto. Sei que os amantes do framework ao lerem isso vão achar loucura, mas isso teve um ponto extremamente positivo: acabei criando uma arquitetura própria que não só resolveu o problema de performance na inicialização, como também permitiu com que a configuração do GraniteDS fosse extremamente pequena no Java e a configuração no Flex fosse nula. Sim, isso mesmo, configuração nula no Flex! Bom, nula é muito forte, mas definitivamente bem transparente ao programador...rsPretendo organizar essa parte de comunicação do código em um projeto open source separado para quem sabe tornar a vida dos programadores em Flex mais simples. Nessa semana irei dar um curso de flex para uns amigos do trabalho e pretendo documentar as idéias básica do protótipo aqui nesse blog. Então aguardem as cenas dos próximos capítulos...

Google App Engine + Adobe Flex - Parte 2

Seguindo a série GAE + Flex, vou falar um pouco dos problemas que encontrei ligados ao Flex, mais especificamente à comunicação remota.
Inicialmente eu decidi utilizar o BlazeDS por ser a versão fornecida pela própria Adobe, mesmo ele constando na documentação do GAE como não suportado. Como tinha acabado de sair a versão do Spring Flex (ver referência a ele no post anterior) junto com a então nova versão do Spring (3.0.2) fui tentar utilizar para ver se a nova versão já corrigia o problema de compatibilidade.
 Localmente tudo funcionou direitinho, mas aí aprendi uma grande lição, que vale para vários outros casos, quando se usa o GAE: só porque a aplicação roda localmente, não quer dizer que ela funcione no servidor.
Na própria documentação havia um workaround para o problema, mas ela envolvia alterar o fonte do Blaze e recompilar o jar. Por questão pessoal não gosto de soluções que eu tenha que alterar o fonte de um framework, afinal, e quando a versão mudar, quem garante que a alteração será compatível? Vou ter que ficar dando manutenção em framework para fazer as coisas funcionarem? Vou depender de alguma pessoa para consertar o problema?
Uma vez que não fiquei feliz com a alternativa, fui pesquisar para saber se por acaso a Adobe pretendia resolver o problema e encontrei a questão já levantada no Jira deles. Ou seja, simplesmente eles fecharam a issue, o que pra mim indica que não pretendem consertar tão cedo. Apenas deixei meu comentário no Jira deles e foi pesquisar outras alternativas.
A solução veio da própria documentação supracitada do GAE: o GraniteDS. Ele é compatível com o GAE e  para minha surpresa, a documentação dele é bem simples, até mais que a do Blaze, incluindo ainda documentação sobre como integrar o mesmo com Spring e com EJB. Fiz o teste para chamadas remotas e funcionou direitinho. Estava escolhida então minha tecnologia para comunicação Java - Flex.
 Outra grata surpresa do Granite é que ele diz que resolve (não testei para ver) o problema de LazyInitializationException lançada pelo Hibernate, que ocorre quando se serializa objetos de domínio diretamente utilizando o BlazeDS. Quanto a esse problema de Lazy Loading, prentendo abordar em um post futuro.
Então o resumo da ópera é que se você pretende usar FLEX + GAE, GraniteDS é uma solução simples e direta.

 Espero que a dica ajude e aqueles que se interessem por Flex, GAE ou GraniteDS e entrem em contato para que possamos trocar experiências.

 Até o próximo post...