O projeto escolhido por mim para ilustrar o Trabalho de Formatura foi o aprimoramento do R/Gen e conversão para a linguagem Java. A escolha foi feita em conjunto com o meu supervisor no estágio (Carlos Augusto de Assis Lima) e submetida à apreciação por parte da minha supervisora no IME/USP (Profa. Ana Cristina Vieira de Melo). Os motivos que levaram a essa decisão compreendem desde a oportunidade de aprendizagem sobre a arquitetura Rerum até a familiarização com ferramentas de modelagem UML e com a tecnologia Java.
O R/Gen é um produto desenvolvido originalmente em linguagem C++ e foi criado para aumentar a produtividade em aplicações construídas a partir de uma arquitetura cliente/servidor em três camadas. Com ele é possível, a partir da modelagem de objetos feita com uma ferramenta CASE como o Rational Rose ou o ArgoUML, automatizar parte da geração de código. Os passos empregado para a obtenção desse resultado, bem como noções sobre a arquitetura em três camadas e a implementação do R/Gen são dadas nas seções subsequentes.
A arquitetura em três camadas possui a seguinte caracterização:
A camada de apresentação abrange toda a interface com o usuário. Um exemplo de interface (gráfica) é uma aplicação cliente feita com Delphi, Visual Basic ou Power Builder. A comunicação com a segunda camada é feita, normalmente, através de uma DLL.
A camada de negócio implementa as "regras de negócio" no servidor, ou seja, as tarefas que a aplicação deve realizar. Para isso, contém um conjunto de classes que instanciam objetos de negócio. Essas classes são denominadas classes BOS (Business Objects) na arquitetura Rerum. Um exemplo é a implementação de um método "recuperaNome" em uma classe "Pessoas". Esse método recebe um número de CPF e faz uma chamada a um método implementado na terceira camada, que faz a consulta ao banco de dados e retorna o nome associado ao CPF.
A camada de persistência é responsável por todas as consultas ao banco de dados e também pela persistência (gravação) dos dados dos objetos em tabelas do banco de dados. Assim, é implementada parcialmente no servidor e parcialmente no próprio banco de dados (por exemplo, na criação de stored procedures). Utiliza para isso um conjunto de classes de persistência denominadas, na arquitetura Rerum, classes COL (Collection Objects). Para cada classe BOS existe uma classe COL equivalente. Aproveitando o exemplo anterior, existiria um método "recuperaNomeBD" em uma classe COL que recebe os dados passados pelo método "recuperaNome" da classe BOS e executa a consulta propriamente dita ao banco de dados.
Algumas considerações sobre a arquitetura em três camadas
A principal característica dessa arquitetura é permitir a reutilização de componentes e a escalabilidade da aplicação, dado que cada camada é uma abstração da camada imediatamente inferior. Se, por exemplo, houver a necessidade de trocar um banco de dados SQL Server da Microsoft por um Oracle, apenas alterações na camada 3 serão necessárias.
Entretanto, a performance é uma preocupação essencial no desenvolvimento de aplicações distribuídas. Por isso, toda a arquitetura Rerum, que compreende principalmente a personalização da camada de persistência, foi implementada em linguagem C++. Por outro lado, especialmente com o advento da Internet, faz-se presente a necessidade de construir aplicações multi-plataforma. Neste contexto, apesar da degradação de performance, uma implementação Java é interessante por permitir uma rápida adaptação de um ambiente para outro. Cumpre ressaltar que, graças aos constantes aprimoramentos da máquina virtual Java e do aumento de poder de processamento dos computadores, a razão entre degradação de performance e ganhos com a portabilidade de aplicações vem diminuindo consideravelmente.
A Rerum percebeu essa necessidade e, como tem por filosofia a adoção de tecnologias emergentes que acrescentem qualidade e produtividade, tem planos para portar a arquitetura de C++ para Java. Como primeiro passo, decidiu pela conversão do R/Gen, tarefa que foi atribuída a mim e serve de base para esta monografia.
Principal aplicação da arquitetura em três camadas
Além da possibilidade de reutilização de componentes, a arquitetura em três camadas da Rerum tem uma aplicação fundamental: o mapeamento de classes em tabelas de um banco de dados relacional. Com isso, as aplicações tratam bancos de dados relacionais como se fossem orientados a objetos.
Esse mapeamento é feito na camada de persistência de acordo com algumas regras, a saber:
É essencial que haja um identificador universal único que tenha a mesma lei de formação para todas as classes e deve ser gerado automaticamente pelo sistema e pelo processo que estará instanciando o objeto pela primeira vez. Para tanto, o surrogate deve ser do tipo numérico contendo a identificação da máquina que o gerou mais data e hora do sistema no momento da geração, incluindo milissegundos. No caso de máquinas multiprocessadas, deve-se anexar um sequencial randômico para inibir a geração de números idênticos em um mesmo servidor.
A razão para a existência de um atributo surrogate prende-se à necessidade de formulação das integridades referenciais e a decomposição dos objetos complexos e heranças nas tabelas relacionais de forma padronizada e genérica.
Criando-se tipos abstratos, torna-se facilitada a independência em relação ao ambiente de implementação. Por exemplo, para um tipo char * em C++ poderia ser criado um tipo TbString usando-se typedef.
Define-se uma tabela por classe acrescentando-se os atributos identificadores de cada classe à respectiva tabela. Seja a classe A com os atributos a1 e a2 e a classe B com o atributo b1 e seja r1 o relacionamento entre elas. O mapeamento é dado em duas tabelas relacionais A [id, a1, a2] e B [id, b1, idAr1], onde id corresponde ao atributo identificador do objeto e idAr1 é a chave estrangeira que aponta para o objeto associado. A figura abaixo ilustra a situação tratada:
Define-se uma tabela por classe acrescentando-se os atributos identificadores de cada classe à respectiva tabela. A figura abaixo ilustra a situação tratada:
Define-se uma tabela por classe acrescentando-se os atributos identificadores de cada classe à respectiva tabela. Neste caso, é necessária a criação de uma tabela auxiliar para indicar as associações entre os objetos. A figura abaixo ilustra a situação tratada:
Define-se uma tabela por classe acrescentando-se os atributos identificadores de cada classe à respectiva tabela. Também é definida uma tabela específica para a classe que representa a associação e, por ter a associação o status de classe, a mesma também deve receber um atributo identificador de objetos. A figura abaixo ilustra a situação tratada:
Sob o ponto de vista de mapeamento para o ambiente relacional as auto-associações devem seguir as mesmas regras das associações comuns, levando-se em conta apenas que o mapeamento das chaves estrangeiras para as auto-associações do tipo 1:1 e 1:M devem ocorrer na própria tabela que representa a classe auto-relacionada e para a auto-associação do tipo M:N, a tabela auxiliar deve apontar duas vezes para a classe que se auto-associa. A figura abaixo ilustra tais mapeamentos:
As agregações do ponto de vista do mapeamento para o ambiente relacional seguem regras idênticas ao mapeamento das associações.
O mapeamento da herança deve guiar-se pela natureza das consultas que serão feitas sobre as classes projetadas e pelo volume de instâncias de cada classe. Identificam-se quatro formas para o mapeamento das heranças. São elas:
Descrição da camada de persistência
Esta camada tem por característica a possibilidade de estar isolada do modelo negocial, por um processo à parte, que, inclusive, pode estar sendo executado em outro servidor. A execução dos processos de persistência é encapsulada pela classe POManager, que pode ser distribuída. Para o acesso remoto à POManager tem-se POStubManager.
Toda e qualquer classe negocial que tem por propriedade ser persistente deve herdar propriedades de POPersistenceClass. POPersistenceClass possui toda a interface necessária à persistência de objetos, e é responsável pela comunicação direta com o Gerenciador de Objetos Persistentes (POManager) por intermédio de sua classe stub (POStubManager).
Uma classe negocial pode ter uma lista de outros objetos como sua componente por intermédio dos relacionamento de agregação ou associação. A agregação de uma lista de objetos deve ser dada por POPersistenceList, que é responsável pelo controle de acesso a um conjunto de objetos persistentes. A persistência em si dos objetos negociais é dada pela especialização da classe POColObj, que contém a interface genérica necessária ao mapeamento dos objetos no ambiente relacional. A inteligência necessária ao mapeamento dos objetos, bem como seu acesso, é dada pela especialização dos métodos de POColObj. Sendo assim, para cada classe negocial existe uma classe, herança de POColObj, responsável pela persistência de seus objetos. Isto ocorre "na prática" com a geração de uma classe COL para cada classe BOS, conforme dito anteriormente.
As duas próximas figuras fornecem uma visão geral da arquitetura de persistência e da organização das classes:
Com base na arquitetura descrita anteriormente, o R/Gen surge como reponsável pela geração de classes BOS e COL a partir de dois arquivos IDL (Interface Definition Language), um para definição de objetos e outro para definição de regras de persistência, além de um arquivo template que contém o "esqueleto" do código a ser gerado.
O arquivo que fornece a estrutura dos objetos chama-se ODL (Object Definition Language) e o arquivo que controla a persistência chama-se PRS (Persistence). A sintaxe de ambos é muito parecida e pode ser vista nos exemplos a seguir:
Arquivo ODL:
module INTRANET { typedef TbLong TNum; typedef TbShort TSituacaoDocumento; typedef TbString Usuario; typedef TbString GrupoUsuario; typedef TbString UseCase; typedef TbString Cenario; typedef TbString TNomeProjeto; typedef TbDate TData; typedef TbShort TNumRelease; typedef TbString TNomeItemDO; typedef TbString TVersao; typedef TbString TDescricao; typedef TbString TNome; typedef TbShort TAcesso; typedef TbLong TStatus; typedef TbShort TMetodo; typedef TbShort Tipo; typedef TbShort TParametro; typedef TbShort TLocalErro; typedef TbShort TSeveridade; typedef TbShort TpInterface; typedef TbShort TSituacaoPendencia; typedef TbShort TPrioridade; typedef TbShort TEtapa; typedef TbShort TComponente; interface Release : POObject () : persistent { attribute TNumRelease numero; relationship Projeto projeto; }; interface UsuarioEmProjeto : POObject () : persistent { relationship Usuario usuario; relationship Projeto projeto; }; interface PerfilUsuarioEmProjeto : POObject () : persistent { relationship UsuarioEmProjeto usuarioEmProjeto; relationship GrupoUsuario grupoUsuario; }; interface ItemDO : POObject () : persistent { attribute TNomeItemDO nome; attribute TVersao versao; attribute TData data; attribute TData dtUltAtualizacao; attribute TDescricao descricao; attribute TNome responsavel; relationship Usuario projetista; }; interface Projeto : ItemDO () : persistent { TStatus teste () ; }; interface Ator : ItemDO () : persistent { TStatus teste () ; }; interface Dominio : ItemDO () : persistent { TStatus teste () ; }; interface UseCase : ItemDO () : persistent { attribute TDescricao objetivo; attribute TDescricao passos; attribute TDescricao excecoes; attribute TDescricao observacoes; }; interface Regra : ItemDO () : persistent { TStatus teste (); }; interface RefCruzada : POObject () : persistent { relationship ItemDO itemDO; relationship ItemDO itemDOAssociado; }; interface Mensagem : ItemDO () : persistent { TStatus teste (); }; interface Interface : ItemDO () : persistent { attribute TDescricao localizacao; attribute TDescricao caminho; }; interface Metodo : ItemDO () : persistent { attribute TMetodo tipo; attribute TInt crud; attribute Tdescricao logica; attribute TDescricao dicaSQL; attribute TpInterface tpInterface; }; interface Parametro : ItemDO () : persistent { attribute TInt constante; attribute Tipo tipo; attribute TAcesso acesso; attribute TInt ponteiro; attribute Tparametro IO; attribute TDescricao critica; }; interface MensagemRetorno : POObject () : persistent { attribute TLocalErro local; attribute Tdescricao situacaoErro; attribute TSeveridade severidade; attribute TDescricao acao; relationship Mensagem idmensagem; relationship Método idmetodo; }; interface Classe : ItemDO () : persistent { TStatus teste (); }; interface Atividade : POObject () : persistent { attribute TNome nome; }; interface Tarefa : POObject() : persistent { attribute TPrioridade prioridade; relationship Atividade atividade; }; interface Pendencia : Tarefa() : persistent { attribute TSituacaoPendencia situacao; attribute TVersao build; attribute TVersao buildLiberacao; relationship UseCase useCase; }; interface TarefaPlanejada : Tarefa() : persistent { TStatus teste (); }; interface DetalheTarefa : POObject() : persistent { attribute TDescricao descricao; attribute TData data; relationship Tarefa tarefa; relationship Usuario executante; }; interface DetalheEvento : DetalheTarefa() : persistent { TStatus teste (); }; interface Ocorrencia : DetalheTarefa() : persistent { attribute TEtapa etapa; attribute TInt sequencia; }; interface DetalheHora : DetalheTarefa () : persistent { TStatus teste (); }; interface TipoDeDado : ItemDO () : persistent { TStatus teste (); }; interface DEC : ItemDO () : persistent { TStatus teste (); }; interface ComponenteInterface : ItemDO() : persistent { attribute TDescricao rotulo ; attribute TComponente tipo; attribute TDescricao instrConstrucao; }; interface Cenario : ItemDO () : persistent { attribute Tdescricao ativacao; attribute TDescricao regrasBasicas; }; interface PassoCenario : POObject () : persistent { attribute Tint sequencia; attribute Tdescricao evento; attribute Tint H; attribute Tdescricao acao; attribute TDescricao metodoApresentacao; attribute TDescricao metodoProcesso; relationship Cenario cenario; }; }
Arquivo PRS:
module INTRANET { interface Release:Release { classid 20; attribute numero numero:TNumRelease; relationship projeto projeto:TObjectId; }; interface UsuarioEmProjeto:UsuarioEmProjeto { classid 30; relationship usuario usuario:TObjectId; relationship projeto projeto:TObjectId; }; interface PerfilUsuarioEmProjeto:PerfilUsuarioEmProjeto { classid 40; relationship usuarioEmProjeto usuarioEmProjeto:TObjectId; relationship grupoUsuario grupoUsuario:TObjectId; }; interface ItemDO:ItemDO { classid 50; attribute nome nome:TNomeItemDO; attribute versao versao:TVersao; attribute data data:TData; attribute dtUltAtualizacao dtUltAtualizacao:TData; attribute descricao descricao:TDescricao; attribute responsavel responsavel:TNome; relationship projetista projetista:TObjectId; }; interface Projeto:ItemDO { classid 10; }; interface Ator:ItemDO { classid 60; }; interface Dominio:ItemDO { classid 70; }; interface UseCase:ItemDO { classid 80; attribute objetivo objetivo:TDescricao; attribute passos passos:TDescricao; attribute excecoes excecoes:TDescricao; attribute observacoes observacoes:TDescricao; }; interface Regra:ItemDO { classid 90; }; interface RefCruzada:RefCruzada { classid 100; relationship itemDO itemDO:TObjectId; relationship itemDOAssociado itemDOAssociado:TObjectId; }; interface Mensagem:ItemDO { classid 110; }; interface Interface:ItemDO { classid 120; attribute localizacao localizacao:TDescricao; attribute caminho caminho:TDescricao; }; interface Metodo:ItemDO { classid 130; attribute tipo tipo:TMetodo; attribute tpInterface tpInterface:TpInterface; attribute crud crud:TBoolean; attribute logica logica:TDescricao; attribute dicaSQL dicaSQL:TDescricao; }; interface Parametro:ItemDO { classid 140; attribute constante constante:TBoolean; attribute acesso acesso:TAcesso; attribute ponteiro ponteiro:TBoolean; attribute IO IO:TParametro; attribute critica critica:TDescricao; }; interface MensagemRetorno:MensagemRetorno { classid 150; attribute local local:TLocalErro; attribute situacaoErro situacaoErro:TDescricao; attribute severidade severidade:TSeveridade; attribute acao acao:TDescricao; relationship idmensagem idmensagem:TObjectId; relationship idmetodo idmetodo:TObjectId; }; interface Classe:ItemDO { classid 160; }; interface Atividade:POObject { classid 170; attribute nome nome:TNome; }; interface Tarefa:POObject { classid 180; attribute prioridade prioridade:TPrioridade; relationship atividade atividade:TObjectId; }; interface Pendencia:Tarefa { classid 190; attribute situacao situacao:TSituacaoPendencia; attribute build build:TVersao; attribute buildLiberacao buildLiberacao:TVersao; relationship useCase useCase:TObjectId; }; interface TarefaPlanejada:Tarefa { classid 200; }; interface DetalheTarefa:POObject { classid 210; attribute descricao descricao:TDescricao; attribute data data:TData; relationship tarefa tarefa:TObjectId; relationship executante executante:TObjectId; }; interface DetalheEvento:DetalheTarefa { classid 220; }; interface Ocorrencia:DetalheTarefa { classid 230; attribute etapa etapa:TEtapa; attribute sequencia sequencia:TInt; }; interface DetalheHora:DetalheTarefa { classid 240; }; interface TipoDeDado:ItemDO { classid 250; }; interface DEC:ItemDO { classid 270; }; interface ComponenteInterface:ItemDO { classid 290; attribute rotulo rotulo:TDescricao; attribute tipo tipo:TComponente; attribute instrConstrucao instrConstrucao:TDescricao; }; interface Cenario:ItemDO { classid 300; attribute ativacao ativacao:TDescricao; attribute regrasBasicas regrasBasicas:TDescricao; }; interface PassoCenario:PObject { classid 310; attribute sequencia sequencia:TInt; attribute evento evento:TDescricao; attribute H H:TInt; attribute acao acao:TDescricao; attribute metodoApresentacao metodoApresentacao:TDescricao; attribute metodoProcesso metodoProcesso:TDescricao; relationship cenario cenario:TObjectId; }; }
O arquivo ODL especifica inicialmente quais são os tipos abstratos de dados através do uso de typedef. Em seguida, lista as interfaces que determinam os atributos e relacionamentos de uma dada classe, bem como fornece informação sobre herança e tipo da classe (persistent ou transient). Por exemplo, na definição
interface Release : POObject () : persistent
foi declarada uma classe Release que é herança de POObject e é do tipo persistent, ou seja, será mapeada para um esquema relacional. Dentro dessa classe foi declarado um atributo (especificado após a palavra-chave attribute) e um relacionamento (especificado após a palavra-chave relationship). Com essa informação, o parser do R/Gen é capaz de determinar como o esquema de objetos deve ser armazenado.
O arquivo PRS contém, para cada classe do tipo persistent declarada na ODL, como deve ser o seu mapeamento no BD. Assim, na definição
interface Release:Release
são fornecidas as informações sobre o atributo e o relacionamento declarados na ODL e é definido um identificador de classe que deve ser único e é especificado após a palavra-chave classid.
Na versão Java desenvolvida por mim, a leitura e interpretação desses arquivos é feita com o auxílio de um scanner, implementado com a ajuda da ferramenta JLex (analisador léxico), e de um parser, construído segundo a sintaxe do Java Cup (analisador sintático). Na versão C++ foram utilizadas as ferramentas flex e bison para os mesmos fins.
A partir das informações colhidas dos dois arquivos de definição, o R/Gen monta um esquema da classe que deve ser gerada, mantendo uma lista de atributos, uma lista de relacionamentos e uma lista de métodos que essa classe deve conter.
Assim, o ciclo de vida da utilização do R/Gen pode ser resumido da seguinte forma:
1 - Criação do modelo de objetos segundo a especificação UML em alguma ferramenta CASE.
2 - Geração dos arquivos de definição que servirão de entrada para o gerador. Isto pode ser feito dentro das próprias ferramentas CASE usando-se linguagens de script providas por elas mesmas.
3 - Ativação do R/Gen Java pela linha de comando. Por exemplo:
java Rerum.RGen.RGen -d -g -prs intranet.odl intranet.prs ItemDO itemdo.cpp boscpp.txtNeste caso, o gerador foi ativado com a opção "-d" usada para produção de mensagens de depuração que são gravadas em um arquivo contendo data e hora. Além disso, a opção "-prs" informa que deve ser usado, além do arquivo ODL (intranet.odl), o arquivo PRS (intranet.prs). Em seguida, vem o nome da classe que deve ser gerada (ItemDO), o nome do arquivo que deve ser gerado (itemdo.cpp) e por fim o nome do template (boscpp.txt).
Assim, será produzida uma classe de negócio BOS (informação determinada pelo template) chamada ItemDO no arquivo itemdo.cpp.
Se ao invés do template boscpp.txt tivesse sido fornecido o template colcpp.txt, a classe ItemDO gerada seria a classe COL correspondente à classe BOS do exemplo anterior. O template permite ainda a geração de arquivos de cabeçalho com a utilização dos templates boshpp.txt e colhpp.txt. Um exemplo de template pode ser visto abaixo:
Arquivo template (boscpp.txt):
/******************************************************************************* ******************************************************************************** Project : Source : Class : #@BC$className Type : Author : Version : Creation Date : Description : Generated by RGen Java v1.0 - RERUM Engenharia de Sistemas Ltda. ******************************************************************************** *******************************************************************************/ #include#include #include "#@L$className.hpp" #include "#@L$relType.hpp" //Headers //BBLK#User Includes#> //EBLK#User Includes#> //Define de classe persistente - Fabrica PPDEFINE_ICLASS (#@BC$fatherName, #@BC$className , CLID#@C$className) // Constructor / Destructor //============================================================================================ #@BC$className::#@BC$className() //-------------------------------------------------------------------------------------------- //BBLK#Init Constructor#> //EBLK#Init Constructor#> { //BBLK#User Constructor Code#> //EBLK#User Constructor Code#> }; //============================================================================================ #@BC$className::#@BC$className(const #@BC$className & pObj) //-------------------------------------------------------------------------------------------- { recursiveCopy(pObj); }; //============================================================================================ #@BC$className::~#@BC$className() //-------------------------------------------------------------------------------------------- { //BBLK#User Code#> //EBLK#User Code#> }; // Equal Operator Overloading //============================================================================================ #@BC$className& #@BC$className::operator=(const #@BC$className& pObj) //-------------------------------------------------------------------------------------------- { recursiveCopy(pObj); return *this; }; // Metodos virtuais sobrescritos para persistencia polimorfica //============================================================================================ void #@BC$className::saveGuts(TFile & f) const //-------------------------------------------------------------------------------------------- { #@BC$fatherName::saveGuts(f); //BBLK#User saveGutsFile#> //EBLK#User saveGutsFile#> f << this->#@$attribName; f << this->#@$relName; }; //============================================================================================ void #@BC$className::saveGuts(Tvostream & strm) const //-------------------------------------------------------------------------------------------- { #@BC$fatherName::saveGuts(strm); //BBLK#User saveGutsStream#> //EBLK#User saveGutsStream#> strm << this->#@$attribName; strm << this->#@$relName; }; //============================================================================================ void #@BC$className::restoreGuts(TFile & f) //-------------------------------------------------------------------------------------------- { #@BC$fatherName::restoreGuts(f); //BBLK#User restoreGutsFile#> //EBLK#User restoreGutsFile#> f >> this->#@$attribName; f >> this->#@$relName; }; //============================================================================================ void #@BC$className::restoreGuts(Tvistream & strm) //-------------------------------------------------------------------------------------------- { #@BC$fatherName::restoreGuts(strm); //BBLK#User restoreGutsStream#> //EBLK#User restoreGutsStream#> strm >> this->#@$attribName; strm >> this->#@$relName; }; //============================================================================================ TStatus #@BC$className::asStream(TTVStream& p_strm) const //-------------------------------------------------------------------------------------------- { TStatus t_status= #@BC$fatherName::asStream(p_strm); p_strm << this->#@$attribName; p_strm << this->#@$relName; return t_status; }; //============================================================================================ TStatus #@BC$className::fromStream(TTVStream& p_strm) //-------------------------------------------------------------------------------------------- { TStatus t_status= #@BC$fatherName::fromStream(p_strm); p_strm >> this->#@$attribName; p_strm >> this->#@$relName; return t_status; }; //Metodos acessores publicos //BBLK#User Acessors#> //EBLK#User Acessors#> //============================================================================================ void #@BC$className::set#@BC$attribName(const #@$attribType& p#@BC$attribName) //-------------------------------------------------------------------------------------------- { this->#@$attribName = p#@BC$attribName; }; //============================================================================================ #@$attribType& #@BC$className::get#@BC$attribName (void) //-------------------------------------------------------------------------------------------- { REFRESHFIELD(#@$attribName) return this->#@$attribName; }; //============================================================================================ void #@BC$className::set#@BC$relName( #@$relType * p#@BC$relName) //-------------------------------------------------------------------------------------------- { SETPOOBJECT(p#@BC$relName, #@$relType, #@$relName) }; //============================================================================================ void #@BC$className::setRef#@BC$relName (const TObjectId& p#@BC$relName) //-------------------------------------------------------------------------------------------- { this->#@$relName=p#@BC$relName; }; //============================================================================================ #@$relType* #@BC$className::get#@BC$relName (void) //-------------------------------------------------------------------------------------------- { #@$relType *p; GETPOOBJECT(p ,#@$relType, #@$relName, CLID#@C$relType, 0) return p; }; //============================================================================================ TObjectId #@BC$className::getRef#@BC$relName (void) //-------------------------------------------------------------------------------------------- { REFRESHFIELD(#@$relName) return this->#@$relName.getRefObjectId(); }; //============================================================================================ TStatus #@BC$className::#@$methodAndParametWithOutReturn //-------------------------------------------------------------------------------------------- { TStatus t_status = PO_OK; //BBLK#User #@$methodName Code#> //EBLK#User #@$methodName Code#> return t_status; }; //============================================================================================ void #@BC$className::print (void) //-------------------------------------------------------------------------------------------- { cout << endl; #@BC$fatherName::print(); cout << "#@$attribName: " << this->#@$attribName << endl; cout << "#@$relName: " << this->getRef#@BC$relName() << endl; }; //BBLK#User Methods#> //EBLK#User Methods#>
Após a execução do R/Gen, a seguinte saída será produzida, para a linha de comando exemplificada acima:
Arquivo gerado (itemdo.cpp):
/******************************************************************************* ******************************************************************************** Project : Source : Class : ItemDO Type : Author : Version : Creation Date : Description : Generated by RGen Java v1.0 - RERUM Engenharia de Sistemas Ltda. ******************************************************************************** *******************************************************************************/ #include#include #include "itemdo.hpp" #include "usuario.hpp" //Headers //BBLK#User Includes#> //EBLK#User Includes#> //Define de classe persistente - Fabrica PPDEFINE_ICLASS (POObject, ItemDO , CLIDITEMDO) // Constructor / Destructor //============================================================================================ ItemDO::ItemDO() //-------------------------------------------------------------------------------------------- //BBLK#Init Constructor#> //EBLK#Init Constructor#> { //BBLK#User Constructor Code#> //EBLK#User Constructor Code#> }; //============================================================================================ ItemDO::ItemDO(const ItemDO & pObj) //-------------------------------------------------------------------------------------------- { recursiveCopy(pObj); }; //============================================================================================ ItemDO::~ItemDO() //-------------------------------------------------------------------------------------------- { //BBLK#User Code#> //EBLK#User Code#> }; // Equal Operator Overloading //============================================================================================ ItemDO& ItemDO::operator=(const ItemDO& pObj) //-------------------------------------------------------------------------------------------- { recursiveCopy(pObj); return *this; }; // Metodos virtuais sobrescritos para persistencia polimorfica //============================================================================================ void ItemDO::saveGuts(TFile & f) const //-------------------------------------------------------------------------------------------- { POObject::saveGuts(f); //BBLK#User saveGutsFile#> //EBLK#User saveGutsFile#> f << this->nome; f << this->versao; f << this->data; f << this->dtUltAtualizacao; f << this->descricao; f << this->responsavel; f << this->projetista; }; //============================================================================================ void ItemDO::saveGuts(Tvostream & strm) const //-------------------------------------------------------------------------------------------- { POObject::saveGuts(strm); //BBLK#User saveGutsStream#> //EBLK#User saveGutsStream#> strm << this->nome; strm << this->versao; strm << this->data; strm << this->dtUltAtualizacao; strm << this->descricao; strm << this->responsavel; strm << this->projetista; }; //============================================================================================ void ItemDO::restoreGuts(TFile & f) //-------------------------------------------------------------------------------------------- { POObject::restoreGuts(f); //BBLK#User restoreGutsFile#> //EBLK#User restoreGutsFile#> f >> this->nome; f >> this->versao; f >> this->data; f >> this->dtUltAtualizacao; f >> this->descricao; f >> this->responsavel; f >> this->projetista; }; //============================================================================================ void ItemDO::restoreGuts(Tvistream & strm) //-------------------------------------------------------------------------------------------- { POObject::restoreGuts(strm); //BBLK#User restoreGutsStream#> //EBLK#User restoreGutsStream#> strm >> this->nome; strm >> this->versao; strm >> this->data; strm >> this->dtUltAtualizacao; strm >> this->descricao; strm >> this->responsavel; strm >> this->projetista; }; //============================================================================================ TStatus ItemDO::asStream(TTVStream& p_strm) const //-------------------------------------------------------------------------------------------- { TStatus t_status= POObject::asStream(p_strm); p_strm << this->nome; p_strm << this->versao; p_strm << this->data; p_strm << this->dtUltAtualizacao; p_strm << this->descricao; p_strm << this->responsavel; p_strm << this->projetista; return t_status; }; //============================================================================================ TStatus ItemDO::fromStream(TTVStream& p_strm) //-------------------------------------------------------------------------------------------- { TStatus t_status= POObject::fromStream(p_strm); p_strm >> this->nome; p_strm >> this->versao; p_strm >> this->data; p_strm >> this->dtUltAtualizacao; p_strm >> this->descricao; p_strm >> this->responsavel; p_strm >> this->projetista; return t_status; }; //Metodos acessores publicos //BBLK#User Acessors#> //EBLK#User Acessors#> //============================================================================================ void ItemDO::setNome(const TNomeItemDO& pNome) //-------------------------------------------------------------------------------------------- { this->nome = pNome; }; //============================================================================================ TNomeItemDO& ItemDO::getNome (void) //-------------------------------------------------------------------------------------------- { REFRESHFIELD(nome) return this->nome; }; //============================================================================================ void ItemDO::setVersao(const TVersao& pVersao) //-------------------------------------------------------------------------------------------- { this->versao = pVersao; }; //============================================================================================ TVersao& ItemDO::getVersao (void) //-------------------------------------------------------------------------------------------- { REFRESHFIELD(versao) return this->versao; }; //============================================================================================ void ItemDO::setData(const TData& pData) //-------------------------------------------------------------------------------------------- { this->data = pData; }; //============================================================================================ TData& ItemDO::getData (void) //-------------------------------------------------------------------------------------------- { REFRESHFIELD(data) return this->data; }; //============================================================================================ void ItemDO::setDtUltAtualizacao(const TData& pDtUltAtualizacao) //-------------------------------------------------------------------------------------------- { this->dtUltAtualizacao = pDtUltAtualizacao; }; //============================================================================================ TData& ItemDO::getDtUltAtualizacao (void) //-------------------------------------------------------------------------------------------- { REFRESHFIELD(dtUltAtualizacao) return this->dtUltAtualizacao; }; //============================================================================================ void ItemDO::setDescricao(const TDescricao& pDescricao) //-------------------------------------------------------------------------------------------- { this->descricao = pDescricao; }; //============================================================================================ TDescricao& ItemDO::getDescricao (void) //-------------------------------------------------------------------------------------------- { REFRESHFIELD(descricao) return this->descricao; }; //============================================================================================ void ItemDO::setResponsavel(const TNome& pResponsavel) //-------------------------------------------------------------------------------------------- { this->responsavel = pResponsavel; }; //============================================================================================ TNome& ItemDO::getResponsavel (void) //-------------------------------------------------------------------------------------------- { REFRESHFIELD(responsavel) return this->responsavel; }; //============================================================================================ void ItemDO::setProjetista( Usuario * pProjetista) //-------------------------------------------------------------------------------------------- { SETPOOBJECT(pProjetista, Usuario, projetista) }; //============================================================================================ void ItemDO::setRefProjetista (const TObjectId& pProjetista) //-------------------------------------------------------------------------------------------- { this->projetista=pProjetista; }; //============================================================================================ Usuario* ItemDO::getProjetista (void) //-------------------------------------------------------------------------------------------- { Usuario *p; GETPOOBJECT(p ,Usuario, projetista, CLIDUSUARIO, 0) return p; }; //============================================================================================ TObjectId ItemDO::getRefProjetista (void) //-------------------------------------------------------------------------------------------- { REFRESHFIELD(projetista) return this->projetista.getRefObjectId(); }; //============================================================================================ void ItemDO::print (void) //-------------------------------------------------------------------------------------------- { cout << endl; POObject::print(); cout << "nome: " << this->nome << endl; cout << "versao: " << this->versao << endl; cout << "data: " << this->data << endl; cout << "dtUltAtualizacao: " << this->dtUltAtualizacao << endl; cout << "descricao: " << this->descricao << endl; cout << "responsavel: " << this->responsavel << endl; cout << "projetista: " << this->getRefProjetista() << endl; }; //BBLK#User Methods#> //EBLK#User Methods#>
As tags iniciadas com "#@$" representam os dados que devem ser inseridos de acordo com os arquivos de definição. Já os delimitadores iniciados com !BEGINLIST_ e !ENDLIST_ determinam onde devem ser escritos os atributos, relacionamentos e métodos da classe.
Dessa forma, o gerador construiu parte do código-fonte que será usado para implementação da classe. Quando o programador for escrever seu código dentro do arquivo gerado, deverá fazê-lo entre os delimitadores iniciados por //BBLK# e //EBLK#. Isto deve ser feito porque o gerador também possui uma opção de re-geração de código, acionada com a opção "-r" ao invés de "-g" na linha de comando. A utilidade dessa função se deve ao fato de que podem haver mudanças no modelo de objetos. Para que o programador não perca o código feito, o R/Gen não considera o que estiver escrito entre os delimitadores no processo de engenharia reversa do arquivo gerado.
O R/Gen Java foi todo desenvolvido segundo o paradigma da orientação a objetos e estruturado em pacotes (packages), com a seguinte configuração:
package Rerum.RGen package Rerum.RGen.Control package Rerum.RGen.Debug package Rerum.RGen.DO package Rerum.RGen.RReport package Rerum.RGen.TO
A organização das classes pode ser vista na tabela abaixo:
Nome da classe | Descrição |
Rerum.RGen.RGen | Classe principal que contem o metodo main. |
Rerum.RGen.Control.Code | Interface que agrupa os codigos de sucesso e insucesso usados. |
Rerum.RGen.Control.Control | Classe que controla a geracao do codigo. |
Rerum.RGen.Control.ParserCodeODL | Interface gerada pelo CUP que contem as constantes simbolo para a interpretacao do arquivo ODL. |
Rerum.RGen.Control.ParserCodePRS | Interface gerada pelo CUP que contem as constantes simbolo para a interpretacao do arquivo PRS. |
Rerum.RGen.Control.ParserODL | Parser gerado pelo CUP para arquivos ODL. |
Rerum.RGen.Control.ParserPRS | Parser gerado pelo CUP para arquivos PRS. |
Rerum.RGen.Control.ScannerODL | Scanner gerado pelo JLex para arquivos ODL. |
Rerum.RGen.Control.ScannerPRS | Scanner gerado pelo JLex para arquivos PRS. |
Rerum.RGen.DO.DOAttribute | Classe usada para identificacao de atributos. |
Rerum.RGen.DO.DOClass | Classe usada para identificacao de classes. |
Rerum.RGen.DO.DOItem | Classe generica usada para identificacao de objetos. |
Rerum.RGen.DO.DOMethod | Classe usada para identificacao de metodos. |
Rerum.RGen.DO.DOParameter | Classe usada para identificacao de parametros. |
Rerum.RGen.DO.DOPhysicalAttribute | Classe usada para identificacao de atributos fisicos. |
Rerum.RGen.DO.DORelationship | Classe usada para identificacao de relacionamentos. |
Rerum.RGen.DO.DOSchema | Classe usada para controlar as informacoes sobre o esquema de objetos. |
Rerum.RGen.DO.DOSimpleType | Classe usada para identificacao de tipos simples. |
Rerum.RGen.DO.DOType | Classe usada para identificacao de tipos. |
Rerum.RGen.DO.POAccessControl | Classe usada para controle de acesso. |
Rerum.RGen.RReport.Island | Classe usada para armazenar trechos de relatorios. |
Rerum.RGen.RReport.RBaseItem | Classe base para a geracao de relatorios. |
Rerum.RGen.RReport.RCode | Interface que contem as constantes usadas em relatorios. |
Rerum.RGen.RReport.RField | Classe que implementa definicoes de campos de relatorios. |
Rerum.RGen.RReport.RList | Classe que implementa definicoes de listas em relatorio. |
Rerum.RGen.RReport.RReport | Classe que controla a geracao de relatorios. |
Rerum.RGen.TO.TOList | Classe utilizada para implementacao de uma abstracao de lista. |
Rerum.RGen.TO.TOString | Classe utilizada para implementacao de uma abstracao de string. |
A documentação completa e detalhada foi gerada com a ferramenta javadoc e pode ser vista neste link.
O cronograma inicial do projeto previa a implementação de mais funcionalidades na versão C++ do R/Gen, bem como a resolução de alguns problemas, especialmente no processo de engenharia reversa. Entretanto, a direção da empresa optou pela conversão do R/Gen para Java e as modificações necessárias serão feitas sobre a versão implementada por mim. Assim, o cronograma de atividades realizadas foi:
Julho
- Fase 1 - Análise e projeto
Estudo da versão C++ do R/Gen, incluindo diagramas UML.
Estudo da arquitetura em 3 camadas, em especial a implementação da Rerum para a camada de persistência.
Definição da estrutura de pacotes da versão Java.
Agosto
- Fase 2 - Implementação da versão Java
Estudo sobre o funcionamento das ferramentas JLex e Java Cup e da sintaxe dos arquivos de entrada para essas ferramentas.
Início da implementação (pacote Rerum.RGen.TO).
Setembro
- Fase 2 - Implementação da versão Java
Implementação das classes do pacote Rerum.RGen.DO.
Outubro
- Fase 2 - Implementação da versão Java
Implementação das classes do pacote Rerum.RGen.RReport.
Novembro
- Fase 2 - Implementação da versão Java
Implementação das classes do pacote Rerum.RGen.Control e Rerum.RGen.RGen.
Dezembro
- Fase 3 - Testes e documentação
Testes utilizando aplicações reais (como a Intranet de desenvolvimento exemplificada anteriormente).
Documentação do código-fonte, geração com a ferramenta javadoc e criação de um pequeno manual sobre o R/Gen para usuários e desenvolvedores de futuras versões.
O processo de desenvolvimento foi totalmente solitário, ou seja, excetuando-se a fase de análise, eu implementei todo o R/Gen Java sozinho. Minha única obrigação com o meu supervisor era informá-lo periodicamente sobre o andamento das atividades através da confecção semanal do RSP (Relatório de Situação de Projeto), que é um documento onde o gerente de um projeto define as metas a serem alcançadas na próxima semana e as metas que não puderam ser alcançadas na semana anterior. Caso houvesse algum problema ou atraso, o supervisor ficaria responsável por encontrar um solução. Felizmente, não houve nenhum atraso durante a produção.
Entretanto, dois aspectos devem ser ressaltados. Um deles é que eu trabalhei sobre a versão C++ do R/Gen. Embora não seja trivial converter uma aplicação de complexidade média como o R/Gen, meu trabalho ficou bastante facilitado dado que eu tinha uma versão próxima ao desejado à minha disposição. Outro fator de destaque é a definição "folgada" do cronograma, ou seja, havia tempo de sobra para cumprir as metas estabelecidas. Isso foi feito pois, entre agosto e outubro, fui designado para participar de um outro projeto simultaneamente, na empresa TecBan (Tecnologia Bancária), o que já foi previsto em julho pelo meu supervisor no estágio. Minha participação foi na documentação de use cases para o projeto de reestruturação do CCA, um software intenro utilizado pela TecBan para controle dos caixas eletrônicos da rede Banco 24 Horas.
As principais dificuldade encontradas no desenvolvimento do R/Gen Java ocorreram na fase de criação dos arquivos de sintaxe do JLex e Java Cup. Foi necessário isolar o problema em partes menores (por exemplo, fazendo parser de arquivos pequenos e analisando o comportamento resultante). Isto se deveu ao fato de não existir uma boa documentação sobre as ferramentas.
Um outro problema que demandou certo tempo para ser resolvido foi na geração do relatório, que nada mais é que um buffer que contém todo o código gerado e que deve ser escrito no arquivo de saída. O que acontecia era que apenas parte desse buffer era escrito no arquivo, embora a impressão do buffer na saída padrão mostrasse que ele estava correto. A solução foi utilizar um método flush() que forçava a gravação do buffer no arquivo.
1) Programando em Java Ken Arnold e James Gosling Makron Books 2) Java In A Nutshell David Flanagan O' Reilly 3) Guia de Referência (Arquiteturas de Aplicação Web) Documento interno Rerum Tecnologia 4) Projeto e Implementação de Camadas de Persistência Documento interno Rerum Tecnologia 5) Mapping Objects To Relational Databases Scott W. Ambler Ronin International 6) The Design Of A Robust Persistence Layer For Relational Databases Scott W. Ambler Ronin International 7) Foundations Of Object Relational Mapping Mark L. Fusell White paper 8) Object-Oriented Analysis And Design With Applications Grady Booch Benjamin/Cummings