Um problema.. um Desafio.. Sessão, JSF, AjaxDWR, botão de fechar

Olá pessoal…

tenho um problema que tem se demonstrado um grande desafio de solução. Até implaquei uma, mas se demonstrou muito instável. No próximo post, colocarei essa solução.

O problema em questão é gerenciar uma sessão JSF com funções ligadas diretamente ao cliente (browser).
Mas porque gerenciar essa sessão? Esta pergunta é válida pois se não existe um porque, não exite também o problema, não é mesmo?
Muito bem. Muitas aplicações não tem um gerenciamento sólido pelos movimentos do cliente. O que elas têm é um simples timeout configurado no tomcat, que após esse tempo, o tomcat mata a sessão em jogo. Quando faz isso, destrói objetos de sessão e limpa endereços de memória no servidor ligados àquela sessão. Assim, como num toque de mágica, a máquina tomcat faz o gerenciamento pra você e você não precisa se preocupar com session.invalidate() em qualquer momento. Aplicações assim costumam ter objetos e atributos de sessão que não comprometem mais do que memória RAM.
No entanto, quando se tem atributos de sessão como conexão de banco de dados que é gerenciada manualmente por você mesmo, é preciso, em algum momento, declarar o fechamento dessa conexão. A pergunta é: ‘quando’? A resposta é fácil: ‘Quando a sessão acaba.’ Isso, por tempo ocioso no tomcat acontece automaticamente. Mas imagine que você tenha um grande número de acessos e conexões no servidor, que por sua vez requer que essas conexões sejam destruídas o mais rápido possível. Então uma solução, um meio de matar a sessão é colocar um botão na aplicação com o Label: ‘SAIR’, que mata as sessões. Mas e se o usuário não clicar em sair e apertar o botão de fechar do browser? --> a sessão continua e a conexão também. Mas então colocamos no evento onunload da pagina para chamar, através de AJAX, um método para matar a sessão. Mas o problema é que quando você submitar a página, ou apertar o botão de voltar no browser, também vai matar a sessão, e se você estiver gerenciando a aplicação por um phaselistener que redireciona para login se a sessão estiver nula, então sempre vai redirecionar para login.
Parece até um beco sem saída, algo extraordinariamente chato. ehhehe

A minha pergunta para os programadores JAVA desse portal é: "Existe uma maneira de matar a sessão logo após o clique no botão de fechar do browser?"

Ainda eu não descobri uma solução que fosse estável, nem mesmo consegui em grandes fórums pela internet resposta para isso. O que muita gente me referenciou foram alguns codigos javascript do tipo: clique aqui, faça isso, use esse browser e seja feliz. Mas eu quero uma solução bonita. Se usar javascript para pegar o clique do botao de fechar, então que sirva para todos os browsers ( ou os mais importantes). Os códigos que já verifiquei servem para IE, mas não para firefox. Isso ocorre pelo DOM do IE ser diferente do firefox.

Fica a pergunta… será que existe? será?

(abaixo uma solução instável)

Uma solução instável.

Nos meus jsfs ou htmls, coloquei no evento onunload da pagina um certo método AjaxDWR que chama um método nos meus beans, que seta para o objeto ‘usuario’ da sessão o valor true para um atributo chamado ‘fechouBrowser’. Para fazer isso, pegar o FacesContext e o usuario da sessao, usei classes que do framework AjaxDWR, pois quando se chama um metodo java por javascript, o FacesContext se torna null. Para setar o atributo, usei uma classe que implementa FacesContext e seto um novo facescontext para a minha aplicação, com o usuário que eu modifiquei. Ainda no método que chamei por AjaxDwr, coloco logo depois de setar esse atributo para a sessão um delay de 1 segundo. Mas porque delay? Quando o metodo que o dwr chamou é disparado, ao mesmo tempo, em outra thread, o phaselistener é disparado. Nesse phaselistener eu seto o atributo fechouBrowser para false novamente. Quando o delay acaba, pego o facescontext denovo, e vejo se o atributo fechouBrowser é true. Se for true, fecho as conexoes, se for false, deixo tudo como estava. Mas porque colocar isso no phaselistener? Pq se o usuario tiver submitado a página o phaselistener dispara. Se ele fechou o browser, o phaselistener nao dispara. Abaixo vem os códigos.

       <body onunload="auxDwr.fazDelayeDesconecta()" onload="auxDwr.setaFechouBrowserParaFalse()">  

método para o delay

       public static void fazDelayeDesconecta(){
		try {  
		getFacesContext(WebContextFactory.get().getHttpServletRequest(), WebContextFactory.get().getHttpServletResponse(), false, false);
		     TimeUnit.SECONDS.sleep(1); 
   		} catch (InterruptedException ignored) {}
   		FacesContext facesContext = getFacesContext(WebContextFactory.get().getHttpServletRequest(), WebContextFactory.get().getHttpServletResponse(), true, true);
   		try{
   			Usuario user = ((Usuario)facesContext.getApplication().createValueBinding("#{usuario}").getValue(facesContext));
   		
   			if( user.fechouBrowser){
	   				logoff();
	   		}
   		}catch(Exception e ){
   			System.out.println("Deu pau!");
   		}
	}

método para o phaselistener

 public void beforePhase(PhaseEvent phaseEvent){
    //se passa por aqui, entao o usuario nao fechou o browser, mas submitou a pagina.
     FacesContext facescontext = FacesContext.getCurrentInstance();
     Usuario userSession = (Usuario)facescontext.getApplication().createValueBinding("#{usuario}").getValue(facescontext);
     userSession.fechouBrowser = false;     
    // depois seta para a sessão
}

método para pegar o facescontext com o dwr.

private static FacesContext getFacesContext(ServletRequest request, ServletResponse response, boolean somenteLeitura, boolean abreBrowser) {
		  FacesContext facesContext = FacesContext.getCurrentInstance();
		  if (facesContext != null) return facesContext;

		  javax.faces.context.FacesContextFactory contextFactory = (FacesContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
		  LifecycleFactory lifecycleFactory = (LifecycleFactory)FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
		  Lifecycle lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
	
		  facesContext = contextFactory.getFacesContext(WebContextFactory.get().getSession().getServletContext(), request, response, lifecycle);

		 Usuario usuario = (Usuario)facesContext.getApplication().createValueBinding("#{usuario}").getValue(facesContext);
		  if(!somenteLeitura){
			  if(!abreBrowser){
				  usuario.fechouBrowser = true;  
			  } else {
				  usuario.fechouBrowser = false;
			  }
		  	}
		  facesContext.getApplication().createValueBinding("#{usuario}").setValue(facesContext, usuario);
		  
		  InnerFacesContext.setFacesContextAsCurrentInstance(facesContext);
		  
		  
		  return facesContext;
		}
	private abstract static class InnerFacesContext extends FacesContext
	{
	  protected static void setFacesContextAsCurrentInstance(FacesContext facesContext) {
	    FacesContext.setCurrentInstance(facesContext);
	  }
	}
	
	public static void setaFechouBrowserParaFalse(){
		FacesContext facesContext =	getFacesContext(WebContextFactory.get().getHttpServletRequest(), WebContextFactory.get().getHttpServletResponse(), false, true);
	}

Isso se tornou instável pois delay é uma gambiarra…
Sinceramente… Tem uma lógica legal. Não que seja a coisa mais perfeita. Mas é algo que é desafiador. Eu fico pensando… “puts, se funcionasse… ia ser muito louco”.
Tornou-se instável porque na minha máquina funcionava certinho, já na dum cara que acessa remotamente, com velocidade mais baixa, dava errado, e sempre redirecionava para login.
Às vezes até na minha máquina as coisas davam errado.

Puts… se funcionasse, ia ser louco.

Por que vc nao usa pool de conexoes?!

http://www.guj.com.br/posts/list/21859.java
http://www.guj.com.br/posts/list/18429.java
http://www.devmedia.com.br/articles/viewcomp.asp?comp=4113&hl=
http://www.antoniopassos.pro.br/blog/?p=88
http://www.javafree.org/news/view.jf?idNew=2851

Por que deixar a conexao aberta na sessao?! Por que manter uma conexao diferente para cada sessao aberta no sistema?

Isso se caso tudo que vc quer fazer tiver relacionado as conexoes