Hibernate.orgCommunity Documentation

Chapitre 13. Intercepteurs et événements

13.1. Intercepteurs
13.2. Système d'événements
13.3. Sécurité déclarative de Hibernate

Il est souvent utile pour l'application de réagir à certains événements qui surviennent dans Hibernate. Cela autorise l'implémentation de certaines fonctionnalités génériques, et l'extension de fonctionnalités d'Hibernate.

L'interface Interceptor fournit des "callbacks" de la session vers l'application permettant à l'application de consulter et/ou de manipuler des propriétés d'un objet persistant avant qu'il soit sauvegardé, mis à jour, supprimé ou chargé. Une utilisation possible de cette fonctionnalité est de tracer l'accès à l'information. Par exemple, l'Interceptor suivant positionne createTimestamp quand un Auditable est créé et met à jour la propriété lastUpdateTimestamp quand un Auditable est mis à jour.

Vous pouvez soit implémenter Interceptor directement ou (mieux) étendre 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;
    }
}

Il y a deux types d'intercepteurs : lié à la Session et lié à la SessionFactory.

Un intercepteur lié à la Session est défini lorsqu'une session est ouverte via l'invocation des méthodes surchargées SessionFactory.openSession() acceptant un Interceptor (comme argument).

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

Un intercepteur lié à SessionFactory est enregistré avec l'objet Configuration avant la construction de la SessionFactory. Dans ce cas, les intercepteurs fournis seront appliqués à toutes les sessions ouvertes pour cette SessionFactory; ceci est vrai à moins que la session ne soit ouverte en spécifiant l'intercepteur à utiliser. Les intercepteurs liés à la SessionFactory doivent être thread safe, en faisant attention à ne pas stocker des états spécifiques de la session puisque plusieurs sessions peuvent utiliser cet intercepteur (potentiellement) de manière concurrente.

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

Si vous devez réagir à des événements particuliers dans votre couche de persistance, vous pouvez aussi utiliser l'architecture d'événements de Hibernate3. Le système d'événements peut être utilisé en supplément ou en remplacement des interceptors.

Essentiellement toutes les méthodes de l'interface Session sont corrélées à un événement. Vous avez un LoadEvent, un FlushEvent, etc (consultez la DTD du fichier de configuration XML ou le paquetage org.hibernate.event pour avoir la liste complète des types d'événement définis). Quand une requête est faite à partir d'une de ces méthodes, la Session Hibernate génère un événement approprié et le passe au listener configuré pour ce type. Par défaut, ces listeners implémentent le même traitement dans lequel ces méthodes aboutissent toujours. Cependant, vous êtes libre d'implémenter une version personnalisée d'une de ces interfaces de listener (c'est-à-dire, le LoadEvent est traité par l'implémentation de l'interface LoadEventListener déclarée), dans quel cas leur implémentation devrait être responsable du traitement des requêtes load() faites par la Session.

Les listeners devraient effectivement être considérés comme des singletons ; dans le sens où ils sont partagés entre des requêtes, et donc ne devraient pas sauvegarder des états en tant que variables d'instance.

Un listener personnalisé devrait implémenter l'interface appropriée pour l'événement qu'il veut traiter et/ou étendre une des classes de base (ou même l'événement prêt à l'emploi utilisé par Hibernate comme ceux déclarés non-finaux à cette intention). Les listeners personnalisés peuvent être soit inscrits par programmation à travers l'objet Configuration, ou spécifiés dans la configuration XML de Hibernate (la configuration déclarative à travers le fichier de propriétés n'est pas supportée). Voici un exemple de listener personnalisé pour l'événement de chargement :

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");
        }
    }
}

Vous avez aussi besoin d'une entrée de configuration indiquant à Hibernate d'utiliser ce listener en plus du listener par défaut :


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

Vous pouvez aussi l'inscrire par programmation :

Configuration cfg = new Configuration();

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

Les listeners inscrits déclarativement ne peuvent pas partager d'instances. Si le même nom de classe est utilisée dans plusieurs éléments <listener/>, chaque référence résultera en une instance distincte de cette classe. Si vous avez besoin de la faculté de partager des instances de listener entre plusieurs types de listener, vous devez utiliser l'approche d'inscription par programmation.

Pourquoi implémenter une interface et définir le type spécifique durant la configuration ? Une implémentation de listener pourrait implémenter plusieurs interfaces de listener d'événements. Par ailleurs, le fait de définir le type durant l'inscription, rend l'activation ou la désactivation plus facile au moment de la configuration.

Généralement, la sécurité déclarative dans les applications Hibernate est gérée dans la couche de session. Maintenant, Hibernate3 permet à certaines actions d'être approuvées via JACC, et autorisées via JAAS. Cette fonctionnalité optionnelle est construite au dessus de l'architecture d'événements.

D'abord, vous devez configurer les listeners d'événements appropriés pour permettre l'utilisation d'autorisations 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"/>

Notez que <listener type="..." class="..."/> est juste un raccourci pour <event type="..."><listener class="..."/></event> quand il y a exactement un listener pour un type d'événement particulier.

Ensuite, toujours dans hibernate.cfg.xml, liez les permissions aux rôles :


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

Les noms de rôle sont les rôles compris par votre fournisseur JAAC.