2005-02-05 20:18:01 -05:00
|
|
|
<chapter id="events">
|
|
|
|
<title>Interceptors and events</title>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
It is often useful for the application to react to certain events that occur
|
|
|
|
inside Hibernate. This allows implementation of certain kinds of generic
|
|
|
|
functionality, and extension of Hibernate functionality.
|
|
|
|
</para>
|
|
|
|
|
2005-08-10 00:54:55 -04:00
|
|
|
<sect1 id="objectstate-interceptors" revision="2">
|
2005-02-05 20:18:01 -05:00
|
|
|
<title>Interceptors</title>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
The <literal>Interceptor</literal> interface provides callbacks from the session to the
|
|
|
|
application allowing the application to inspect and/or manipulate properties of a
|
|
|
|
persistent object before it is saved, updated, deleted or loaded. One
|
|
|
|
possible use for this is to track auditing information. For example, the following
|
|
|
|
<literal>Interceptor</literal> automatically sets the <literal>createTimestamp</literal>
|
|
|
|
when an <literal>Auditable</literal> is created and updates the
|
|
|
|
<literal>lastUpdateTimestamp</literal> property when an <literal>Auditable</literal> is
|
|
|
|
updated.
|
|
|
|
</para>
|
2005-08-10 00:54:55 -04:00
|
|
|
|
|
|
|
<para>
|
|
|
|
You may either implement <literal>Interceptor</literal> directly or (better) extend
|
|
|
|
<literal>EmptyInterceptor</literal>.
|
|
|
|
</para>
|
2005-02-05 20:18:01 -05:00
|
|
|
|
|
|
|
<programlisting><![CDATA[package org.hibernate.test;
|
|
|
|
|
|
|
|
import java.io.Serializable;
|
|
|
|
import java.util.Date;
|
|
|
|
import java.util.Iterator;
|
|
|
|
|
2005-08-10 00:54:55 -04:00
|
|
|
import org.hibernate.EmptyInterceptor;
|
|
|
|
import org.hibernate.Transaction;
|
2005-02-05 20:18:01 -05:00
|
|
|
import org.hibernate.type.Type;
|
|
|
|
|
2005-08-10 00:54:55 -04:00
|
|
|
public class AuditInterceptor extends EmptyInterceptor {
|
2005-02-05 20:18:01 -05:00
|
|
|
|
|
|
|
private int updates;
|
|
|
|
private int creates;
|
2005-08-10 00:54:55 -04:00
|
|
|
private int loads;
|
2005-02-05 20:18:01 -05:00
|
|
|
|
|
|
|
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) {
|
2005-08-10 00:54:55 -04:00
|
|
|
if ( entity instanceof Auditable ) {
|
|
|
|
loads++;
|
|
|
|
}
|
2005-02-05 20:18:01 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2005-08-10 00:54:55 -04:00
|
|
|
public void afterTransactionCompletion(Transaction tx) {
|
|
|
|
if ( tx.wasCommitted() ) {
|
|
|
|
System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads);
|
|
|
|
}
|
2005-02-05 20:18:01 -05:00
|
|
|
updates=0;
|
|
|
|
creates=0;
|
2005-08-10 00:54:55 -04:00
|
|
|
loads=0;
|
2005-02-05 20:18:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
}]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
The interceptor would be specified when a session is created.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
2005-08-10 00:54:55 -04:00
|
|
|
You may also set an interceptor on a global level, using the <literal>Configuration</literal>.
|
|
|
|
In this case, the interceptor must be threadsafe.
|
2005-02-05 20:18:01 -05:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
|
|
|
|
|
|
|
|
</sect1>
|
|
|
|
|
2005-08-10 00:54:55 -04:00
|
|
|
<sect1 id="objectstate-events" revision="3">
|
2005-02-05 20:18:01 -05:00
|
|
|
<title>Event system</title>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
If you have to react to particular events in your persistence layer, you may
|
|
|
|
also use the Hibernate3 <emphasis>event</emphasis> architecture. The event
|
|
|
|
system can be used in addition or as a replacement for interceptors.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Essentially all of the methods of the <literal>Session</literal> interface correlate
|
|
|
|
to an event. You have a <literal>LoadEvent</literal>, a <literal>FlushEvent</literal>, etc
|
|
|
|
(consult the XML configuration-file DTD or the <literal>org.hibernate.event</literal>
|
|
|
|
package for the full list of defined event types). When a request is made of one of
|
|
|
|
these methods, the Hibernate <literal>Session</literal> generates an appropriate
|
|
|
|
event and passes it to the configured event listener for that type. Out-of-the-box,
|
|
|
|
these listeners implement the same processing in which those methods always resulted.
|
|
|
|
However, you are free to implement a customization of one of the listener interfaces
|
|
|
|
(i.e., the <literal>LoadEvent</literal> is processed by the registered implemenation
|
|
|
|
of the <literal>LoadEventListener</literal> interface), in which case their
|
|
|
|
implementation would be responsible for processing any <literal>load()</literal> requests
|
|
|
|
made of the <literal>Session</literal>.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
The listeners should be considered effectively singletons; meaning, they are shared between
|
2005-03-30 07:56:53 -05:00
|
|
|
requests, and thus should not save any state as instance variables.
|
2005-02-05 20:18:01 -05:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
A custom listener should implement the appropriate interface for the event it wants to
|
|
|
|
process and/or extend one of the convenience base classes (or even the default event
|
|
|
|
listeners used by Hibernate out-of-the-box as these are declared non-final for this
|
2005-02-28 07:43:52 -05:00
|
|
|
purpose). Custom listeners can either be registered programmatically through the
|
2005-02-05 20:18:01 -05:00
|
|
|
<literal>Configuration</literal> object, or specified in the Hibernate configuration
|
|
|
|
XML (declarative configuration through the properties file is not supported). Here's an
|
|
|
|
example of a custom load event listener:
|
|
|
|
</para>
|
|
|
|
|
2005-08-10 00:54:55 -04:00
|
|
|
<programlisting><![CDATA[public class MyLoadListener implements LoadEventListener {
|
2005-02-05 20:18:01 -05:00
|
|
|
// this is the single method defined by the LoadEventListener interface
|
2005-08-10 00:54:55 -04:00
|
|
|
public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
|
2005-02-05 20:18:01 -05:00
|
|
|
throws HibernateException {
|
|
|
|
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
|
|
|
|
throw MySecurityException("Unauthorized access");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
2005-08-10 00:54:55 -04:00
|
|
|
You also need a configuration entry telling Hibernate to use the listener in addition
|
|
|
|
to the default listener:
|
2005-02-05 20:18:01 -05:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[<hibernate-configuration>
|
|
|
|
<session-factory>
|
|
|
|
...
|
2005-08-10 00:54:55 -04:00
|
|
|
<event type="load">
|
|
|
|
<listener class="com.eg.MyLoadListener"/>
|
|
|
|
<listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
|
|
|
|
</event>
|
2005-02-05 20:18:01 -05:00
|
|
|
</session-factory>
|
|
|
|
</hibernate-configuration>]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
2005-02-28 07:43:52 -05:00
|
|
|
Instead, you may register it programmatically:
|
2005-02-05 20:18:01 -05:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[Configuration cfg = new Configuration();
|
2005-08-10 00:54:55 -04:00
|
|
|
LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };
|
|
|
|
cfg.EventListeners().setLoadEventListeners(stack);]]></programlisting>
|
2005-02-05 20:18:01 -05:00
|
|
|
|
|
|
|
<para>
|
|
|
|
Listeners registered declaratively cannot share instances. If the same class name is
|
|
|
|
used in multiple <literal><listener/></literal> elements, each reference will
|
2005-02-28 07:43:52 -05:00
|
|
|
result in a separate instance of that class. If you need the capability to share
|
|
|
|
listener instances between listener types you must use the programmatic registration
|
2005-02-05 20:18:01 -05:00
|
|
|
approach.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Why implement an interface and define the specific type during configuration? Well, a
|
|
|
|
listener implementation could implement multiple event listener interfaces. Having the
|
|
|
|
type additionally defined during registration makes it easier to turn custom listeners on
|
|
|
|
or off during configuration.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
</sect1>
|
|
|
|
|
2005-08-10 00:54:55 -04:00
|
|
|
<sect1 id="objectstate-decl-security" revision="2">
|
2005-02-05 20:18:01 -05:00
|
|
|
<title>Hibernate declarative security</title>
|
|
|
|
<para>
|
|
|
|
Usually, declarative security in Hibernate applications is managed in a session facade
|
|
|
|
layer. Now, Hibernate3 allows certain actions to be permissioned via JACC, and authorized
|
|
|
|
via JAAS. This is optional functionality built on top of the event architecture.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
First, you must configure the appropriate event listeners, to enable the use of JAAS
|
2005-03-30 08:27:16 -05:00
|
|
|
authorization.
|
2005-02-05 20:18:01 -05:00
|
|
|
</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>
|
|
|
|
|
2005-08-10 00:54:55 -04:00
|
|
|
<para>
|
|
|
|
Note that <literal><listener type="..." class="..."/></literal> is just a shorthand
|
|
|
|
for <literal><event type="..."><listener class="..."/></event></literal>
|
|
|
|
when there is exactly one listener for a particular event type.
|
|
|
|
</para>
|
|
|
|
|
2005-02-05 20:18:01 -05:00
|
|
|
<para>
|
2005-03-30 08:27:16 -05:00
|
|
|
Next, still in <literal>hibernate.cfg.xml</literal>, bind the permissions to roles:
|
2005-02-05 20:18:01 -05:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[<grant role="admin" entity-name="User" actions="insert,update,read"/>
|
|
|
|
<grant role="su" entity-name="User" actions="*"/>]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
2005-03-30 08:27:16 -05:00
|
|
|
The role names are the roles understood by your JACC provider.
|
2005-02-05 20:18:01 -05:00
|
|
|
</para>
|
|
|
|
|
|
|
|
</sect1>
|
|
|
|
|
|
|
|
</chapter>
|
|
|
|
|