Hibernate.orgCommunity Documentation

Capítulo 14. Interceptadores e Eventos

14.1. Interceptadores
14.2. Sistema de Eventos
14.3. Segurança declarativa do Hibernate

É muito útil quando a aplicação precisa reagir a certos eventos que ocorrem dentro do Hibernate. Isso permite a implementação de certas funções genéricas, assim como permite estender as funcionalidades do Hibernate.

A interface Interceptor permite fornecer informações da sessão para o aplicativo, permitindo que o aplicativo inspecione e/ou manipule as propriedades de um objeto persistente antes de ser salvo, atualizado, excluído ou salvo. Pode ser usado para gerar informações de auditoria. Por exemplo, o seguinte Interceptor ajusta a função automaticamente createTimestamp quando um Auditable é criado e atualiza a função lastUpdateTimestamp quando um Auditable é atualizado.

Você pode implementar Interceptor diretamente ou pode estender EmptyInterceptor.

package org.hibernate.test;


import java.io.Serializable;
import java.util.Date;
import java.util.Iterator;
import org.hibernate.EmptyInterceptor;
import org.hibernate.Transaction;
import org.hibernate.type.Type;
public class AuditInterceptor extends EmptyInterceptor {
    private int updates;
    private int creates;
    private int loads;
    public void onDelete(Object entity,
                         Serializable id,
                         Object[] state,
                         String[] propertyNames,
                         Type[] types) {
        // do nothing
    }
    public boolean onFlushDirty(Object entity,
                                Serializable id,
                                Object[] currentState,
                                Object[] previousState,
                                String[] propertyNames,
                                Type[] types) {
        if ( entity instanceof Auditable ) {
            updates++;
            for ( int i=0; i < propertyNames.length; i++ ) {
                if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
                    currentState[i] = new Date();
                    return true;
                }
            }
        }
        return false;
    }
    public boolean onLoad(Object entity,
                          Serializable id,
                          Object[] state,
                          String[] propertyNames,
                          Type[] types) {
        if ( entity instanceof Auditable ) {
            loads++;
        }
        return false;
    }
    public boolean onSave(Object entity,
                          Serializable id,
                          Object[] state,
                          String[] propertyNames,
                          Type[] types) {
        if ( entity instanceof Auditable ) {
            creates++;
            for ( int i=0; i<propertyNames.length; i++ ) {
                if ( "createTimestamp".equals( propertyNames[i] ) ) {
                    state[i] = new Date();
                    return true;
                }
            }
        }
        return false;
    }
    public void afterTransactionCompletion(Transaction tx) {
        if ( tx.wasCommitted() ) {
            System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads);
        }
        updates=0;
        creates=0;
        loads=0;
    }
}

Os interceptadores se apresentam de duas formas: Session-scoped e SessionFactory-scoped.

Um interceptador delimitado da Session, é definido quando uma sessão é aberta usando o método sobrecarregado da SessionFactory.openSession() que aceita um Interceptor como parâmetro.

Session session = sf.openSession( new AuditInterceptor() );

Um interceptador da SessionFactory-scoped é definido no objeto Configuration antes da SessionFactory ser instanciada. Nesse caso, o interceptador fornecido será aplicado para todas as sessões abertas por aquela SessionFactory; Isso apenas não ocorrerá caso seja especificado um interceptador no momento em que a sessão for aberta. Um interceptador no escopo de SessionFactory deve ser thread safe. Cetifique-se de não armazenar funções de estado específicos da sessão, pois, provavelmente, múltiplas sessões irão utilizar esse interceptador simultaneamente.

new Configuration().setInterceptor( new AuditInterceptor() );

Se você precisar executar uma ação em determinados eventos da camada de persistência, você também pode usar a arquitetura de event do Hibernate3. Um evento do sistema pode ser utilizado como complemento ou em substituição a um interceptador.

Essencialmente todos os métodos da interface Session possuem um evento correlacionado. Se você tiver um LoadEvent, um LoadEvent, etc. Consulte o DTD do XML de arquivo deconfiguração ou o pacote org.hibernate.event para a lista completa dos tipos de eventos). Quando uma requisição é feita em um desses métodos, a Session do hibernate gera um evento apropriado e o envia para o listener de evento correspondente àquele tipo de evento. Esses listeners implementam a mesma lógica que aqueles métodos, trazendo os mesmos resultados. Entretanto, você é livre para implementar uma customização de um desses listeners (isto é, o LoadEvent é processado pela implementação registrada da interface LoadEventListener), então sua implementação vai ficar responsável por processar qualquer requisição load() feita pela Session.

Para todos os efeitos esses listeners devem ser considerados singletons. Isto significa que eles são compartilhados entre as requisições, e assim sendo, não devem salvar nenhum estado das variáveis instanciadas.

Um listener personalizado deve implementar a interface referente ao evento a ser processado e/ou deve estender a classes base equivalentes (ou mesmo os listeners padrões usados pelo Hibernate, eles não são declarados como finais com esse objetivo). O listener personalizado pode ser registrado programaticamente no objeto Configuration, ou declarativamente no XML de configuração do Hibernate especificado. A configuração declarativa através do arquivo de propriedades não é suportado. Aqui temos um exemplo de como carregar um listener personalizado:

public class MyLoadListener implements LoadEventListener {

    // this is the single method defined by the LoadEventListener interface
    public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
            throws HibernateException {
        if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
            throw MySecurityException("Unauthorized access");
        }
    }
}

Você também precisa adicionar uma entrada no XML de configuração do Hibernate para registrar declarativamente qual listener deve se utilizado em conjunto com o listener padrão:


<hibernate-configuration>
    <session-factory>
        ...
        <event type="load">
            <listener class="com.eg.MyLoadListener"/>
            <listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
        </event>
    </session-factory>
</hibernate-configuration
>

Ou, você pode registrar o listener programaticamente:

Configuration cfg = new Configuration();

LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };
cfg.EventListeners().setLoadEventListeners(stack);

Listeners registrados declarativamente não compartilham da mesma instância. Se o mesmo nome da classe for utilizado em vários elementos <listener/>, cada um resultará em uma instância separada dessa classe. Se você tem a necessidade de compartilhar uma instância de um listener entre diversos tipos de listeners você deve registrar o listener programaticamente.

Mas, por quê implementar uma interface e definir o tipo específico durante a configuração? Bem, um listener pode implementar vários listeners de evento. Com o tipo sendo definido durante o registro, fica fácil ligar ou desligar listeners personalizados durante a configuração.

Geralmente a segurança declarativa nos aplicativos do Hibernate é gerenciada em uma camada de fachada de sessão. Agora o Hibernate3 permite certas ações serem aceitas através do JACC e autorizadas através do JAAS. Esta é uma funcionalidade opcional construída em cima da arquitetura do evento.

Primeiro, você precisa configurar um evento listener apropriado, para possibilitar o uso da autorização JAAS.


<listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/>
<listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/>
<listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>
<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>

Note que <listener type="..." class="..."/> é somente um atalho para <event type="..."><listener class="..."/></event> quando existir somente um listener para um tipo de evento em particular.

Depois disso, ainda em hibernate.cfg.xml, vincule as permissões aos papéis:


<grant role="admin" entity-name="User" actions="insert,update,read"/>
<grant role="su" entity-name="User" actions="*"/>

Os nomes das funções são as funções conhecidas pelo seu provedor JACC.