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.

8 comentários:

  1. fiz tudo exatamente como dito gera esse erro

    message /cadastro.xhtml @16,64 value="#{cadastroBean.pessoa.nome}": Target Unreachable, identifier 'cadastroBean' resolved to null

    description The server encountered an internal error that prevented it from fulfilling this request.

    exception

    javax.servlet.ServletException: /cadastro.xhtml @16,64 value="#{cadastroBean.pessoa.nome}": Target Unreachable, identifier 'cadastroBean' resolved to null
    javax.faces.webapp.FacesServlet.service(FacesServlet.java:606)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    root cause

    javax.el.PropertyNotFoundException: /cadastro.xhtml @16,64 value="#{cadastroBean.pessoa.nome}": Target Unreachable, identifier 'cadastroBean' resolved to null
    com.sun.faces.facelets.el.TagValueExpression.getType(TagValueExpression.java:100)
    com.sun.faces.renderkit.html_basic.HtmlBasicInputRenderer.getConvertedValue(HtmlBasicInputRenderer.java:95)
    javax.faces.component.UIInput.getConvertedValue(UIInput.java:1030)
    javax.faces.component.UIInput.validate(UIInput.java:960)
    javax.faces.component.UIInput.executeValidate(UIInput.java:1233)
    javax.faces.component.UIInput.processValidators(UIInput.java:698)
    javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:1214)
    javax.faces.component.UIForm.processValidators(UIForm.java:253)
    javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:1214)
    javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:1214)
    javax.faces.component.UIViewRoot.processValidators(UIViewRoot.java:1169)
    com.sun.faces.lifecycle.ProcessValidationsPhase.execute(ProcessValidationsPhase.java:76)
    com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
    com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
    javax.faces.webapp.FacesServlet.service(FacesServlet.java:593)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    note The full stack trace of the root cause is available in the Apache Tomcat/7.0.56 logs.

    ResponderExcluir
    Respostas
    1. Poste sua classe cadastroBean, seu application-context.xml do spring, seu web.xml, seu faces-context.xml e seu xhtml pra eu dar uma olhada e tentar ver o que está havendo.

      Excluir
  2. Bom dia amigo, funciona com o Spring 4?

    ResponderExcluir
    Respostas
    1. Carlos, não testei com Spring 4 mas creio que funcionará sim. Mas não sei te dizer. Ficarei feliz se me der o feedback aqui dizendo se funcionou ou não.

      Excluir
    2. Este comentário foi removido pelo autor.

      Excluir
    3. Testei via maven e infelizmente não funcionou, pois seu framework utiliza as libs do Spring 3, que acaba dando conflito. De qualquer forma obrigado pela resposta.

      Você teria alguma dica de como fazer esse escopo funcionar, mesmo que seja de uma forma mais complicada? Pois caso contrário terei que retirar o Spring de todo o projeto (grande) para implementar o CDI e utilizar o do CODI. Obrigado!

      Excluir
    4. Bom dia amigo, conseguir resolver com o MyFaces Orchestra, segui somente os passos desse post: http://archsofty.blogspot.com.br/2010/05/adicionando-escopo-de-conversacao-ao.html , postei aqui para quem tiver com o mesmo problema que eu estava. De todo modo muito obrigado.

      Excluir