Audit Logging

Version 2

    It appears to be a common request for users of Hibernate to want to log changes to data in an Audit History table. I'm yet to see any solution that works in the forums, mainly due to problems with getting pre-update values from the database to compare with new values.

     

    Interceptor Approach

    The following audit logging solution explicitly gets the current object from the database and compares values to create the audit history before saving. This is achieved using reflection so that any class can be audited regardless of its id type. It can also cater for component and polymorphic mappings as well. Fields with static, final or transient modifiers are not logged but this can be easily changed. This code does impose some tagging of your hibernate classes with interfaces though which could be potentially be modified to use a more polymorphic approach which I plan to do when I get some time. Any improvements will be posted here.

     

    At the moment these are the requirements for this to work:

    1. All classes to be audited should implement the Auditable interface. This doesn't need to define any specific methods but if all your ids are Integers you could define a getId() method that returns an Integer.

    2. Any component mappings such as Address for a person or company should implement an Interface called Component, or change the code to something else you'd prefer. This tags a property as a component so that the Audit log interceptor will climb into the componenent class and compare its old/new values along with the owning class.

    3. Every class should override toString() and return something useful for logging such as the id. The reason for this is so that when it tries to compare parent classes, it can compare something consistent and useful for the log. Some people may not like tying toString() to such a specific purpose so may choose to do something else.

    4. As mentioned in other posts you must use a different sessionFactory for your auditLogging than your usual DAOs. There is good discussion in other places about the reasons for this.

    5.This will log all properties in your object unless they have static, final or transient modifiers.

    6. The getUserName() method ties this code to Acegi Security so change if you do not use this security framework.

    I initially tried to get this working using Hibernate event listeners but this same code would not work for some reason. It seems to be a bit more fragile in there. From what I gather, the event listeners are the way forward so if anyone gets it working in there please post your code.

    
    
    /*
     * Created on May 18, 2005
     *
     * Created By Rob Monie
     */
    package com.mycompany.model.audit;
    
    import java.io.Serializable;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    import java.util.Arrays;
    import java.util.Date;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Set;
    
    import net.sf.acegisecurity.Authentication;
    import net.sf.acegisecurity.UserDetails;
    import net.sf.acegisecurity.context.ContextHolder;
    import net.sf.acegisecurity.context.security.SecureContext;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.hibernate.CallbackException;
    import org.hibernate.EntityMode;
    import org.hibernate.HibernateException;
    import org.hibernate.Interceptor;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.Transaction;
    import org.hibernate.type.Type;
    
    /**
     * @author Rob
     * 
     * Hibernate Interceptor for logging saves, updates and deletes to the
     * AuditLogRecord Table
     */
    public class AuditLogInterceptor implements Interceptor {
    
        private Log log = LogFactory.getLog(AuditLogInterceptor.class);
    
        private SessionFactory sessionFactory;
        private static final String UPDATE = "update";
        private static final String INSERT = "insert";
        private static final String DELETE = "delete";
    
        /**
         * @param sessionFactory
         *            The sessionFactory to set.
         */
        public void setSessionFactory(SessionFactory sessionFactory) {
            this.sessionFactory = sessionFactory;
        }
    
        private Set inserts = new HashSet();
        private Set updates = new HashSet();
        private Set deletes = new HashSet();
    
        /*
         * (non-Javadoc)
         * 
         * @see net.sf.hibernate.Interceptor#onLoad(java.lang.Object,
         *      java.io.Serializable, java.lang.Object[], java.lang.String[],
         *      net.sf.hibernate.type.Type[])
         */
        public boolean onLoad(Object arg0, Serializable arg1, Object[] arg2, String[] arg3, Type[] arg4)
                throws CallbackException {
            // TODO Auto-generated method stub
            return false;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see net.sf.hibernate.Interceptor#onFlushDirty(java.lang.Object,
         *      java.io.Serializable, java.lang.Object[], java.lang.Object[],
         *      java.lang.String[], net.sf.hibernate.type.Type[])
         */
        public boolean onFlushDirty(Object obj, Serializable id, Object[] newValues, Object[] oldValues,
                String[] properties, Type[] types) throws CallbackException {
    
            if (obj instanceof Auditable) {
                
                Session session = sessionFactory.openSession();
                Class objectClass = obj.getClass();
                String className = objectClass.getName();
    
                // Just get the class name without the package structure 
                String[] tokens = className.split("\\.");
                int lastToken = tokens.length - 1;
                className = tokens[lastToken];
    
                // Use the id and class to get the pre-update state from the database
                Serializable persistedObjectId = getObjectId(obj);
                Object preUpdateState = session.get(objectClass,  persistedObjectId);
                
                try {
                    
                    logChanges(obj, preUpdateState, null, persistedObjectId.toString(), UPDATE, className);
                    
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                
                session.close();
            }
            
            return false;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see net.sf.hibernate.Interceptor#onSave(java.lang.Object,
         *      java.io.Serializable, java.lang.Object[], java.lang.String[],
         *      net.sf.hibernate.type.Type[])
         */
        public boolean onSave(Object obj, Serializable id, Object[] newValues, String[] properties, Type[] types)
                throws CallbackException {
            if (obj instanceof Auditable) {
                
                try {
                    Class objectClass = obj.getClass();
                    String className = objectClass.getName();
                    String[] tokens = className.split("\\.");
                    int lastToken = tokens.length - 1;
                    className = tokens[lastToken];
                    
                    logChanges(obj, null, null, null, INSERT, className);
                    
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            return false;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see net.sf.hibernate.Interceptor#onDelete(java.lang.Object,
         *      java.io.Serializable, java.lang.Object[], java.lang.String[],
         *      net.sf.hibernate.type.Type[])
         */
        public void onDelete(Object obj, Serializable id, Object[] newValues, String[] properties, Type[] types)
                throws CallbackException {
            
    
            if (obj instanceof Auditable) {
    
                try {
                    
                    Class objectClass = obj.getClass();
                    String className = objectClass.getName();
                    String[] tokens = className.split("\\.");
                    int lastToken = tokens.length - 1;
                    className = tokens[lastToken];
                    
                    logChanges(obj, null, null, id.toString(), DELETE, className);
                    
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
    
            }
    
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see net.sf.hibernate.Interceptor#preFlush(java.util.Iterator)
         */
        public void preFlush(Iterator arg0) throws CallbackException {
            // TODO Auto-generated method stub
    
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see net.sf.hibernate.Interceptor#postFlush(java.util.Iterator)
         */
        public void postFlush(Iterator arg0) throws CallbackException {
            log.debug("In postFlush of AuditLogInterceptor..");
    
            Session session = sessionFactory.openSession();
    
            try {
                for (Iterator itr = inserts.iterator(); itr.hasNext();) {
                    AuditLogRecord logRecord = (AuditLogRecord) itr.next();
                    logRecord.setEntityId(getObjectId(logRecord.getEntity()).toString());
                    session.save(logRecord);
                }
                for (Iterator itr = updates.iterator(); itr.hasNext();) {
                    AuditLogRecord logRecord = (AuditLogRecord) itr.next();
                    session.save(logRecord);
                }
                for (Iterator itr = deletes.iterator(); itr.hasNext();) {
                    AuditLogRecord logRecord = (AuditLogRecord) itr.next();
                    session.save(logRecord);
                }
            } catch (HibernateException e) {
                throw new CallbackException(e);
            } finally {
                inserts.clear();
                updates.clear();
                deletes.clear();
                session.flush();
                session.close();
            }
    
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see net.sf.hibernate.Interceptor#isUnsaved(java.lang.Object)
         */
        public Boolean isUnsaved(Object arg0) {
            // TODO Auto-generated method stub
            return null;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see net.sf.hibernate.Interceptor#findDirty(java.lang.Object,
         *      java.io.Serializable, java.lang.Object[], java.lang.Object[],
         *      java.lang.String[], net.sf.hibernate.type.Type[])
         */
        public int[] findDirty(Object arg0, Serializable arg1, Object[] arg2, Object[] arg3, String[] arg4, Type[] arg5) {
            // TODO Auto-generated method stub
            return null;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see net.sf.hibernate.Interceptor#instantiate(java.lang.Class,
         *      java.io.Serializable)
         */
        public Object instantiate(Class arg0, Serializable arg1) throws CallbackException {
            // TODO Auto-generated method stub
            return null;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see org.hibernate.Interceptor#isTransient(java.lang.Object)
         */
        public Boolean isTransient(Object arg0) {
            // TODO Auto-generated method stub
            return null;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see org.hibernate.Interceptor#instantiate(java.lang.String,
         *      org.hibernate.EntityMode, java.io.Serializable)
         */
        public Object instantiate(String arg0, EntityMode arg1, Serializable arg2) throws CallbackException {
            // TODO Auto-generated method stub
            return null;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see org.hibernate.Interceptor#getEntityName(java.lang.Object)
         */
        public String getEntityName(Object arg0) throws CallbackException {
            // TODO Auto-generated method stub
            return null;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see org.hibernate.Interceptor#getEntity(java.lang.String,
         *      java.io.Serializable)
         */
        public Object getEntity(String arg0, Serializable arg1) throws CallbackException {
            // TODO Auto-generated method stub
            return null;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see org.hibernate.Interceptor#afterTransactionBegin(org.hibernate.Transaction)
         */
        public void afterTransactionBegin(Transaction arg0) {
            // TODO Auto-generated method stub
    
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see org.hibernate.Interceptor#beforeTransactionCompletion(org.hibernate.Transaction)
         */
        public void beforeTransactionCompletion(Transaction arg0) {
            // TODO Auto-generated method stub
    
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see org.hibernate.Interceptor#afterTransactionCompletion(org.hibernate.Transaction)
         */
        public void afterTransactionCompletion(Transaction arg0) {
            // clear any audit log records potentially remaining from a rolled back
            // transaction
            inserts.clear();
            updates.clear();
            deletes.clear();
    
        }
    
        
      /**
       * Logs changes to persistent data
     * @param newObject the object being saved, updated or deleted
     * @param existingObject the existing object in the database.  Used only for updates
     * @param parentObject the parent object. Set only if passing a Component object as the newObject
     * @param persistedObjectId the id of the persisted object.  Used only for update and delete
     * @param event the type of event being logged.  Valid values are "update", "delete", "save"
     * @param className the name of the class being logged.  Used as a reference in the auditLogRecord
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private void logChanges(Object newObject, Object existingObject, Object parentObject,
                            String persistedObjectId, String event, String className)
         throws IllegalArgumentException, IllegalAccessException, InvocationTargetException  {     
    
          Class objectClass = newObject.getClass();      
          //get an array of all fields in the class including those in superclasses if this is a subclass.
          Field[] fields = getAllFields(objectClass, null);
    
          // Iterate through all the fields in the object
    
          fieldIteration: for (int ii = 0; ii < fields.length; ii++) {
              
              //make private fields accessible so we can access their values
              fields[ii].setAccessible(true);
              
              //if the current field is static, transient or final then don't log it as 
              //these modifiers are v.unlikely to be part of the data model.
              if(Modifier.isTransient(fields[ii].getModifiers())
                 || Modifier.isFinal(fields[ii].getModifiers())
                 || Modifier.isStatic(fields[ii].getModifiers())) {
                  continue fieldIteration;
              }
              
              String fieldName = fields[ii].getName();
              if(! fieldName.equals("id")) {
                 
              Class interfaces[] = fields[ii].getType().getInterfaces();
                  for (int i = 0; i < interfaces.length;) {
                      if (interfaces[i].getName().equals("java.util.Collection")) {
                          continue fieldIteration;
                          
                      //If the field is a class that is a component (Hibernate mapping type) then iterate through its fields and log them
                      } else if(interfaces[i].getName().equals("com.mycompany.model.audit.Component")){
                          
                         
                          Object newComponent = fields[ii].get(newObject);
                          Object existingComponent = null;
                          
                          if(event.equals(UPDATE)) {
                              existingComponent = fields[ii].get(existingObject);
                              if(existingComponent == null && newComponent != null){
                                  try {
                                      existingComponent = newComponent.getClass().newInstance();
                                  } catch (InstantiationException e) {
                                    // TODO Auto-generated catch block
                                    e.printStackTrace();
                                  } catch (IllegalAccessException e) {
                                    // TODO Auto-generated catch block
                                    e.printStackTrace();
                                  }
                             } else {
                                 //if neither objects contain the component then don't log anything
                                  continue fieldIteration;
                             }
                          }
                          
                          if(newComponent == null) {
                              continue fieldIteration;
                          }
                          
                          logChanges(newComponent, existingComponent, newObject, persistedObjectId, event, className);
                          continue fieldIteration;
                         
                      }
                      i++;
                  }
    
                  String propertyNewState;
                  String propertyPreUpdateState;
    
                  //get new field values
                  try {
                      Object objPropNewState = fields[ii].get(newObject);
                      if (objPropNewState != null) {
                          propertyNewState = objPropNewState.toString();
                      } else {
                          propertyNewState = "";
                      }
    
                  } catch (Exception e) {
                      propertyNewState = "";
                  }
                  
                  if(event.equals(UPDATE)) {
                  
                      try {
                          Object objPreUpdateState = fields[ii].get(existingObject);
                          if (objPreUpdateState != null) {
                              propertyPreUpdateState = objPreUpdateState.toString();
                          } else {
                              propertyPreUpdateState = "";
                          }
                      } catch (Exception e) {
                          propertyPreUpdateState = "";
                      }
                      
                      // Now we have the two property values - compare them
                      if (propertyNewState.equals(propertyPreUpdateState)) {
                          continue; // Values haven't changed so loop to next property
                      } else  {
                          AuditLogRecord logRecord = new AuditLogRecord();
                          logRecord.setEntityName(className);
                          logRecord.setEntityAttribute(fieldName);
                          logRecord.setMessage(event);
                          logRecord.setUpdatedBy(this.getUserName());
                          logRecord.setUpdatedDate(new Date());
                          logRecord.setNewValue(propertyNewState);
                          logRecord.setOldValue(propertyPreUpdateState);
                          logRecord.setEntityId(persistedObjectId);
                          if(parentObject == null) {
                              logRecord.setEntity((Auditable) newObject);
                          } else {
                              logRecord.setEntity((Auditable) parentObject);
                          }
    
                          updates.add(logRecord);
    
                      }
                      
                      
                  } else if(event.equals(DELETE)) {
                          Object returnValue = fields[ii].get(newObject);
                      
                          AuditLogRecord logRecord = new AuditLogRecord();
                          logRecord.setEntityName(className);
                          logRecord.setEntityAttribute(fieldName); 
                          logRecord.setMessage(event);
                          logRecord.setUpdatedBy(this.getUserName());
                          logRecord.setUpdatedDate(new Date());
                          logRecord.setNewValue("");
                          if (returnValue != null)
                              logRecord.setOldValue(returnValue.toString());
                          if (persistedObjectId != null)
                              logRecord.setEntityId(persistedObjectId);
    
                          if(parentObject == null) {
                              logRecord.setEntity((Auditable) newObject);
                          } else {
                              logRecord.setEntity((Auditable) parentObject);
                          }
    
                          deletes.add(logRecord);
                          
                      } else if(event.equals(INSERT)) {
                          
                          Object returnValue = fields[ii].get(newObject);
    
                          AuditLogRecord logRecord = new AuditLogRecord();
                          logRecord.setEntityName(className);
                          logRecord.setEntityAttribute(fieldName); 
                          logRecord.setMessage(event);
                          logRecord.setUpdatedBy(this.getUserName());
                          logRecord.setUpdatedDate(new Date());
                          logRecord.setOldValue("");
    
                          if (returnValue != null) {
                              logRecord.setNewValue(returnValue.toString());
                          } else
                              logRecord.setNewValue("");
     
    
                          if(parentObject == null) {
                              logRecord.setEntity((Auditable) newObject);
                          } else {
                              logRecord.setEntity((Auditable) parentObject);
                          }
    
                          inserts.add(logRecord);
    
                  }
    
                  
              }
          }
    }
    
      
    
        /**
         * Returns an array of all fields used by this object from it's class and all superclasses.
         * @param objectClass the class 
         * @param fields the current field list
         * @return an array of fields
         */
        private Field[] getAllFields(Class objectClass, Field[] fields) {
            
            Field[] newFields = objectClass.getDeclaredFields();
            
            int fieldsSize = 0;
            int newFieldsSize = 0;
            
            if(fields != null) {
                fieldsSize = fields.length;
            }
            
            if(newFields != null) {
                newFieldsSize = newFields.length;
            }
    
            Field[] totalFields = new Field[fieldsSize + newFieldsSize];
            
           if(fieldsSize > 0) {
               System.arraycopy(fields, 0, totalFields, 0, fieldsSize);
           }
           
           if(newFieldsSize > 0) { 
               System.arraycopy(newFields, 0, totalFields, fieldsSize, newFieldsSize);
           }
           
           Class superClass = objectClass.getSuperclass();
           
           Field[] finalFieldsArray;
           
           if (superClass != null && ! superClass.getName().equals("java.lang.Object")) {
               finalFieldsArray = getAllFields(superClass, totalFields);
           } else {
               finalFieldsArray = totalFields;
           }
           
           return finalFieldsArray;
    
        }
    
        /**
         * Gets the id of the persisted object
         * @param obj the object to get the id from
         * @return object Id
         */
        private Serializable getObjectId(Object obj) {
            
            Class objectClass = obj.getClass();
            Method[] methods = objectClass.getMethods();
    
            Serializable persistedObjectId = null;
            for (int ii = 0; ii < methods.length; ii++) {
                // If the method name equals 'getId' then invoke it to get the id of the object.
                if (methods[ii].getName().equals("getId")) {
                    try {
                        persistedObjectId = (Serializable)methods[ii].invoke(obj, null);
                        break;      
                    } catch (Exception e) {
                        log.warn("Audit Log Failed - Could not get persisted object id: " + e.getMessage());
                    }
                }
            }
            return persistedObjectId;
        }
        
        /**
         * Gets the current user's id from the Acegi secureContext
         * 
         * @return current user's userId
         */
        private String getUserName() {
            SecureContext secureContext = (SecureContext) ContextHolder.getContext();
    
            // secure context will be null when running unit tests so leave userId
            // as null
            if (secureContext != null) {
                Authentication auth = (Authentication) ((SecureContext) ContextHolder.getContext()).getAuthentication();
    
                String userName = null;
                if (auth.getPrincipal() instanceof UserDetails) {
                    UserDetails userDetails = (UserDetails) auth.getPrincipal();
                    userName = userDetails.getUsername();
                } else {
                    userName = auth.getPrincipal().toString();
                }
                
                if(userName == null || userName.equals("")) {
                    return "anonymousUser";
                } else {
                    return userName;
                }
                
            } else {
                return "anonymousUser";
            }
        }
    
    }
    
    
    

     

    Pre[X]EventListener Approach

    A simple alternative to the interceptor approach (add capture to the actor/user as needed) is to use the hibernate event model.

    We first need an AuditTrail model that we will use to log CRUD operations:

    CREATE TABLE AUDIT_TRAIL
    (
        ID                          NUMBER NOT NULL,
        ENTITY_ID                   VARCHAR2(50) NOT NULL,
        ENTITY_NAME                 VARCHAR2(50) NOT NULL,
        ENTITY_PROPERTY             VARCHAR2(50) NOT NULL,
        ENTITY_PROPERTY_OLD_VALUE   VARCHAR2(4000),
        ENTITY_PROPERTY_NEW_VALUE   VARCHAR2(4000),
        OPERATION_TYPE              VARCHAR2(50),
        ACTOR_ID                    NUMBER NOT NULL,
        RELEVANCE_TIME              TIMESTAMP(6) NOT NULL,
        CONSTRAINT PK_AUDIT_TRAIL PRIMARY KEY (ID)
    )
    

     

    A possible enhancement would be to add a configuration setting to determine what entities to log (currently logs every entity). Creating an obtrusive IAuditable interface that has a getter/setter for the actor/user can be used to check an instanceof before an insert is made to AuditTrail, but a configuration that holds all of the mappings/entities that need audit trails seem like a better solution? You decide!

    /*
     | File:    HibernateAuditLogListener.java
     | Created: Mar 3, 2008
     | Author:  Will Hoover
     */
    import java.io.Serializable;
    import java.util.Collection;
    import java.util.Date;
    
    import org.hibernate.EntityMode;
    import org.hibernate.HibernateException;
    import org.hibernate.StatelessSession;
    import org.hibernate.cfg.Configuration;
    import org.hibernate.event.Initializable;
    import org.hibernate.event.PreDeleteEvent;
    import org.hibernate.event.PreDeleteEventListener;
    import org.hibernate.event.PreInsertEvent;
    import org.hibernate.event.PreInsertEventListener;
    import org.hibernate.event.PreLoadEvent;
    import org.hibernate.event.PreLoadEventListener;
    import org.hibernate.event.PreUpdateEvent;
    import org.hibernate.event.PreUpdateEventListener;
    import org.mycompany.model.impl.AuditTrail;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * Audit Log Listener is used to log insert, update, delete, and load operations. Complete list of listeners/events can be obtained at <tt>org.hibernate.event.EventListeners</tt>.
     * 
     * @see org.hibernate.event.EventListeners
     * @author whoover
     */
    public final class HibernateAuditLogListener implements
    PreDeleteEventListener, PreInsertEventListener, PreUpdateEventListener,
    PreLoadEventListener, Initializable {
    
        private static final long serialVersionUID = 1L;
        private static final Logger LOG = LoggerFactory.getLogger(HibernateAuditLogListener.class);
        public static final String OPERATION_TYPE_INSERT = "INSERT";
        public static final String OPERATION_TYPE_UPDATE = "UPDATE";
        public static final String OPERATION_TYPE_DELETE = "DELETE";
    
    
        /**
         * {@inheritDoc}
         */
        @Override
        public final void initialize(final Configuration cfg) {
            //
        }
    
        /**
         * Log deletions made to the current model in the the Audit Trail.
         * 
         * @param event
         *            the pre-deletion event
         */
        @Override
        public final boolean onPreDelete(final PreDeleteEvent event) {
            try {
                final Long actorId = 0L;
                final Serializable entityId = event.getPersister().hasIdentifierProperty() ? event.getPersister().getIdentifier(event.getEntity(), event.getPersister().guessEntityMode(event.getEntity())) : null;
                final String entityName = event.getEntity().getClass().toString();
                final Date transTime = new Date(); // new Date(event.getSource().getTimestamp());
    
                // need to have a separate session for audit save
                StatelessSession session = event.getPersister().getFactory().openStatelessSession();
                session.beginTransaction();
    
                if (LOG.isDebugEnabled()) {
                    LOG.debug("{} for: {}, ID: {}, actor: {}, date: {}", new Object[] { entityName, entityId, actorId, transTime });
                }
                session.insert(new AuditTrail(entityId.toString(), entityName, OPERATION_TYPE_DELETE, null, null, OPERATION_TYPE_DELETE, actorId, transTime));
    
                session.getTransaction().commit();
                session.close();
            } catch (HibernateException e) {
                LOG.error("Unable to process audit log for DELETE operation", e);
            }
            return false;
        }
        /**
         * Log insertions made to the current model in the the Audit Trail.
         * 
         * @param event
         *            the pre-insertion event
         */
        @Override
        public final boolean onPreInsert(final PreInsertEvent event) {
            try {
                // TODO : need to get the actor ID somehow
                final Long actorId = 0L;
                final String entityId = event.getPersister().hasIdentifierProperty() ? event.getPersister().getIdentifier(event.getEntity(), event.getPersister().guessEntityMode(event.getEntity()))
                        .toString() : "";
                final String entityName = event.getEntity().getClass().toString();
                final Date transTime = new Date(); // new Date(event.getSource().getTimestamp());
                final EntityMode entityMode = event.getPersister().guessEntityMode(event.getEntity());
                Object newPropValue = null;
    
                // need to have a separate session for audit save
                StatelessSession session = event.getPersister().getFactory().openStatelessSession();
                session.beginTransaction();
    
                for (String propertyName : event.getPersister().getPropertyNames()) {
                    newPropValue = event.getPersister().getPropertyValue(event.getEntity(), propertyName, entityMode);
                    // because we are performing an insert we only need to be concerned will non-null values
                    if (newPropValue != null) {
                        // collections will fire their own events
                        if (!(newPropValue instanceof Collection)) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("{} for: {}, ID: {}, property: {}, value: {}, actor: {}, date: {}", new Object[] { OPERATION_TYPE_INSERT, entityName, entityId, propertyName, newPropValue, actorId, transTime });
                            }
                            session.insert(new AuditTrail(entityId, entityName, propertyName, null, newPropValue != null ? newPropValue.toString() : null, OPERATION_TYPE_INSERT, actorId, transTime));
                        }
                    }
                }
    
                session.getTransaction().commit();
                session.close();
            } catch (HibernateException e) {
                LOG.error("Unable to process audit log for INSERT operation", e);
            }
            return false;
        }
    
        /**
         * Log updates made to the current model in the the Audit Trail.
         * 
         * @param event
         *            the pre-update event
         */
        @Override
        public final boolean onPreUpdate(PreUpdateEvent event) {
            try {
                // TODO : need to get the actor ID somehow
                final Long actorId = 0L;
                final Serializable entityId = event.getPersister().hasIdentifierProperty() ? event.getPersister().getIdentifier(event.getEntity(), event.getPersister().guessEntityMode(event.getEntity()))
                        : null;
                final String entityName = event.getEntity().getClass().toString();
                final Date transTime = new Date(); // new Date(event.getSource().getTimestamp());
                final EntityMode entityMode = event.getPersister().guessEntityMode(event.getEntity());
                Object oldPropValue = null;
                Object newPropValue = null;
    
                // need to have a separate session for audit save
                StatelessSession session = event.getPersister().getFactory().openStatelessSession();
                session.beginTransaction();
    
                // get the existing entity from session so that we can extract existing property values
                Object existingEntity = session.get(event.getEntity().getClass(), entityId);
    
                // cycle through property names, extract corresponding property values and insert new entry in audit trail
                for (String propertyName : event.getPersister().getPropertyNames()) {
                    newPropValue = event.getPersister().getPropertyValue(event.getEntity(), propertyName, entityMode);
                    // because we are performing an insert we only need to be concerned will non-null values
                    if (newPropValue != null) {
                        // collections will fire their own events
                        if (!(newPropValue instanceof Collection)) {
                            oldPropValue = event.getPersister().getPropertyValue(existingEntity, propertyName, entityMode);
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("{} for: {}, ID: {}, property: {}, old value: {}, new value: {}, actor: {}, date: {}", new Object[] { OPERATION_TYPE_UPDATE, entityName, entityId, propertyName, oldPropValue, newPropValue, actorId, transTime });
                            }
                            session.insert(new AuditTrail(entityId.toString(), entityName, propertyName, oldPropValue != null ? oldPropValue.toString() : null, newPropValue != null ? newPropValue
                                    .toString() : null, OPERATION_TYPE_UPDATE, actorId, transTime));
                        }
                    }
                }
    
                session.getTransaction().commit();
                session.close();
            } catch (HibernateException e) {
                LOG.error("Unable to process audit log for UPDATE operation", e);
            }
            return false;
        }
    
        /**
         * Log the loading of the current model in the the Audit Trail.
         * 
         * @param event
         *            the pre-load event
         */
        @Override
        public final void onPreLoad(final PreLoadEvent event) {
            // TODO : complete load audit log if desired
        }
    }
    

     

    We can add the following listener definitions in our hibernate.cfg.xml to tell Hiberante that we are listening:

    <listener type="pre-delete" class="org.mycompany.dao.listener.impl.HibernateAuditLogListener"/>
    <listener type="pre-update" class="org.mycompany.dao.listener.impl.HibernateAuditLogListener"/>
    <listener type="pre-insert" class="org.mycompany.dao.listener.impl.HibernateAuditLogListener"/>
    <listener type="pre-load" class="org.mycompany.dao.listener.impl.HibernateAuditLogListener"/>
    

    Addendum : we can change above to a Post[X]Event, this enables us to get Id and other meta data in a more reliable way than pre[x] events. -- Vyas.