275 lines
12 KiB
XML
275 lines
12 KiB
XML
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
<chapter id="events">
|
|
<title>Les intercepteurs et les événements</title>
|
|
|
|
<para>
|
|
Il est souvent utile pour l'application de réagir à certains événements
|
|
qui surviennent dans Hibernate. Cela autorise l'implémentation de certaines sortes de
|
|
fonctionnalités génériques, et d'extensions de fonctionnalités d'Hibernate.
|
|
</para>
|
|
|
|
<sect1 id="objectstate-interceptors" revision="3">
|
|
<title>Intercepteurs</title>
|
|
|
|
<para>
|
|
L'interface <literal>Interceptor</literal> fournit des "callbacks" de la session vers l'application
|
|
et permettent à 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'<literal>Interceptor</literal> suivant positionne
|
|
<literal>createTimestamp</literal> quand un <literal>Auditable</literal> est créé
|
|
et met à jour la propriété <literal>lastUpdateTimestamp</literal> quand un
|
|
<literal>Auditable</literal> est mis à jour.
|
|
</para>
|
|
|
|
<para>
|
|
Vous pouvez soit implémenter <literal>Interceptor</literal> directement ou (mieux)
|
|
étendre <literal>EmptyInterceptor</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[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) {
|
|
// ne fait rien
|
|
}
|
|
|
|
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 postFlush(Iterator entities) {
|
|
System.out.println("Creations: " + creates + ", Updates: " + updates);
|
|
}
|
|
|
|
public void afterTransactionCompletion(Transaction tx) {
|
|
if ( tx.wasCommitted() ) {
|
|
System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads);
|
|
}
|
|
updates=0;
|
|
creates=0;
|
|
loads=0;
|
|
}
|
|
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Il y a deux types d'intercepteurs: lié à la <literal>Session</literal> et
|
|
lié à la <literal>SessionFactory</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Un intercepteur lié à la <literal>Session</literal> est défini
|
|
lorsqu'une session est ouverte via l'invocation des méthodes surchargées SessionFactory.openSession()
|
|
acceptant un <literal>Interceptor</literal> (comme argument).
|
|
</para>
|
|
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
|
|
|
|
<para>
|
|
Un intercepteur lié a <literal>SessionFactory</literal> est défini avec l'objet <literal>Configuration</literal>
|
|
avant la construction de la <literal>SessionFactory</literal>. Dans ce cas, les intercepteurs fournis seront
|
|
appliqués à toutes les sessions ouvertes pour cette <literal>SessionFactory</literal>; ceci est vrai
|
|
à moins que la session ne soit ouverte en spécifiant l'intercepteur à utiliser.
|
|
Les intercepteurs liés à la <literal>SessionFactory</literal> doivent être thread safe, faire attention
|
|
à ne pas stocker des états spécifiques de la session puisque plusieurs sessions peuvent utiliser
|
|
l'intercepteur de manière concurrente.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-events" revision="4">
|
|
<title>Système d'événements</title>
|
|
|
|
<para>
|
|
Si vous devez réagir à des événements particuliers dans votre couche de persistance,
|
|
vous pouvez aussi utiliser l'architecture d'<emphasis>événements</emphasis> d'Hibernate3.
|
|
Le système d'événements peut être utilisé en supplément ou en remplacement des interceptors.
|
|
</para>
|
|
|
|
<para>
|
|
Essentiellement toutes les méthodes de l'interface <literal>Session</literal> sont corrélées à
|
|
un événement. Vous avez un <literal>LoadEvent</literal>, un <literal>FlushEvent</literal>, etc
|
|
(consultez la DTD du fichier de configuration XML ou le paquet <literal>org.hibernate.event</literal>
|
|
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
|
|
<literal>Session</literal> 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 <literal>LoadEvent</literal> est traité par
|
|
l'implémentation de l'interface <literal>LoadEventListener</literal> déclarée), dans
|
|
quel cas leur implémentation devrait être responsable du traitement des
|
|
requêtes <literal>load()</literal> faites par la <literal>Session</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
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
|
|
de variables d'instance.
|
|
</para>
|
|
|
|
<para>
|
|
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
|
|
<literal>Configuration</literal>, ou spécifiés la configuration XML d'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 :
|
|
</para>
|
|
|
|
<programlisting><![CDATA[public class MyLoadListener implements LoadEventListener {
|
|
// C'est une simple méthode définie par l'interface LoadEventListener
|
|
public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
|
|
throws HibernateException {
|
|
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
|
|
throw MySecurityException("Unauthorized access");
|
|
}
|
|
}
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Vous avez aussi besoin d'une entrée de configuration disant à Hibernate d'utiliser
|
|
ce listener en plus du listener par défaut :
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<hibernate-configuration>
|
|
<session-factory>
|
|
...
|
|
<event type="load">
|
|
<listener class="com.eg.MyLoadListener"/>
|
|
<listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
|
|
</event>
|
|
</session-factory>
|
|
</hibernate-configuration>]]></programlisting>
|
|
|
|
<para>
|
|
Vous pouvez aussi l'inscrire par programmation :
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Configuration cfg = new Configuration();
|
|
LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };
|
|
cfg.EventListeners().setLoadEventListeners(stack);]]></programlisting>
|
|
|
|
<para>
|
|
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 <literal><listener/></literal>,
|
|
chaque référence sera 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.
|
|
</para>
|
|
|
|
<para>
|
|
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. Avoir en plus le type défini durant l'inscription rend plus facile
|
|
l'activation ou la désactivation pendant la configuration.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-decl-security" revision="2">
|
|
<title>Sécurité déclarative d'Hibernate</title>
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
<para>
|
|
D'abord, vous devez configurer les listeners d'événements appropriés pour permettre
|
|
l'utilisation d'autorisations JAAS.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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"/>]]></programlisting>
|
|
|
|
<para>
|
|
Notez que <literal><listener type="..." class="..."/></literal> est juste un raccourci
|
|
pour <literal><event type="..."><listener class="..."/></event></literal>
|
|
quand il y a exactement un listener pour un type d'événement particulier.
|
|
</para>
|
|
|
|
<para>
|
|
Ensuite, toujours dans <literal>hibernate.cfg.xml</literal>, lier les permissions aux rôles :
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<grant role="admin" entity-name="User" actions="insert,update,read"/>
|
|
<grant role="su" entity-name="User" actions="*"/>]]></programlisting>
|
|
|
|
<para>
|
|
Les noms de rôle sont les rôles compris par votre fournisseur JAAC.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|