Início > Programação > Criando um Interceptor do Hibernate Com o Spring para Gravar a Trilha de Auditoria

Criando um Interceptor do Hibernate Com o Spring para Gravar a Trilha de Auditoria

Trilha de auditoria é um termo genérico para registro de uma seqüência de atividades. Deve-se registrar e armazenar as atividades do sistema em uma seqüência selecionada por projetistas ou administradores com base na importância dessas atividades para a segurança. Uma auditoria é indispensável para monitoramento relacionado à segurança de qualquer aplicativo baseado em servidor (e-mail, a bancos de dados, web, etc).

Uma trilha de auditoria confiável é uma ferramenta valiosa e normalmente um requisito legal para determinadas indústrias. Trilhas de auditoria são mantidas e todas as atividades envolvendo acesso e modificação de arquivos vulneráveis ou críticos são registradas.

O Hibernate oferece os Interceptors, que fornecem callbacks que permitem a uma aplicação manipular objetos persistentes antes que eles sejam enviados para o banco de dados. Vamos demonstrar como fazer uma trilha de auditoria utilizando o EmptyInterceptor, que é um interceptador que não faz nada, mas será a classe base do nosso Interceptador.

Para facilitar, vamos criar um enum para cada tipo de operação que registraremos na trilha:

public enum Operacao {
	INSERT("INSERT"),
	DELETE("DELETE"),
	UPDATE("UPDATE");

	private String id;

	private Operacao(String id) {
		this.id = id;
	}

	public String getId() {
		return this.id;
	}
}

Como nosso interceptador agirá em todas as operações contra o banco, é interessante marcarmos quais entidades serão auditadas. Com uma interface, fica fácil:

public interface Auditavel {
	public Long getId();

Não há necessidade de exemplificar como uma entidade implementando essa interface ficaria e nem de mostrar como o DAO dela seria, pois não há nada especial. Em seguida, vamos criar o mapeamento de nossa entidade TrilhaAuditoria que, por óbvio, não é Auditavel:

@Entity
@Immutable
@Table(name = "TBL_TRILHA_AUDITORIA")
public class TrilhaAuditoria{

	@Id
	@GeneratedValue(generator = "SEQ", strategy = GenerationType.AUTO)
	@Column(name = "PK_TRILHA")
	private Long id;

	@Column(name = "TIPO_OPERACAO")
	private String operacao;

	@Column(name = "NOME_TABELA")
	private String nomeTabela;

	@Column(name = "FK_REGISTRO")
	private Long fkRegistro;

	@Column(name = "FK_USUARIO_ULTIMA_ALTERACAO")
	private Long idUsuario;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(name = "DATA_ULTIMA_ALTERACAO")
	private Date data;

	public void setData(Date data){
            this.data = data;
        }

	public TrilhaAuditoria() {
	}

	public TrilhaAuditoria(Operacao operacao, String nomeTabela, 
            Long fkRegistro, Long idUsuario) {
		this.operacao = operacao.getId();
		this.nomeTabela = nomeTabela;
		this.fkRegistro = fkRegistro;
		this.idUsuario = idUsuario;
	}
}

Com todos esses elementos criados, vamos passar para a implementação do interceptador. É importante ressaltar que para fazer acesso a banco de dentro de um interceptor é necessário criar uma nova sessão com um interceptor vazio, caso contrário, a tentativa de acesso disparará novamente o interceptor:

public class TrilhaInterceptor extends EmptyInterceptor {

	@Autowired
	private SessionFactory sessionFactory;

	private Set<TrilhaAuditoria> trilhas = new HashSet<TrilhaAuditoria>();

	@Override
	public void onDelete(Object entity, Serializable id, Object[] state, 
             String[] propertyNames, Type[] types) {
		adicionarTrilha(entity, Operacao.DELETE);
	}

	@Override
	public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, 
             Object[] previousState, String[] propertyNames, Type[] types) {
		adicionarTrilha(entity, Operacao.UPDATE);
		return false;
	}

	@Override
	public boolean onSave(Object entity, Serializable id, Object[] state, 
             String[] propertyNames, Type[] types) {
		adicionarTrilha(entity, Operacao.INSERT);
		return false;
	}

	@SuppressWarnings("rawtypes")
	@Override
	public void postFlush(Iterator entidades) {
		try {
			for (TrilhaAuditoria trilha : trilhas) {
				class GravadorTrilha implements Work {
					private TrilhaAuditoria trilha;
					
					GravadorTrilha(TrilhaAuditoria trilha) {
						this.trilha = trilha;
					}
					
					@Override
					public void execute(Connection con) throws SQLException {
					     Session openSession = sessionFactory.openSession(con,
                                                   EmptyInterceptor.INSTANCE);
						try {
							openSession.save(trilha);
							openSession.flush();
						} finally {
							openSession.close();
						}
					}
				}
				trilha.setData(new Date()); // o ideal e buscar a data do banco de dados
				sessionFactory.getCurrentSession().doWork(new GravadorTrilha(trilha));
			}
		} finally {
			trilhas.clear();
		}
	}

	private void adicionarTrilha(Object entidade, Operacao operacao) {
                // Apenas as entidades auditaveis serao adicionadas
		if (entidade instanceof Auditavel) {
			Auditavel auditavel = (Auditavel) entidade;
			Table tabela = auditavel.getClass().getAnnotation(Table.class);
			Long idUsuarioUltimaAlteracao = /*obter id do usuario*/;
			TrilhaAuditoria trilha = new TrilhaAuditoria(operacao, tabela.name(), 
					auditavel.getId(), idUsuarioUltimaAlteracao);
			trilhas.add(trilha);
		}
	}

}

Por último, é só mapear essa bean no arquivo de configuração do Spring:

       <bean id="trailInterceptor" 
           class="br.com.projeto.dao.interceptor.TrilhaInterceptor"/>
        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
                  annotation.AnnotationSessionFactoryBean">
            <!-- (...) Demais propriedades -->
            <property name="entityInterceptor" ref="trailInterceptor"/>
        </bean>

Escolhemos controlar a gravação da trilha pela interface Auditavel, mas você também poderia fazer isso no DAO da sua entidade utilizando a interface Work e uma sessão sem interceptação:

public void salvarSemInterceptor(Entidade entidade){
    try {
        class SalvarWork implements Work {
            private Entidade entidade;
                SalvarWork(Entidade entidade) {
                    this.entidade = entidade;
                }
            @Override
            public void execute(Connection con) throws SQLException {
                Session openSession = sessionFactory.
                    openSession(con, EmptyInterceptor.INSTANCE);
                try {
                    openSession.update(entidade);
                    openSession.flush();
                } finally {
                    openSession.close();
                }
           }
        }
        getSession().doWork(new SalvarWork(entidade));
    } catch (Throwable e) {
        e.printStackTrace();
    }
}
Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: