sexta-feira, 23 de agosto de 2013

Como criar uma cópia de um objeto persistente de forma automática (JPA, HIBERNATE, ETC...)

Abaixo está o link para baixar o componente 


Download:  LINK

 Há um tempo atrás, em 2011, precisei implementar em um projeto o conceito de versionamento de registros no banco. A medida que o usuário alterava algumas entidades, o sistema deveria na verdade gerar uma copia do objeto original contendo as alterações realizadas, incrementando o número de versão do registro e efetuar um novo insert no banco ao invés de um update. Isso manteria as versões anteriores a alteração realizada. Ontem, um amigo aqui do trabalho me solicitou ajuda com o mesmo problema. E eu vi que mais pessoas poderiam estar precisando disso. Então resolvi postar aqui para ajudar a quem necessite.

O problema 


 Inicialmente pensasse logo que é algo simples de se fazer. Algo como 'apenas seto o id para null e mando fazer um novo insert'. Mas não é bem assim. Pensar dessa forma é dar um tiro no pé. Isso vai gerar inconsistência na base e problemas no sistema. Porque? O problema é que ao fazermos uma copia fiel do objeto persistente nós queremos todo o contexto persistente do mesmo. Isso quer dizer que precisamos de todos os seus relacionamentos replicados também. E todos os relacionamentos dos relacioanamentos, dos relacionamento, dos relacionamentos, enfim.... precisamos replicar toda a árvore de relacionamentos do objeto original. E quando falo de relacionamentos são de todos os tipos, 1 x N, N x N, 1 x 1, enfim... Exemplo do problema. Para exemplificar, vou mostrar um exemplo com 1 x N, mas que segue a mesma necessidade, problema e solução para os outros tipos de relacionamento. Imagine que temos 3 tabelas e suas respectivas entidades. Pessoa, Carro e Multas. Onde os relacionamentos são como abaixo: Pessoa 1 x N Carro Carro 1 x N Multa O problema é que o lado N sempre tem uma referência ao lado 1, pois esse lado 1 é a chave estrangeira ao qual o relacionamento pertence. Então Carro tem uma propriedade Pessoa, que é o dono do carro. E Multa tem uma propriedade Carro, que é o carro que tem a multa. Então imagine as seguintes instancias.


Observe que a pessoa é proprietaria de dois carros. Cada carro tem uma referência para a pessoa de id=1. Cada carro tem sua lista de multas. Onde cada multa tem uma referência que para o carro ao qual a multa pertence. Dito isto, o problema em simplesmente fazer uma copia da pessoa copiando as propriedades do original para o novo é que os carros contém a referência a pessoa original, bem como suas multas contem uma referencia a seu respectivo carro original. Desta forma, apenas fazer a copia e setar null no ID para que o Hibernate ou JPA faça um novo insert, vai causa inconsistência no banco, pois o relacionamento entre a pessoa e os carros, bem como carros e as multas, continua apontando para suas respetivas referências originais, e não para a copia. Vou dar um exemplo de dois casos que podem acontecer.

  - Caso 1

 Se nós fizermos uma cópia da pessoa original, copiarmos as propriedades do original para a copia e setarmos null no id da copia de modo a forçar o hibernate ou jpa a fazer um novo insert, vamos ver como a figura ficará. Abaixo, coloquei a mesma figura anterior só que marquei a instancia original de pessoa com a letra X, para que diferenciemos a original da copia, quando a fizermos. Então, observe que a pessoa abaixo, é a original e é a instancia X.


Observe que os carros apontam para a pessoa (proprietário) correta e que ao qual realmente pertencem. Instancia X. Agora se fizermos uma copia e criarmos outra instancia de Pessoa, teremos a instância Y, essa instância é a copia e nós trasnferiremos tudo da original para ela. Setando o id como null, queremos forçar ao mecanismo de persistência adotado, que se faça um novo insert. Então passando as propriedades da original para a nova instancia, teremos a instancia Y de pessoa como abaixo:


Observe que agora temos a instância Y e pegamos todas as propriedades da instancia X e passamos para a Y. Observe que os carros são os mesmos da instância X, então eles contém como referência de proprietário a pessoa original (instância X) e suas multas são as mesmas do original, e apontam para seus respectivos carros da pessoa X, e não da Y. Na verdade o correto seria termos novas instâncias dos carros, apontando para a copia da pessoa, a instancia Y, e novas instâncias de multas apontando para estes novos carros e por ai sucessivamente. Persistir a pessoa da forma como está na imagem, se não ocasionar em nenhum erro na validação dos dados por parte do framework de persistencia adotado, ocasionaria no insert apenas de uma nova pessoa, só que essa nova pessoa não teria nenhum carro, pois não existe relacionamento com nenhum carro. Pois os carros apontam para a instancia X e já foram persistidos. O mesmo aconteceria com as multas, que já foram persistidas e apontam para os carros da pessoa original X.

  - Caso 2

 A outra forma que você poderia pensar em fazer, seria ao invés de criar uma nova instância da Pessao X, usar a mesma instância e apenas setar null no ID. Como os outros objetos carro se referem a pessoa por referência, então mudar o id da instancia X, refletiria na mudança em todos os proprietarios dos carros. Então no momento do insert, eles apontariam para o carro correto.

 Ai é que você se engana. Você se esquece que os carros e as multas possuem seus próprios id´s já setados,como mostrado na imagem, pois já foram persistidos e foram recuperados junto com a pessoa para que fosse feito o versionamento. Fazer um update em pessoa dessa forma resultaria em um update, pois eles ja possuem id e ja existem na base, nos objetos Carro alterando a FK (referencia) do proprietario. Ou seja, os carros deixariam de ser do proprietario X para serem da nova pessoa inserida no banco. Então o relacionamento da pessoa original seria perdido.

 Outro problema seria que um objeto recuperado do banco com hibernate ou jpa, eles vem com um PROXY, que gerenciam todo o acesso a classe real. São estes proxies que fazer e tornam possível por exemplo, o mecanimso de LAZY, que é a consulta ao banco do objetos que compoem a entidade, apenas quando necessário, quando chamado o método get. Isso é o proxy que faz pois ele intercepta a chamada ao metodo. Isso é transparente para você. Este mesmo proxy garante que a primary key (ID) não seja alterado. Então uma tentativa de setar null em um objeto recuperado da base resultará numa exceção. Remover o proxy é possível mas isso não resolverá o problema.

 Para este pequeno exemplo, o correto algoritmo seria o algoritmo abaixo:

1 - Criar uma nova instancia de pessoa
2 - Copiar todas as propriedades menos o ID a lista de carros
3 - Criar uma nova instancia de cada carro existente na lista original de carros da pessoa original
4 - Copiar cada propriedade de um carro para o outro, menos o ID e a lista de multas
5 - Criar uma nova instancia de cada multa existente em cada carro na lista original de multas do carro original
6 - Setar a referência ao carro ao qual a multa pertence como a nova copia do carro original
7 - Adicionar as copias das multas do carro original a cada copia de cada carro criado
8 - Adicionar as copias dos carros criados a nova pessoa criada
9 - Fazer o insert

 Isso resultará em:
- 1 insert de uma nova pessoa
- 2 inserts de novos carros apontando para esta nova pessoa
- 4 inserts de novas multas. Sendo 2 multas apontando para o primeiro carro e 2 multas para o segundo 

Observe quanta coisa você teria que fazer na mão e só para replicar o contexto persistente dessa simples classe pessoa. E mesmo essa simples classe, qualquer alteração nos relacionamentos, a adição de mais relacionamentos 1 x N ou N x N ou 1 x 1, a remoção de outros, etc.. resultaria na mudança e na revisão de todo este algoritmo.

 Leve em consideração também que o algoritmo muda de acordo com o tipo de relacionamento. De acordo com o mapeamento realizado na classe. Por exemplo, em um relacionamento 1 x 1, a FK pode estar mapeada na própria classe ou no outro lado do relacionamento. Se for do outro lado, o algoritmo tem que mudar a referencia da instancia do outro lado do relacionamento, para a nova instancia (copia) criada. Se for N x N, o algoritmo é outro, e por ai vai.

 Agora imagine ter que manter isso. Agora imagine que você precise disso em várias entidades de seus sistema. Ou até mesmo que um requisito seja versionar todas as entidades principais. Imagine você tendo que fazer isso para todos os contextos persistentes, que mudam de uma classe para outra, pois os relacionamentos são diferentes de uma classe para outra, bem como seus tipos. Imagine manter tudo isso a cada mudança nesses relacionamentos. Vai fazer na mão?

A solução 

 Pesquisei na época que precisei disto e não encontrei nada que me desse suporte a fazer isso. Na verdade encontrei foram posts de pessoas com o mesmo problema e sempre batiam neste problemas na consistência dos relacionamentos. Mas nunca resultavam em um solução. Muito menos automatizada. Então decidi por mim mesmo fazer, na época em 2011. Comecei a pensar em cada algoritmo de replicação de acordo com cada tipo de relacionamento, de acordo com cada casa que pude vislumbrar. Então criei um "carinha" que analisa os mapeamentos com base nas anotações do javax.persistence como @Entity, @OneToMany, @OneToOne, etc... e com base nas informações de meta dados das classes das entidades envolvidas decide o que fazer e aplica a estrategia correta para gerar a replicação de cada atributo corretamente, gerando assim uma copia completa e correta de todo o contexto persistente, de toda a árvore de relacionamentos de uma entidade persistente. Esse recurso é muito fácil de usar e é transparente para qualquer um que precise.

Estou disponibilizando as classes e os fontes das mesmas de forma livre para todos que precisarem. Da mesma forma, peço favor de manter a autoria e os devidos créditos.

 Abaixo vai um exemplo e algumas instruções simples de uso. No final tem o link para baixar o componente.

Exemplo:

Pessoa pessoa = pessoaDAO.find(1);
PersistenceCloner cloner = new PersistenceCloner(pessoa); 
Pessoa pessoaCopia = cloner.generateCopyToPersist(); 

 Essas três linhas de código fazem o que queremos. Obtemos uma pessoa da base. Criamos um cloner para Pessoa. Chamamos o método generateCopyToPersist para obtermos uma copia da entidade persistente pronta se chamar o insert. Um recurso que implementei permite anotar as propriedades que serão ignorada no momento da transferência das propriedades do objeto original para a copia. O que isso quer dizer? Que podemos marcar uma propriedade que não será gerada uma copia e transferida para nova instancia criada. Digamos que nesta classe pessoa nós tenhamos um campo chamado versãoAnterior e nós não queremos que este campo seja copiado de uma versão para outra, logicamente pq cada versão apontará para uma versão anterior diferente.

Para estes casos, eu criei a anotação @NoPersistenceCopy. Essa anotação é usada para marcar propriedades que devem ser ignorada no momento da transferência das propriedades do objeto original para a copia. Ela permite que isso seja feito de duas formas. Setando simplesmente null na propriedade no objeto copia, ou forçando a copia do mesmo objeto do original para o copia mesmo que a copia seja necessária devido aos mapeamentos. 

 Exemplo1: 

 @NoPersistenceCopy 
private String versaoAnterior; 

 Isso fará com que independente do mapeamento nessa propriedade, o objeto setado nessa propriedade no objeto original no momento da copia, não passe pelo processo de copia. Ou seja, o objeto não será copiado(entenda como copia, uma nova instancia identica a original). Ao invés disso, o componente pegará o valor da propriedade no objeto original e setará como valor na mesma propriedade no objeto copia. Então a propriedade no objeto copia terá a mesma referência que a mesma propriedade no objeto original tem. 

 Exemplo2: 

@NoPersistenceCopy(setNull = true) 
private String versaoAnterior;

Isso fará com que a propriedade seja ignorada da mesma forma que foi descrita no exemplo anterior mas ao invés de setar o mesmo valor da propriedade do objeto original, o componente setará 'null' na propriedade no objeto copia. 


Resumo 

 Para utilizar, basta criar um PersistenceCloner passando a referência do objeto original que se deseja copiar, e chamar o método generateCopyToPersist. Pronto, a copia esta pronta e toda a complexidade de se fazer isto está abstraída para você.

Abaixo está o link para baixar o componente


DownloadLINK

 Qualquer dúvida, bug, sugestão ou agradecimento, postar comentário aqui os me enviar email pelo victorlindberg713@gmail.com. Abraço, espero ter ajudado.

ATUALIZAÇÃO DESSE POST EM 24/02/2023.

Pessoal, há muitos anos eu parei de evoluir o lindberg-framework. Pelo menos essa versão que continha essa solução de copia de contexto persistente.  Com o fim do google code, acabou que não levei todo o projeto pro git-hub. Mas essa parte de cópia de contexto persistente eu coloquei lá sim, mas com outro nome. Eu escrevi essa solução há mais de 10 anos atrás e até hoje pessoas passam pelo mesmo problema e sei que isso pode ajudar. Então, estou atualizando esse post aqui colocando o link atual da lib no git-hub. Abraço e espero ter ajudado a resolver o seu problema.

terça-feira, 28 de fevereiro de 2012

Como manter o estado de um ManagedBean entre páginas diferentes (conversação), somente na mesma página (escopo de view) com JSF 2 + Spring 3?

A muito tempo que venho vendo que muitos desenvolvedores (muitos mesmo) que trabalham com JSF tem a necessidade de manter o estado de um managedbean (manter o bean vivo) enquanto alguma página estiver usando o mesmo (conversação) e também enquanto apenas uma mesma página estiver acessando o managedbean (escopo de view). É só da uma procurada no google que se vê milhares de posts de dúvidas.

No JSF 1 tinhamos o richfaces 3 que nos fornecia a tag <keepalive> que promovia justamente a manutenção do managedbean enquanto alguma página com um keepalive para esse managedbean estivesse acessando este bean. Com o JSF 2 o richfaces 3 não tem total compatibilidade. Uma das coisas que param de funcionar é justamente o keepalive. Então veio o richfaces 4 que da suporte ao JSF 2. Mas o detalhe é que nesta versão o keepalive foi removido pelo motivo do JSF 2 já prover o ViewScope nativo. Mas esqueceram que nós desenvolvedores usávamos também o keepalive para manter conversações entre mais de uma página que precisam acessar o mesmo managedbean e precisam manter o estado do mesmo. Resultado, uma enxurrada de dúvidas em posts pedindo socorro para resolver o problema.

Eu me deparei com o mesmo problema e fui em busca de soluções.

Este post trata sobre como fazer isto usando no JSF 2 usando o Spring 3 de uma forma fácil e transparente usando o módulo myview do lindbergframework.

Para você que usa o CDI do JEE 6 ao invés do spring, não se desespere. Para resolver o mesmo problema, existe uma extensão do CDI do myfaces chamada CODI que prover o ViewAccessScoped que nos permite manter conversações enquanto alguma página estiver usando o managedbean. Para tal veja este post tratando sobre o assunto e veja que explico como usar o CODI tanto com o maven quanto em projetos não maven.

Vamos ao que interessa.

Eu pesquisei, pesquisei, quebrei a cabeça e estudei como fazer para colocar o escopo de view e de conversação em managedbean´s gerenciados pelo spring integrado com o jsf.

Então criei um módulo do lindbergframework que é independente do resto do framework e trará de agora em diante várias utilidades para a camada de visão. A release 1.0 que é que estou abordando neste post prover como recurso justamente todo a infraestrutura e configuração mais complicada e pesada para colocar estes escopos para funcionar.

Então vamos ao que interessa.

Para usar o myview e colocar os escopos para funcionar é muito simples.

Basta colocar o myview em seu projeto a partir do maven ou baixando diretamente o zip com o jar do myview e suas dependencias incluindo spring (se seu projeto ja possui as libs do spring as mesmas que estão dentro do zip do myview não são necessárias e devem ser ignoradas) e seguir alguns passos simples que serão descritos abaixo.

Para obter o myview pelo maven basta colocar o repositório abaixo, caso seu pom.xml ainda não tenha o mesmo declarado:

<repository>
        <id>sonatype-releases</id>
        <name>Sonatype Releases Repository</name>
        <url>http://oss.sonatype.org/content/repositories/releases/</url>
</repository>

E adicionar a dependência para o myview como abaixo:

<dependency>
        <groupId>org.lindbergframework.web</groupId>
        <artifactId>lindbergframework-myview</artifactId>
        <version>1.0</version>
</dependency>

Para baixar diretamente o ZIP contendo o myview + spring + dependencias clique no link abaixo:
https://lindbergframework.googlecode.com/files/lindbergframework-myview-1.0.rar


NOTA: Essa pasta zip com os jars necessários para se utilizar o myview com o spring 3 e o jsf 2 contém os jars do lindbergframework-myview e suas dependencias. E inclui também so jars do spring caso não tenha adicionado ainda. Como provavelmente sua aplicação já tem os jars do spring fique a vontade para desconsiderar os do spring. Mas fique atento que todos são necessários, incluindo o do myfaces-orchestra-core20-1.4, e mesmo que sua aplicação ja tenha o spring pode ser que falte algum jar do spring que é requerido pelo myview como por exemplo spring-orm, spring-tx, spring-web, etc... Então é bom ficar atento.


Depois do myview obtido e devidamente instalado em sua aplicação que usar JSF 2 + spring 3 siga as instruções abaixo.

VAMOS CONFIGURAR O MYVIEW DE FORMA SIMPLES EM ALGUNS PASSOS

- Adicione no seu xml arquivo de configuração do spring (applicationContext.xml) a importação para o xml do myview

<import resource="classpath:/META-INF/myview-spring-init.xml"/>

- Se estiver usando algum framework orm como hibernate ou jpa lembre sempre daaqueles probelmas de Lazy Initialization são uma constante para quem não sabe trabalhar adequadamente com esse tipo de mecanismo com JSF usando escopos de conversação e view. Para resolver isso, basta declarar o bean com id persistentContextFactory no seu applicationContext.xml adequado para qual tecnologia vai usar, hibernate ou jpa, passando para ele respectivamente a sessionFactory ou entityManager de acordo com o que tiver usando como abaixo:

- Se estiver usando Hibernate declare

<bean id="persistentContextFactory"
      class="org.lindbergframework.web.conversation.spring.HibernatePersistenceContextFactory">
        <property name="sessionFactory" ref="sessionFactory"/>
</bean>

IMPORTANTE: Lembrando que ref="sessionFactory" espera que sua sessionFactory tenha o ID 'sessionFactory'. Caso o ID da sua sessionFactory seja outro você deve mudar o valor desse atributo também.

- Se estiver usando JPA declare da seguinte forma

<bean id="persistentContextFactory"
      class="org.apache.myfaces.orchestra.conversation.spring.JpaPersistenceContextFactory">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

IMPORTANTE: Definir esse bean "persistentContextFactory" no seu applicationContext.xml não é requerido para o funcionamento dos escopos adicionais do myview. Isto torna-se importante pelo fato de que usando hibernate e jpa nos escopos persistentes além de request como view e conversação (access) é necessário uma manipulação diferente da session do hibernate e do entityManager da JPA de modo a evitar problemas comums como o corriqueiro problema de lazy initialization entre outros. Então se você usa hibernate ou jpa é expressamente recomendado que declare essas 3 meras linhas de XML em seu applicationContext.xml afim de evitar dores de cabela e problemas.

Da mesma forma que a sessionFactory, se sua entityManagerFactory tiver um ID diferente você deve mudar o valor do atributo "ref" passando o correto ID da sua "entityManagerFactory".

TA QUASE PRONTO

- Agora é necessário que você no seu faces-config.xml, caso não tenha declarado ainda, declare o seguinte:

<application>
       <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
</application>

- Agora no seu web.xml é necessário que declare os listeners do spring caso ainda tenha configurado (se sua aplicação ja usa spring, isso certamente ja deve ta congiruado e funcionando. Coloquei apenas para lembrar)

<listener>
       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<listener>
       <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

- Continuandono seu web.xml, para funcionar o escopo de conversação, é requerido que você declare:
<listener>
       <listener-class>org.apache.myfaces.orchestra.conversation.servlet.ConversationManagerSessionListener</listener-class>
</listener>

- Caso esteja usando hibernate é bom você declarar o filtro que vai lhe garantir o recurso de OpenSessionInView nos novos escopos adicionados pelo MyView. Isso é necessário para evitar os problemas referentes a "session is closed" e de lazy initialization nas páginas JSF.

<filter>
       <filter-name>hibernateFilter</filter-name>
       <filter-class>org.lindbergframework.orm.hibernate.support.MyViewScopesOpenSessionInViewFilter</filter-class>
</filter>

<filter-mapping>
       <filter-name>hibernateFilter</filter-name>
       <url-pattern>/*</url-pattern>
</filter-mapping>

PRONTO TA TUDO CONFIGURADO.

Agora é só usar os escopos anotando seus managedBeans com as seguintes anotações de acordo com a necessidade:

* @AccessScoped : Mantem o managedBean "vivo" enquanto alguma página estiver usando o mesmo. Ou seja enquanto o mesmo estiver participando da conversação da página corrente. Quando uma página que não usa o managedBean for acessada o mesmo será destruído e seu estado perdido. Quando uma página que usa o mesmo for novamente acessada, uma nova instancia do managedBean será criada e usada enquanto houver uma página usando o mesmo.

Exemplo:

@Component
@AccessScoped
public class EscopoConversacaoMB{
        //
}

* @SameViewScoped : Mantem o managedBean "vivo" enquanto a mesma página estiver ativa. Quando o usuário for para qualquer outra página diferente o managedBean é destruído e seu estado perdido. Quando o usuário acessar novamente uma página que usa o managedBean, uma nova instancia é criada e continuará "viva" enquanto o usuário permanecer na mesma página.

Exemplo:

@Component
@SameViewScoped
public class EscopoMesmaPaginaMB{
        //
}

IMPORTANTE: Para que o escopo seja aplicado, é necessário que seu managedbean seja um bean do spring. Ou seja é necessário que caso você esteja usando definição de beans via annotation, você defina seus managedBeans com @Component ou caso esteja usando definições de beans via XML, declare os mesmos em seu applicationContext.mxl

Pronto, é isso.

Mais uma vez, abaixo o link para download:
https://lindbergframework.googlecode.com/files/lindbergframework-myview-1.0.rar


Bugs, opiniões, solicitações, críticas, dúvidas de como colocar pra funcionar, eventuais problemas, enfim, para contato enviar email para os endereços abaixo:

lindbergframework@lindbergframework.org
victorlindberg713@gmail.com

Aproveitando a oportunidade, para uma solução simples, plugável e flexivel para eliminar sql dos seus DAOs de projetos que não usam frameworks ORM e sim JDBC ou springDAO puro e direto, para uma solução simples de acesso, população e abstração de acesso a stored procedures e stored functions, usando ou não cursores, população de objetos complexos descomplicada e automática a partir de queries sem a necessidade de se preocupar em trabalhar com ResultSet´s, de percorrer ResultSet´s criando e populando seus objetos, veja o lindbergframework em https://code.google.com/p/lindbergframework/  ou leia diretamente a documentação em https://lindbergframework.googlecode.com/files/lindbergframework-1.0-doc.pdf.

quarta-feira, 25 de janeiro de 2012

Integrando com Adobe Flex via Blazeds

Da mesma forma como foi apresentada no post referente a a integração com JSF (link), é possível integrar o contexto de beans com o Adobe Flex a partir do BlazeDS. Para tal, basta declarar a implementação de flex.messaging.FlexFactory do lindbergframework, que promove a integração, no arquivo servicesconfig.xm como a seguir. A partir daí os beans contidos no contexto estão acessíveis de forma transparente para a camada de visão/front-end Flex. Da mesma forma como foi apresentada a integração do contexto com o JSF, os beans são acessados via seus respectivos ID´s.

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
     <factories>
          <factory id="lindbergFactory" class="org.lindbergframework.integration.web.flex.LindbergFlexBeanFactory" />
     </factories>
</services-config>

quinta-feira, 5 de janeiro de 2012

Integrando o contexto de Beans do lindbergframework com JSF (Java Server Faces)

A integração do JSF com o contexto de Beans do lindbergframework possibilita o uso de qualquer Bean declarado com annotation @Bean e que faça parte do contexto nas páginas JSF, diretamente via o ID do Bean.

Exemplo: Considere que temos um managedBean chamado ManterPessoaMB cujo ID do mesmo, declarado via annotation @Bean seja manterPessoaMB.

@Bean("manterPessoaMB")
public class ManterPessoaMB {

   public String cadastrar(){
      //... implementação da ação de cadastrar
      return null;
   }

}

ATENÇÃO: Para que o bean acima faça parte do contexto de beans o mesmo deve estar direta ou indiretamente abaixo do pacote base, definido na configuração de CORE.


Para efetuar a integração e possibilitar que usemos o bean manterPessoaMB diretamente em páginas JSF a partir do seu ID, é necessário declarar apenas o ELResolver do lindbergframework, org.lindbergframework.integration.web.jsf.beans.LindbergBeanJsfResolver no faces-config.xml da aplicação como abaixo:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd" version="2.0">
     <application>
         <el-resolver>org.lindbergframework.integration.web.jsf.beans.LindbergBeanJsfResolver</el-resolver>
     </application>
</faces-config>

Basta isso para que o contexto de beans do lindbergframework esteja acessível nas páginas JSF.

Abaixo é mostrado um exemplo de um botão que acessa o bean ManterPessoaMB a partir do seu ID definido via annotation @Bean, manterPessoaMB, e chama o action 'cadastrar' declarado no mesmo.

<h:commandButton action="#{manterPessoaMB.cadastrar}" value="Cadastrar" />

Injeção de Dependência e Inversão de Controle com o lindbergframework.

Um conceito importante abordado por muitos frameworks consolidados na comunidade java, como o spring, é o de inversão de controle para o tratamento de injeção de dependências. Esse conceito propõe a transferência de responsabilidade para a criação de beans no projeto fazendo com que o controle sobre a criação, gerenciamento e injeção de dependências destes beans não fique a cargo do programador e seja invertido para um container ou qualquer outro componente que possa tomar controle sobre a execução.

O lindbergframework precisa em muitos casos intervir e customizar a criação de beans para aplicar correta e eficientemente injeção de dependência nos mesmos. Uma outra necessidade de intervenção na criação de beans é para usar diversos tipos de proxies de aspecto para vários fins, como por exemplo no gerenciamento de transações via annotation. Para fazer isso, o lindbergframework aplica um proxy sobre o bean que gerenciará todo o processo de transação de forma transparente. Mas para tal, é necessário que o controle de criação dos beans seja invertido para o LDIC (Lindberg Dependency Injection Container). Toda a parte do gerenciamento de transações do framework será detalhada mais a frente quando a parte de persistência for abordada.

Um framework já consolidado que poderia ter sido adotado é o spring por exemplo, mas isso obrigaria que todo projeto que usasse o lindbergframework também tivesse que usar o spring. Para solucionar o problema um mecanismo de inversão de controle foi criado de forma independente e exclusiva para o lindbergframework. Caso você deseje mesmo assim usar spring em seu projeto o lindbergframework no módulo integration fornece integração com este framework. Isto será demonstrado mais a frente.

NOTA: Observe que ao se usar o lindbergframework, principalmente os recursos de persistência, é necessária a dependência para o Spring. Isso não é necessário para efetuar injeção de dependências. O mecanismo de injeção do lindbergframework é independente e implementa outra solução para o problema. A dependência do Spring é requerida pois o lindbergframework usa recursos do SpringDAO.

Inversão de Controle na Prática

O mecanismo de inversão de controle do lindbergframework é simples e baseia-se inicialmente no pacote base onde o Lindberg Dependency Injection Container (LDIC - Contêiner Lindberg de Injeção de Dependência), responsável por resolver todas as dependências de cada bean, deve procurar os beans. No exemplo anteriormente mostrado o pacote org.lindbergframework.exemplo.* foi definido e isso fará com que o LDIC crie seu contexto de beans visualizando apenas beans que estão dentro do escopo do pacote org.lindbergframework.exemplo ou sub pacotes deste já que o “.*” foi definido. Abaixo vão algumas regras para a definição do pacote base:

1 – O pacote deve ser definido usando o carácter “.”;

2 – Pacotes usando o carácter “/” como por exemplo “org/lindbergframework/xxx” são inválidos;

3 – Para informar que o escopo se restrinja a um pacote único e específico e somente este basta definir o pacote sem “.*”. Por exemplo: br.empresa.beans define que apenas os beans dentro deste pacote serão visualizados. Qualquer bean dentro de um sub pacote deste não será encontrado como por exemplo um bean dentro de br.empresa.beans.exemplo não será visualizado;

4 – Para definir que o escopo é um pacote e incluir no escopo todo e qualquer bean que esteja em algum sub pacote do pacote base, como no exemplo anterior br.empresa.beans como pacote base. Para que um bean dentro de br.empresa.beans.exemplo também sejá visualizado e incluído no contexto o pacote base
deve ser definido usando ”.*” como a seguir:

br.empresa.beans.*

5 – Também é possível definir um pacote base usando seus sub pacotes mas excluindo do contexto alguns sub pacotes que não se deseje que façam parte do contexto de beans. Por exemplo, vamos levar em consideração a seguinte estrutura de pacotes:

br
br.empresa
br.empresa.beans
br.empresa.beans.exemplo
br.empresa.beans.schemas

Se fosse desejável criar um contexto de beans onde o LDIC visualizasse beans dentro do pacote br.empresa.beans e seus sub pacotes mas que o pacote br.empresa.beans.schemas não fizesse parte do contexto. Para tal, é necessário definir o pacote ou pacotes que se deseja excluir do contexto usando o caráter “:”. Para este exemplo a string do pacote básico seria da seguinte forma:

br.empresa.beans:schemas.*

O pacote definido dessa forma diz que o LDIC deve buscar beans no pacote br.empresa.beans e todos os seus sub pacotes menos o sub pacote schemas. Todo e qualquer bean que estiver dentro de br.empresa.beans.schemas os sub pacotes deste não será visualizado pelo LDIC.

Fábrica de Beans (BeanFactory)

A interface BeanFactory é fornecida pelo framework para definir as fábricas de beans que poderão trabalhar em conjunto com o LDIC. Uma classe que implementa essa interface deve fornecer instâncias de beans solicitados de acordo com um ID específico entre outras operações. Cada fábrica implementa a sua forma como cada instância de bean é obtida bem como cada dependência desses beans é resolvida e injetada no bean.

Uma outra interface importante mas que não será abordada mais a fundo neste momento e que trabalha em conjunto com as fábricas de beans é a BeanMapper. Essa interface define um mapeador de beans de modo a fornecer meta dados a uma BeanFactory para a correta criação e resolução de dependências de um bean com base em um ID específico solicitado.

A classe abstrata AbstractBeanFactory que fornece a base para fábricas de beans já contém um atributo BeanMapper de modo a fornecer um mapeador de bean para as fábricas concretas.

Injeção de Dependências

Fornecer um mecanismo para obtenção de instâncias de beans é um recurso importante. Imagine que temos uma classe chamada “A” que possui um dependência para uma classe “B” e esta última por sua vez possui uma dependência para “C”. Se solicitarmos uma instância de “A” é necessário que a instância de “A” venha preenchida com uma instância de “B” e “B” com uma instância de “C” de modo que todas as dependências diretas e indiretas de “A” estejam resolvidas e dessa forma “A” esteja pronto para ser usado.

Isso deve ser feito de forma transparente pelo mecanismo de inversão de controle de modo a fornecer recursos para diminuir o acoplamento entre as classes permitindo, por exemplo, que “A” se refira a “B” e “B” se refira a “C” através de uma interface tornando o código independente da implementação final.

Toda e qualquer interação entre “A”, “B” e “C”, poderia ocorrer através de interfaces onde o desenvolvedor não se preocupe com a implementação que em tempo de execução será utilizada. A instância do bean que contém a implementação a ser usada é responsabilidade do mecanismo de injeção de dependência que deve obter a correta instância e injetá-la onde necessário.


NOTA: Injeção de dependências não está ligado ao uso de interfaces ou não. Mesmo que não se use interfaces nem classes abstratas para prover o baico acoplamento entra as camadas o uso de inversão de controle para injeção de dependências é algo independente e importante.


O lindbergframework provê a interface DependencyManager que define um gerenciador de
dependências que tem como responsabilidade auxiliar uma BeanFactory resolvendo as dependências dos beans solicitados. A implementação padrão desta interface é um gerenciador de dependências baseado nas annotations @Bean e @Inject – AnnotationDependencyManager.

Para exemplificar considere o código abaixo que não usa a injeção automática de dependência:

public interface ISistemaFacade {
    public void cadastrarPessoa(Pessoa pessoa);
}

public class SistemaFacade implements ISistemaFacade{

    private IPessoaBC pessoaBC = new PessoaBC();

    public void cadastrarPessoa(Pessoa pessoa){
        pessoaBC.cadastrar(pessoa);
    }
}

public interface IPessoaBC {
    public void cadastrar(Pessoa pessoa);
}

public class PessoaBC implements IPessoaBC{

    private IPessoaDAO pessoaDAO = new PessoaDAO();

    public void cadastrar(Pessoa pessoa) {
        pessoaDAO.cadastrar(pessoa);
    }
}

public interface IPessoaDAO {
    public void cadastrar(Pessoa pessoa);
}

public class PessoaDAO implements IPessoaDAO{
    public void cadastrar(Pessoa pessoa){
        System.out.println("Pessoa cadastrada: "+pessoa.getNome());
    }
}


Código de teste:
Pessoa pessoa = new Pessoa("joão");
ISistemaFacade sistemaFacade = new SistemaFacade();
sistemaFacade.cadastrarPessoa(pessoa);

Observe que as classes demonstradas acima não usam recurso de inversão de controle para provimento de injeção de dependências e referem-se diretamente as implementações finais das classes ao qual dependem.

Neste caso observe que a fachada do nosso sistema depende de um BusinessController que implementa a interface IPessoaBC e a instância é criada diretamente via operador new no código tornando a fachada do nosso sistema extremamente dependente da implementação PessoaBC da interface IPessoaBC.

Da mesma forma a implementação PessoaBC dependente de uma implementação de IPessoaDAO e como foi feito na fachada a implementação PessoaDAO está diretamente referenciada no código e a instância criada diretamente no código. Neste exemplo os três níveis estão extremamente interdependentes, dificultando qualquer evolução ou manutenção no código pois o código está extremamente amarrado a implementações e não a interfaces mesmo que estejamos usando interfaces pois a implementação final é referida diretamente no código.

Qual o problema nisto? Um exemplo simples seria: Atualmente a maioria das aplicações trabalham com testes (JUnit ou algo similar). Uma pratica comum na implementação de testes é a criação de Mocks e Fakes. Que a grosso modo são objetos que simulam o real comportamento de objetos provendo para tal o comportamento desejado/esperado para um determinado teste. É comum o uso desse tipo de objetos para a criação de testes desconectados do banco. De modo que um mock seria uma implementação, por exemplo, de um DAO real só que simulando as reais operações de persistência bem como o comportamento desejado para um ou mais testes. Dito isto, no exemplo acima não seria possível normalmente (seria caso usássemos Aspecto) fornecer um Mock de IPessoaDAO para fins de testes pois o BC PessoaBC acessa diretamente a implementação real de IPessoaDAO (new PessoaDAO) e isso impossibilita a mudança da implementação do DAO a ser usada pois o BC está diretamente acoplado a implementação do DAO. Se o BC se referisse ao DAO apenas via interface e ao invés de obter a instância da implementação diretamente via operador new usasse um mecanismo de inversão de controle para injetar a implementação de IPessoaDAO desejada, poderíamos para os testes usar a implementação Mock e para aplicação real a implementação real.

Um mecanismo de inversão de controle faz todo esse trabalho deixando o desenvolvedor livre para tratar do que realmente interessa de modo que a criação e injeção dessas dependências são responsabilidades desse mecanismo, que no lindbergframework é chamado de LDIC (Lindberg Dependency Injection Container).

O lindbergframework fornece uma implementação padrão de BeanFacotry que é a classe
AnnotationBeanFactory. Essa fábrica de beans trabalha com as annotations @Bean e @Inject que são utilizadas para a definição de beans e suas dependências. Essa BeanFactory é a padrão e caso uma não seja definida o framework utilizará esta como padrão.

- @Bean: Annotation utilizada para a definir um bean. Os únicos parâmetros que ela tem é o value e singleton. O value é uma String que define o ID do bean. Esse ID deve ser único no contexto e esse ID é o que será usado para referenciar o bean. O ID é requerido nesta annotation. O parâmetro singleton é um boolean que recebe “true” quando o bean deve ser um singleton, ou seja ter apenas uma instância dentro do contexto retornando sempre a mesma instância deste e “false” caso contrário.

- @Inject: Annotation que define um ponto de injeção de dependência em um bean. O único parâmetro que esta annotation contém é o value que é o ID do bean que deve ser injetado no atributo de classe onde esta annotation foi declarada.

Abaixo é mostrado o mesmo exemplo só que fazendo uso dos recursos de inversão de controle do lindbergframework. No exemplo a seguir considere que no ponto de obtenção da instância do bean o Core do framework já foi configurado como mostrado nas sessões anteriores. Como só as implementações foram alteradas as interfaces são omitidas neste exemplo:

import org.lindbergframework.beans.di.annotation.Bean;
import org.lindbergframework.beans.di.annotation.Inject;
@Bean("sistemaFacade")
public class SistemaFacade implements ISistemaFacade{
    @Inject("pessoaBC")
    private IPessoaBC pessoaBC;

    public void cadastrarPessoa(Pessoa pessoa){
        pessoaBC.cadastrar(pessoa);
        }
}



import org.lindbergframework.beans.di.annotation.Bean;
import org.lindbergframework.beans.di.annotation.Inject;
@Bean("pessoaBC")
public class PessoaBC implements IPessoaBC{

    @Inject("pessoaDAO")
    private IPessoaDAO pessoaDAO;

    public void cadastrar(Pessoa pessoa) {
        pessoaDAO.cadastrar(pessoa);
    }
}


import org.lindbergframework.beans.di.annotation.Bean;
@Bean("pessoaDAO")
public class PessoaDAO implements IPessoaDAO{

    public void cadastrar(Pessoa pessoa){
        System.out.println("Pessoa cadastrada: "+pessoa.getNome());
    }
}

Código de teste:
Pessoa pessoa = new Pessoa("joão");
ISistemaFacade sistemaFacade = UserBeanContext.getInstance().getBean("sistemaFacade");
sistemaFacade.cadastrarPessoa(pessoa);


Observe que neste segundo exemplo as classes não referenciam como dependência nenhum implementação específica. SistemaFacade referencia apenas a interface IPessoaBC e não é afetada quanto a implementação que será usada. Da mesma forma PessoaBC referencia apenas a interface IPessoaDAO e seu funcionamento não depende e nem está acoplado a nenhuma implementação desta.

Neste último exemplo a classe SistemaFacade é anotada com a annotation @Bean onde o ID deste bean é definido como sistemaFacade. Este bean possui uma dependência com um IPessoaBC e para definir e instruir ao LDIC que faça a injeção da implementação que deve ser usada, este atributo foi anotado com a annotation @Inject definindo como parâmetro o ID do bean que deve ser injetado neste ponto. Observe que não há necessidade de definição de um setter para pessoaBC.

Da mesma forma a nossa implementação de IPessoaBC a classe PessoaBC é anotada da mesma forma, uma annotation @Bean definindo o ID desta e como temos apenas uma dependência, apenas uma annotation @Inject foi usada, neste caso para a dependência para IPessoaDAO.

Obtendo beans a partir do Contexto

Com as classes devidamente anotadas precisamos obter uma instância de nossa fachada e utilizá-la. É aí que entram as configurações feitas no core e a nossa bean factory. No trecho abaixo obtemos do CoreContext a instância da BeanFacotry e a partir desta fábrica obtemos a instância do bean da fachada do sistema e para isso passamos o ID do bean da fachada que neste caso é “sistemaFacade” para o método getBean da BeanFactory. A instância do bean da fachada retornada por este método já virá pronta com suas dependências injetadas e prontas para uso.

Para este caso a instância de sistemaFacade já virá preenchida com a instância correta de pessoaBC e este por sua vez já estará preenchido com a instância correta de pessoaDAO tudo de forma automática e transparente feita pelo LDIC.

ISistemaFacade sistemaFacade = CoreContext.getInstance().getBeanFactory().getBean("sistemaFacade");

Uma outra forma e até mais simples e direta de se obter um bean a partir do contexto usando o mecanismo de inversão e controle, é a partir da classe UserBeanContext.

Essa classe é um singleton e é um atalho para acesso da fábrica de beans padrão definida. O mesmo trecho de código apresentando anteriormente para obtenção de uma instância como exemplo de IsistemaFacade usando CoreContext poderia ser feita usando UserBeanContext da seguinte forma:

ISistemaFacade sistemaFacade = UserBeanContext.getInstance().getBean("sistemaFacade");

Observe que no trecho acima apenas a interface é referenciada. Fica a cargo do LDIC retornar a instância da implementação que deve ser usada correspondente ao ID passado.

O LDIC também efetua a injeção em dependências definidas em superclasse de modo que se PessoaBC estendesse de uma classe qualquer e esta última tivesse dependências definidas via @Inject estas dependências herdadas, mesmo private, seriam resolvidas e injetadas.

Caso o ID passado para o método getBean não corresponda a um bean válido dentro do contexto da bean factory, uma BeanNotFoundException é lançada informando que um bean com o ID especificado não foi encontrado.

O lindbergframework, já se integra com JSF e Adobe Flex e usando o framework integrado com qualquer um destes últimos não é necessário o uso de UserBeanContext.getInstance().getBean() pois no caso do JSF o managedBean já é criado e suas dependências resolvidas automáticamente pelo LDIC e no caso do Flex o bean invocado para execução de um serviço ou método remoto é resolvido pelo LDIC.

Beans com escopo Singleton

O framework também provê a possibilidade de definir um bean de modo que este seja um singleton. Isto é, tenha apenas uma instância dentro do contexto. Quando um bean é definido com o escopo singleton o LDIC manterá sempre a mesma instância e sempre que o bean for solicitado ao LDIC esta mesma instância será retornada.

Se fosse desejável, por exemplo, definir que o bean SistemaFacade deve ter uma única instância dentro do contexto, então especificaríamos seu escopo como singleton na própria annotation @Bean. Abaixo é mostrado como ficaria a classe SistemaFacade de modo a operar como um singleton.


@Bean(value = "sistemaFacade",singleton = true)
public class SistemaFacade implements IsistemaFacade{}


Observe que a annotation @Bean foi alterada adicionando o atributo singleton definindo o valor deste como true indicando que o bean SistemaFacade deve ser trabalhar como um singleton dentro do contexto. O LDIC agora retornará sempre a mesma instância de SistemaFacade quando este bean for solicitado.

sexta-feira, 30 de dezembro de 2011

Configurando o framework em aplicações WEB

Até agora para configurar o framework, através de seu módulo principal, o CORE, seja
programaticamente, via XML, ou qualquer outra implementação seja em nosso código seja a partir de um método main, seja a partir de uma classe que recebe essa responsabilidade ou qualquer outra estrutura temos que chamar sempre CoreContext.getInstance().initialize(coreConfiguration); ou coreConfiguration.initializeContext(); para configurar e inicializar o framework.

Em uma aplicação WEB é comum que um framework forneça recursos para que a configuração seja feita de forma automática, sem precisar de nada programaticamente.
Este recurso também é provido pelo lindbergframework através de um ServletContextListener e da definição de alguns no Deployment Descriptor (web.xml) da aplicação. A partir daí quando o servidor for inicializado e a aplicação publicada o lindbergframework será automaticamente configurado baseado nas configurações passadas.

Essa configuração é muito simples. Baseia-se em declarar no web.xml da aplicação o ServletContextListener provido pelo lindbergframework e definir os parâmetros (<context-param>) informando onde está o arquivo de configuração do framework e qual a implementação do parser de configuração a ser usado, se será Xml, Properties, Txt, seja lá qual for, sendo que para este último é esperado uma implementação de WebCoreConfiguration.

Tanto o local onde se encontra o arquivo de configuração quando a implementação de WebCoreConfiguration a ser usada não são requeridos pois ambos possuem valores padrão. O path do arquivo de configuração caso não seja definido tem como padrão o valor lindberg-config.xml na raiz do classpath do projeto. E a implementação de WebCoreConfiguration, caso não seja declarada, é usada como padrão:

org.lindbergframework.web.core.configuration.WebClassPathXmlCoreConfiguration.

Abaixo é descrito um exemplo do que precisaria ser adicionado ao nosso web.xml se fossemos usar o mesmo arquivo de configuração que usamos nos exemplos anteriores só que agora em um projeto WEB para configurar o framework de forma automática:

<context-param><!--(ISTO É OPCIONAL. O caminho padrão para o arquivo de configuração é 'lindbergconfig.xml' localizado na raiz do classpath)-->
    <param-name>lindbergConfigLocation</param-name>
    <param-value>org/lindbergframework/configuracao/lindberg-config.xml</param-value>
</context-param>
<context-param> <!--(ISTO É OPCIONAL)-->
    <param-name>lindbergConfigClass</param-name>
    <param-alue>org.lindbergframework.core.context.WebClassPathXmlCoreConfiguration</param-value>
</context-param>
<listener>
    <listener-class>org.lindbergframework.web.LindbergContextLoaderListener</listener-class>
</listener>


No exemplo acima observe que adicionamos o listener LindbergContextLoaderListerner, que é o ServletContextListener provido pelo lindbergframework e que sabe como configurar de forma automatizada o framework. Adicionamos o parâmetro lindbergConfigLocation que é onde está o arquivo de configuração.

Esta propriedade é opcional e caso não seja declarada, o framework usa o nome e local padrão para o arquivo de configuração, que é 'lindberg-config.xml' na raiz do classpath. Importante dizer que o arquivo não obrigatoriamente tem que ser um XML, isto depende da implementação de WebCoreConfiguration que estiver usando. Se, por exemplo, ocorresse a necessidade de usar uma implementação para configurações definidas em um arquivo .properties então este parâmetro apontaria para um arquivo properties, da mesma forma se eu criar minha própria implementação de WebCoreConfiguration que trabalha com um arquivo TXT segundo uma formatação que eu criei então esse parâmetro vai apontar para um arquivo TXT que segue o padrão de formatação definido. Para que haja essa flexibilidade, é que é possível definir a implementação de WebCoreConfiguration que deve ser usada através do parâmetro lindbergConfigClass.

Passos para configuração no web.xml:
• Declarar o listener: org.lindbergframework.web.LindbergContextLoaderListener
• Declarar os Parâmetros via :
-- ◦ lindbergConfigLocation: Local onde está o arquivo de configuração caso o -- arquivo de configuração não siga o padrão é 'lindberg-config.xml' na raiz do
-- classpath.

-- ◦ lindbergConfigClass: Implementação de CoreConfiguration que efetuará o parser -- da configuração. Quando não definido este parâmetro a implementação padrão,
-- WebClassPathXmlCoreConfiguration, é usada.

A partir daí é só iniciar o servidor que o a configuração será efetuada automaticamente e você verá no console as mensagens de log informando o processo de inicialização do contexto WEB do framework.

ATENÇÃO: Para alguns servidores é necessário adicionar a lib do xmlbeans direto nas libs do servidor de modo a sobrescrever a lib padrão provida pelo mesmo. Isso se faz necessário quando ao subir o servidor se obtenha uma mensagem de erro como a descrita abaixo indicando conflito de carregamento de classes do xmlbeans.

”...Loader constraint violation in interface itable initialization: when resolving method
"org.apache.xmlbeans.impl.store.Xobj$NodeXobj.setUserData(Ljava/lang/String;Ljava/lang/Object;Lorg/w3c/dom/UserDataHa
ndler;)Ljava/lang/Object;" the class loader (instance of org/jboss/classloader/spi/base/BaseClassLoader) of the
current class, org/apache/xmlbeans/impl/store/Xobj$NodeXobj, and the class loader (instance of ) for
interface org/w3c/dom/Node have different Class objects for the type org/w3c/dom/UserDataHandler used in the
signature”

A Classe CoreContext

A classe CoreContrext é um singleton e provê acesso a toda configuração do módulo CORE do framework. Para obter a instância singleton do contexto de configuração de core basta chamar

CoreContext.getInstane()

As configurações definidas para o core como o pacote base para o contexto de
injeção de dependências, a implementação da BeanFactory, módulos dependêntes, e qualquer outra configuração de CORE pode ser acessada via CoreContext.

Veja a documentação da classe CoreContext aqui.