Hibernate.orgCommunity Documentation
It is useful for the application to react to certain events that occur inside Hibernate. This allows for the implementation of generic functionality and the extension of Hibernate functionality.
The Interceptor
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
Interceptor
automatically sets the createTimestamp
when an Auditable
is created and updates the
lastUpdateTimestamp
property when an Auditable
is
updated.
You can either implement Interceptor
directly or extend
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; } }
There are two kinds of inteceptors: Session
-scoped and
SessionFactory
-scoped.
A Session
-scoped interceptor is specified
when a session is opened.
Session session = sf.withOptions( new AuditInterceptor() ).openSession();
A SessionFactory
-scoped interceptor is registered with the Configuration
object prior to building the SessionFactory
. Unless
a session is opened explicitly specifying the interceptor to use, the supplied interceptor
will be applied to all sessions opened from that SessionFactory
. SessionFactory
-scoped
interceptors must be thread safe. Ensure that you do not store session-specific states, since multiple
sessions will use this interceptor potentially concurrently.
new Configuration().setInterceptor( new AuditInterceptor() );
If you have to react to particular events in your persistence layer, you can also use the Hibernate event architecture. The event system can be used in addition, or as a replacement, for interceptors.
Many methods of the Session
interface correlate to an event type. The
full range of defined event types is declared as enum values on
org.hibernate.event.spi.EventType
. When a request is made of one of
these methods, the Hibernate Session
generates an appropriate
event and passes it to the configured event listeners 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 LoadEvent
is processed by the registered implementation
of the LoadEventListener
interface), in which case their
implementation would be responsible for processing any load()
requests
made of the Session
.
See the Hibernate Developer Guide for information on registering custom event listeners.
The listeners should be considered stateless; they are shared between requests, and should not save any state as instance variables.
A custom listener implements 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 purpose). Here is an example of a custom load event listener:
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"); } } }
Usually, declarative security in Hibernate applications is managed in a session facade layer. Hibernate allows certain actions to be permissioned via JACC, and authorized via JAAS. This is an optional functionality that is built on top of the event architecture.
First, you must configure the appropriate event listeners, to enable the use of JACC
authorization. Again, see Hibernate Developer Guide
for the details. Below is an example of an appropriate
org.hibernate.integrator.spi.Integrator
implementation for this purpose.
import org.hibernate.event.service.spi.DuplicationStrategy; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.integrator.spi.Integrator; import org.hibernate.secure.internal.JACCPreDeleteEventListener; import org.hibernate.secure.internal.JACCPreInsertEventListener; import org.hibernate.secure.internal.JACCPreLoadEventListener; import org.hibernate.secure.internal.JACCPreUpdateEventListener; import org.hibernate.secure.internal.JACCSecurityListener; public class JaccEventListenerIntegrator implements Integrator { private static final DuplicationStrategy JACC_DUPLICATION_STRATEGY = new DuplicationStrategy() { @Override public boolean areMatch(Object listener, Object original) { return listener.getClass().equals( original.getClass() ) && JACCSecurityListener.class.isInstance( original ); } @Override public Action getAction() { return Action.KEEP_ORIGINAL; } }; @Override @SuppressWarnings( {"unchecked"}) public void integrate( Configuration configuration, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { boolean isSecurityEnabled = configuration.getProperties().containsKey( AvailableSettings.JACC_ENABLED ); if ( !isSecurityEnabled ) { return; } final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); eventListenerRegistry.addDuplicationStrategy( JACC_DUPLICATION_STRATEGY ); final String jaccContextId = configuration.getProperty( Environment.JACC_CONTEXTID ); eventListenerRegistry.prependListeners( EventType.PRE_DELETE, new JACCPreDeleteEventListener(jaccContextId) ); eventListenerRegistry.prependListeners( EventType.PRE_INSERT, new JACCPreInsertEventListener(jaccContextId) ); eventListenerRegistry.prependListeners( EventType.PRE_UPDATE, new JACCPreUpdateEventListener(jaccContextId) ); eventListenerRegistry.prependListeners( EventType.PRE_LOAD, new JACCPreLoadEventListener(jaccContextId) ); } }
You must also decide how to configure your JACC provider. One option is to tell Hibernate what permissions
to bind to what roles and have it configure the JACC provider. This would be done in the
hibernate.cfg.xml
file.
<grant role="admin" entity-name="User" actions="insert,update,read"/> <grant role="su" entity-name="User" actions="*"/>