Envers + Vraptor3[Resolvido]

18 respostas
boneazul

Olá pessoal tudo bem…

Tenho uma especificação em um projeto onde tera de ser implementada auditoria.
Estou utilizando o envers para tal recurso.

Esta tudo ok.Só um ponto que não estou conseguindo utilizar.
Preciso passar qual o usuario esta sendo responsavel pela versão que está sendo registrada.

eu achei o trecho na propria documentacao do envers com outros frameworks …queria saber como pedoria fazer com o vraptor

public class AuditListener implements RevisionListener {	
	public void newRevision(Object revisionEntity) {
        RevEntity actualRevision = (RevEntity) revisionEntity;
        actualRevision.setUsername("Id do usuário");
        //Para o JSF eles usam geralmente ((User)FacesUtils.getManagedBean(ManagedBean.CurrentUser)).getId();
       //Para o Seam eles usam geralmente Identity = (Identity) Component.getInstance("org.jboss.seam.security.identity");identity.getId();
     
       //E para o vraptor como eu faria tenho um componente chamado WebSession que gerencia minha sessao e la se localiza o usuario logado.

    }  
}
package br.com.webtia.contrato.util;

import java.io.Serializable;

import javax.servlet.http.HttpSession;

import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.ioc.SessionScoped;

@SuppressWarnings("serial")
@Component
@SessionScoped
public class WebSession implements Serializable{
	private HttpSession session;

	public WebSession(HttpSession session) {
		this.session = session;
	}

	public void setObjectSession(Object objeto, String name) {
		this.session.setAttribute(name, objeto);
	}

	public Object getObjectSession(String name) {
		return this.session.getAttribute(name);
	}

	public void removeObjectSession(String name) {
		this.session.removeAttribute(name);
	}
}

Como faço pra injetar esse cara lá dentro … é possível??

18 Respostas

G

Se você está usando JAAS para autenticação você pode pegar as credenciais via

boneazul

garcia-jj:
Se você está usando JAAS para autenticação você pode pegar as credenciais via

Não cara to usando uma implementação própria de autenticação,perfis,etc…

isso ta no bean WebSession que é @SessionScoped gerenciado pelo Spring.

Acho que a pergunta seria …como conseguiria recuperar esse bean ja que nao to conseguindo injetar ele no construtor da classe.

Minha recuperacao seria algo do tipo

((User) (InstanciadeWebSession).getObjectSession("user")).getId();

O problema ta sendo recuperar essa instancia de WebSession.

G

Como injetar nessa sua classe AuditListener eu não sei mesmo, nunca usei o Envers dessa forma. Mas uma coisa que eu pensei agora é que você pode usar o TransactionSynchronizationManager do Spring para "setar" o usuário via interceptor do Vraptor e depois pegar no seu AuditListener. Porém dessa forma você vai ficar amarrado ao Spring.

Talvez o Lucas tenha uma solução a fazer via vraptor.

http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/transaction/support/TransactionSynchronizationManager.html

public class MyInterceptor implements Interceptor {
    accepts(...)

    intercepts(...) {
        User user = (User) (InstanciadeWebSession).getObjectSession("user"));
        TransactionSynchronizationManager.bindResource("user", user);
    }

}

No seu AuditListener

public class AuditListener implements RevisionListener {	
    public void newRevision(Object revisionEntity) {
        RevEntity actualRevision = (RevEntity) revisionEntity;
        User u = (User) TransactionSynchronizationManager.getResource("user");
        actualRevision.setUsername(u.getId());
    }  
}
boneazul

Tentei desse modo mas chega null la no listener…tirando o fato que não implementei num interceptor…
mas faria diferença implementar no interceptor ou não???

coloquei direto no meu ponto onde logo o cara

public void authenticate(User user) {
		Boolean userCanLogin = daoUser.userCanLogin(user);
		if (userCanLogin.booleanValue()) {
			User logIn = daoUser.getUserByLogin(user);
			logIn.setRole(daoLiberacaoAcesso.getLiberacao(logIn).getPerfil());
			logIn.getUnidade().getDestinatario().getCidade().getId();
			logIn.getRole().getActions().get(0).getId();
			session.setObjectSession(logIn, "user");
			TransactionSynchronizationManager.bindResource("user", logIn);
			result.redirectTo(GeneralController.class).home();
		} else {
			result.include("error", "Usuario ou senha inválidos");
			result.redirectTo(GeneralController.class).login();
		}
	}
Lucas_Cavalcanti

como vc registra esse AuditListener?

em algum momento vc faz algo do tipo:

objetoMagico.register(new AuditListener());

?

a classe que tem esse código é um componente do VRaptor?

se sim, basta receber o WebSession no controller dessa classe, e na hora de registrar, vc passa no construtor do AuditListener…

[]'s

boneazul

Lucas Cavalcanti:
como vc registra esse AuditListener?

em algum momento vc faz algo do tipo:

objetoMagico.register(new AuditListener());

?

a classe que tem esse código é um componente do VRaptor?

se sim, basta receber o WebSession no controller dessa classe, e na hora de registrar, vc passa no construtor do AuditListener…

[]'s

Lucas entao eu segui a especificação do site do envers pra customizar a entidade que guarda as versões ta implementado o seguinte.

Minha entidade da revisão…

package br.com.webtia.contrato.models;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import org.hibernate.envers.RevisionEntity;
import org.hibernate.envers.RevisionNumber;
import org.hibernate.envers.RevisionTimestamp;

import br.com.webtia.contrato.audit.AuditListener;

@Entity
@Table(name = "AUDITORIA")
//Essa anotação que diz ao envenrs pra usar o meu listener acho
//pelo que entendi do doc
[b]@RevisionEntity(AuditListener.class)[/b]
@SequenceGenerator(name = "SQ_AUDITORIA", sequenceName = "SQ_AUDITORIA", initialValue = 1,allocationSize = 1)  
public class RevEntity {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO, generator="SQ_AUDITORIA")
    @RevisionNumber
    @Column(name = "AUD_ID")
    private int id;

    @RevisionTimestamp
    @Column(name = "AUD_TIMESTAMP")
    private long timestamp;

    @Column(name = "AUD_NOMEUSUARIO")
    private String username;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public long getTimestamp() {
		return timestamp;
	}

	public void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}
}
package br.com.webtia.contrato.audit;

import org.hibernate.envers.RevisionListener;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import br.com.webtia.contrato.integration.User;
import br.com.webtia.contrato.models.RevEntity;

public class AuditListener implements RevisionListener {	
	public void newRevision(Object revisionEntity) {
        RevEntity actualRevision = (RevEntity) revisionEntity;
        actualRevision.setUsername("Jonatan Lemes");
    }
}

Esse new talvez seja internamente ao framework que faça…alguma ideia??? :wink:

Lucas_Cavalcanti

bom… não tem um jeito muito bonito de fazer isso…

mas vc pode criar a classe:

@Component
@RequestScoped
public class StaticWebScope {
   private static ThreadLocal<WebScope> scopes = new ThreadLocal<WebScope>();
   private WebScope scope;
   public StaticWebScope(WebScope scope) {
       this.scope = scope;
   }
   @PostConstruct
   public void setScope() {
        scopes.set(scope);
   }
   public static WebScope getScope() {
       return scopes.get();
   }
}

e usa isso pra pegar o WebScope…

boneazul

Lucas Cavalcanti:
bom… não tem um jeito muito bonito de fazer isso…

mas vc pode criar a classe:

@Component
@RequestScoped
public class StaticWebScope {
   private static ThreadLocal<WebScope> scopes = new ThreadLocal<WebScope>();
   private WebScope scope;
   public StaticWebScope(WebScope scope) {
       this.scope = scope;
   }
   @PostConstruct
   public void setScope() {
        scopes.set(scope);
   }
   public static WebScope getScope() {
       return scopes.get();
   }
}

e usa isso pra pegar o WebScope…

ta chegando null…fala viu ta fogo isso…

as classes

package br.com.webtia.contrato.util;

import javax.annotation.PostConstruct;


import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.ioc.RequestScoped;

@Component
@RequestScoped
public class StaticWebScope {
   private static ThreadLocal<WebSession> scopes = new ThreadLocal<WebSession>();
   private WebSession scope;
   public StaticWebScope(WebSession scope) {
       System.out.println("Setando sessao");
       this.scope = scope;
   }
   @PostConstruct
   public void setScope() {
        scopes.set(scope);
   }
   public static WebSession getScope() {
       return scopes.get();
   }
}
public class AuditListener implements RevisionListener {	
	public void newRevision(Object revisionEntity) {
        RevEntity actualRevision = (RevEntity) revisionEntity;
        actualRevision.setUsername("Jonatan Lemes");
       //Chegando null
        System.out.println(StaticWebScope.getScope().getObjectSession("user"));
    }
}

estranho coloque um system out no construtor do StaticWebScope mas ele nao imprime nada será q não está carregando??

G

Tentei desse modo mas chega null la no listener…tirando o fato que não implementei num interceptor…
mas faria diferença implementar no interceptor ou não???

Sim, faz diferença porque você precisa “setar” para poder pegar no outro lado. Apenas para conhecimento, o TransactionSynchronizationManager usa ThreadLocal para guardar os objetos que você adiciona nele. Como você colocou o bindResource no Login, na próxima requisição você perdeu as informações porque o TransactionSynchronizationManager é thread-scoped e consecutivamente request-scoped.

Quem controla o ciclo do AuditListener é o Envers que está dentro do Hibernate. Ele é na verdade um entity-listener registrado no contexto do Hibernate.

boneazul

garcia-jj:
Tentei desse modo mas chega null la no listener…tirando o fato que não implementei num interceptor…
mas faria diferença implementar no interceptor ou não???

Sim, faz diferença porque você precisa “setar” para poder pegar no outro lado. Apenas para conhecimento, o TransactionSynchronizationManager usa ThreadLocal para guardar os objetos que você adiciona nele. Como você colocou o bindResource no Login, na próxima requisição você perdeu as informações porque o TransactionSynchronizationManager é thread-scoped e consecutivamente request-scoped.

Quem controla o ciclo do AuditListener é o Envers que está dentro do Hibernate. Ele é na verdade um entity-listener registrado no contexto do Hibernate.

Pior que funcionou cara…muito obrigado pela ajuda dos dois criando o interceptor e a classe TransactionSynchronizationManager estatica do spring funcionou…vlw pela ajuda…

G

Lucas Cavalcanti:
bom… não tem um jeito muito bonito de fazer isso…

mas vc pode criar a classe:

@Component
@RequestScoped
public class StaticWebScope {
   private static ThreadLocal<WebScope> scopes = new ThreadLocal<WebScope>();
   private WebScope scope;
   public StaticWebScope(WebScope scope) {
       this.scope = scope;
   }
   @PostConstruct
   public void setScope() {
        scopes.set(scope);
   }
   public static WebScope getScope() {
       return scopes.get();
   }
}

e usa isso pra pegar o WebScope…

Muito cuidado com isso. Tem que lembrar de sempre limpar um thread-local após o término do request, senão em uma outra requisição um outro usuário pode ter a “sorte” de pegar o mesmo thread e esse objeto estará referenciado nesse thread. Nem preciso dizer a caca que isso pode dar…

Thread local é bem útil, mas tem que trabalhar com cuidado.

Lucas_Cavalcanti

@boneazul
o que está vindo null? o getScope() ou o getObjectSession?

coloque um breakpoint no método setScope do StaticWebScope, e veja se esse método está sendo chamado a toda requisição, automaticamente…

se não estiver, vc vai ter que criar um interceptor que chama esse método manualmente… =/

Lucas_Cavalcanti

Thread local é ERRADO.
é GAMBIARRA…

o problema é que quando uma biblioteca como o Envers não possibilitam que vc injete qqer objeto no
seu listener, vc tem que fazer esse tipo de coisa…

o que pode estar acontecendo tb é que o envers tá fazendo o processamento em outra thread…

@boneazul, tenta imprimir o id da thread no interceptor e dentro do seu listener, e vê se é a mesma thread…

[]'s

boneazul

Lucas Cavalcanti:
@boneazul
o que está vindo null? o getScope() ou o getObjectSession?

coloque um breakpoint no método setScope do StaticWebScope, e veja se esse método está sendo chamado a toda requisição, automaticamente…

se não estiver, vc vai ter que criar um interceptor que chama esse método manualmente… =/

bom a solução do jj-garcia rolou usando TransactionSynchronizationManager ja a sua solucao nao era chamado nunca o componente … o null estava no getScope…todo caso teria que usar um interceptor acho pra instanciar esse cara…sempre no escopo de request…mas pensando que isso é pra todo tempo que o cara estiver logado esse componente nao seria application scoped??

mas pensando no TransactionSynchronizationManager colocado uma vez como recurso…teria que me preocupar em limpar algo???

G

Lucas Cavalcanti:
garcia-jj:

Thread local é bem útil, mas tem que trabalhar com cuidado.

Thread local é ERRADO.
é GAMBIARRA…

Então não olhe o que o Spring usa por baixo. É puro thread-local. Dê uma analisada nos fontes do TransactionSynchronizationManager e olhe nas classes que o usam. Aí você vê que muita coisa no Spring usa implicitamente thread-local. Não vejo o thread-local como errado, mas sim mal usado.

Lucas, o envers está no hibernate como um entity-listener. Realmente a única forma de passar para ele o usuário atual é por meios estáticos, como o exemplo que eu passei acima para JAAS. que a propósito é thread-safe. No meu caso é assim que eu uso em um projeto meu e tem funcionado muito bem.

G

Não, deve ser request porque o TransactionSynchronizationManager usa internamente o thread-local, que é, a grosso modo, request-scoped. Se você não colocar ele como request você vai perder os dados na próxima requisição.

Não porque ele faz isso para você. Dê uma olhada nessa classe nos fontes do spring e você verá que ele usa thread-local e depois de acabar todo ele limpa os dados. Eu não tenho os fontes do Spring aqui e não achei o link.

Lucas_Cavalcanti

pra vc acessar via threadLocal vc teria que atualizar a instância a toda requisição, não tem o que fazer…

não conheço o TransactionSynchronizationManager, mas se ele funciona é só usar…

Vandermm

Esta auditoria serve para saber as atividades de cada usuario no site em determinado horário?

Preciso implementar algo assim. Já pesquisei e vi sugestõres para usar o envers ou log4j, mas não sei exatamente qual a forma mais apropriada.

Meu site usa Vraptor e hibernate.

Alguma sugestão?

Criado 14 de maio de 2010
Ultima resposta 19 de jan. de 2011
Respostas 18
Participantes 4