-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 45 posts ]  Go to page Previous  1, 2, 3
Author Message
 Post subject:
PostPosted: Thu Mar 23, 2006 6:03 pm 
Newbie

Joined: Tue May 24, 2005 7:08 pm
Posts: 17
Location: Melbourne
Hi Stewart,

The session.flush() on the second hibernate session should cause the updates to the database. Is it possible that your transaction is being rolled back for any reason ? If you're able to isolate some debug messages and post them I may be able to help but as you said, it's been a while and I can't remember exactly the reasons behind requiring the second Hibernate session and any quirks i may have encountered.

There's some cleanup needed to the orginal code I entered on the Wiki but it should still work fine. I'm guessing it's something to do with your configuration but nothing's springing to mind.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Mar 24, 2006 11:24 am 
Regular
Regular

Joined: Sat Nov 19, 2005 2:46 pm
Posts: 69
rmonie wrote:
Hi Stewart,

The session.flush() on the second hibernate session should cause the updates to the database. Is it possible that your transaction is being rolled back for any reason ? If you're able to isolate some debug messages and post them I may be able to help but as you said, it's been a while and I can't remember exactly the reasons behind requiring the second Hibernate session and any quirks i may have encountered.

There's some cleanup needed to the orginal code I entered on the Wiki but it should still work fine. I'm guessing it's something to do with your configuration but nothing's springing to mind.


Good to hear from you Rob. Thank you for replying. Interesting to see "watched topics" working.

I've got my AuditInterceptor wokring well now.
I think the crux of my problem was that I was using session.save() but did not have a transaction started. By adding session.beginTransaction(); early in postFlush() and session.getTransaction().commit(); in place of session.flush() it started working for me.

Interestingly enough, I'm using the same SessionFactory as for my object model.
I'm also using Hibernate 3.1, but I don't know if that would make a difference to expected behaviour.

My version is taken from yours, but I've altered it and it is a quite a bit different in places. Is it worth posting?

Regards,

_________________
Stewart
London, UK


Top
 Profile  
 
 Post subject: please post your implementation stewart
PostPosted: Wed Apr 19, 2006 1:48 am 
Newbie

Joined: Tue Aug 16, 2005 8:28 am
Posts: 15
hi stewart,

your implementation would help a great deal. please post it!

thanks.

_________________
Give Credit Where Due (if I answer your post)


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 19, 2006 5:53 am 
Regular
Regular

Joined: Sat Nov 19, 2005 2:46 pm
Posts: 69
Note that in onFlushDirty() a session is started on the same connection as the parent, whilst in postFlush() a session is started afresh from the sessionFactory with its own transaction.

In session startup code:
Code:
    if(readOnly)
    {
      session = sessionFactory.openSession();
    }
    else
    {
      AuditInterceptor interceptor = new AuditInterceptor();

      session = sessionFactory.openSession(interceptor);

      interceptor.setUser(user);
      interceptor.setSessionFactory(sessionFactory);
      interceptor.setConnection(session.connection());
    }

Persistable interface (akin to Rob's Auditable interface, but I have more uses for it) - ALL of my hibernate objects implement this interface
Code:
package persistence;

import java.io.Serializable;

public interface Persistable extends java.io.Serializable
{
   Serializable getIdentifier();

   boolean isNew(); // Not used by AuditInterceptor
}

The AuditInterceptor:
Code:
package persistence;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.CallbackException;
import org.hibernate.EmptyInterceptor;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.type.Type;

import security.User;

/**
* Hibernate Interceptor for logging saves, updates and deletes to the Audit
* table.
* Does not handle Components! See Rob's original code for this.
* We only have a couple of components for which the added complexity
* was not worth it. Components handled with toString().
*
* @author Stewart Cambridge. Adapted from code by Rob Monie <a
*         href="http://www.hibernate.org/318.html">Audit Logging</a>
*/
public class AuditInterceptor extends EmptyInterceptor
{
   private Log log = LogFactory.getLog( AuditInterceptor.class );

   private SessionFactory sessionFactory = null;

   private Connection connection = null;

   private User user = null;

   private Map<AuditRecord, Persistable> auditRecords = new HashMap<AuditRecord, Persistable>();

   // package access
   AuditInterceptor()
   {
   }

   // package access
   AuditInterceptor( SessionFactory sessionFactory, User user )
   {
      this.sessionFactory = sessionFactory;
      this.user = user;
   }

   void setConnection( Connection connection )
   {
      this.connection = connection;
   }

   void setSessionFactory( SessionFactory sessionFactory )
   {
      this.sessionFactory = sessionFactory;
   }

   void setUser( User user )
   {
      this.user = user;
   }

   public boolean onFlushDirty( Object object, Serializable id,
                        Object[] newValues, Object[] oldValues,
                        String[] properties, Type[] types )
                                                   throws CallbackException
   {
      log.debug( "AuditInterceptor.onFlushDirty() for " + object.toString() );
      if ( object instanceof Persistable )
      {
         Persistable newPersistable = (Persistable) object;

         Session session = null;

         try
         {
            session = sessionFactory.openSession(connection);

            Persistable oldPersistable = (Persistable) session.get(
               newPersistable.getClass(), newPersistable.getIdentifier() );

            logChanges( newPersistable, oldPersistable, AuditRecord.UPDATE );
         }
         catch ( Exception e )
         {
            log.error( e.getMessage(), e );
         }
         finally
         {
            if ( session != null )
        {
          session.close();
        }
         }
      }

    // only return true if we modified any of the Object[] newValues array
      return false;
   }

   public boolean onSave( Object object, Serializable id, Object[] newValues,
                     String[] properties, Type[] types )
                                                throws CallbackException
   {
      log.debug( "AuditInterceptor.onSave() for " + object.toString() );
      if ( object instanceof Persistable )
      {
         try
         {
            logChanges( (Persistable) object, null, AuditRecord.INSERT );
         }
         catch ( Exception e )
         {
            log.error( e.getMessage(), e );
         }
      }
    // only return true if we modified any of the Object[] newValues array
      return false;
   }

   public void onDelete( Object object, Serializable id, Object[] newValues,
                     String[] properties, Type[] types )
                                                throws CallbackException
   {
      log.debug( "AuditInterceptor.onDelete() for " + object.toString() );
      if ( object instanceof Persistable )
      {
         try
         {
            logChanges( null, (Persistable) object, AuditRecord.DELETE );
         }
         catch ( Exception e )
         {
            log.error( e.getMessage(), e );
         }
      }
   }

   public void postFlush( Iterator entities ) throws CallbackException
   {
      log.debug( "AuditInterceptor.postFlush()" );

      Session session = null;
      try
      {
        session = sessionFactory.openSession();
      session.beginTransaction();

         for ( AuditRecord auditRecord : auditRecords.keySet() )
         {
            // get the id value after the new entity has been persisted
            if ( auditRecord.getAction().equals( AuditRecord.INSERT ) )
            {
          Persistable persistable = auditRecords.get( auditRecord );
               auditRecord.setIdentifier( persistable.getIdentifier().toString() );

          // brand new collections will appear as [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
          // unless we get all the id values after the new owning entity has been persisted
          if(auditRecord.getAfterValue().contains("-1")) // -1 indicates unsaved value
          {
            Field field = getField(persistable.getClass(), auditRecord.getField());
               field.setAccessible( true );
            try
            {
              Object object = field.get( persistable );
              if(object instanceof Collection)
                {
                auditRecord.setAfterValue(toString((Collection) object));
                }
            }
            catch(IllegalAccessException e)
            {
                  log.error( "IllegalAccessException on accessing field " +
                  auditRecord.getField() + " on entity " +
                  persistable + ": " +
                  e.getMessage(), e);
            }
          }
            }
            log.debug( "AuditInterceptor: saving AuditRecord "
                     + auditRecord.toString() );
            session.save( auditRecord );
         }
      }
      catch ( HibernateException e )
      {
         log.error( e.getMessage(), e );
         throw new CallbackException( e );
      }
      finally
      {
         auditRecords.clear();
         if ( session != null )
         {
        try
        {
          session.getTransaction().commit();
              session.close();
        }
          catch ( HibernateException e )
          {
             log.error( e.getMessage(), e );
             throw new CallbackException( e );
          }
         }
      }
   }

   public void afterTransactionCompletion( Transaction transaction )
   {
      // clear any audit log records potentially remaining from a rolled back
      // transaction
      auditRecords.clear();
   }

   /**
    * Logs changes to persistent data
    *
    * @param newPersistable
    *            the object being saved, updated
    * @param oldPersistable
    *            the existing object in the database. Used for updates and deletes
    * @param event
    *            the type of event being logged.
    * @throws IllegalArgumentException
    * @throws IllegalAccessException
    * @throws InvocationTargetException
    */
   private void logChanges( Persistable newPersistable,
                     Persistable oldPersistable, String event )
                                                      throws IllegalArgumentException,
                                                      IllegalAccessException,
                                                      InvocationTargetException
   {
      // get an array of all fields in the class including those in
      // superclasses if this is a subclass.
      Field[] fields = getAllFields((newPersistable == null ) ? oldPersistable.getClass() : newPersistable.getClass(), null );

      // Iterate through all the fields in the object
      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;
         }

         String beforeValue = "";
         String afterValue = "";

         if ( oldPersistable != null )
         {
           beforeValue = getFieldValue(fields[ ii ].get( oldPersistable ));
         }

         if ( newPersistable != null )
         {
           afterValue = getFieldValue(fields[ ii ].get( newPersistable ));
         }

         if ( event.equals( AuditRecord.UPDATE ) )
         {
            if ( afterValue.equals( beforeValue ) )
            {
               // Values haven't changed so loop to next property
               continue;
            }
            else
            {
               auditRecords.put(
                  new AuditRecord( user, newPersistable, fields[ ii ]
                     .getName(), event, beforeValue, afterValue ),
                  newPersistable );
            }
         }
         else if ( event.equals( AuditRecord.DELETE ) )
         {
            auditRecords.put( new AuditRecord( user, oldPersistable,
               fields[ ii ].getName(), event, beforeValue, afterValue ),
               oldPersistable );
         }
         else if ( event.equals( AuditRecord.INSERT ) )
         {
            auditRecords.put( new AuditRecord( user, newPersistable,
               fields[ ii ].getName(), event, beforeValue, afterValue ),
               newPersistable );
         }
      }
   }

  private String getFieldValue(Object object)
  {
    String value = "";
      try
      {
         if ( object != null )
      {
            value = (object instanceof Collection) ? toString((Collection) object) : toString(object);
      }
      }
      catch ( Exception e )
      {
         log.warn( e.getMessage(), e );
      }
    return value;
  }

  private static final String START = "[";
  private static final String END = "]";
  private static final String DELIM = ", ";

  private String toString(Collection collection)
  {
    StringBuilder str = new StringBuilder(START);
    for(Object object : collection)
    {
      str.append(toString(object));
      str.append(DELIM);
    }
    if(!collection.isEmpty())
      str.delete(str.lastIndexOf(DELIM), str.length());
    str.append(END);
      return str.toString();
  }

  private String toString(Object object)
  {
      return (object instanceof Persistable) ? ((Persistable) object).getIdentifier().toString() : object.toString();
  }

   /**
    * Returns an array of all fields used by this object from its 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 = ( fields == null ) ? 0 : fields.length;
      int newFieldsSize = ( newFields == null ) ? 0 : 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();

      return ( superClass != null &&
            !superClass.equals( Object.class ) )
      ? getAllFields( superClass, totalFields )
      : totalFields;
   }

  private Field getField( Class clazz, String fieldName)
  {
     Field field = null;
     try
     {
       field = clazz.getDeclaredField(fieldName);
     }
     catch(NoSuchFieldException e)
     {
       Class superClass = clazz.getSuperclass();
       if( superClass != null &&
          !superClass.equals( Object.class ))
         field = getField(superClass, fieldName);
     }
     return field;
  }
}

The AuditRecord object:
Code:
package persistence;

import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;

import security.User;

public class AuditRecord implements Serializable
{
   public static final String INSERT = "INSERT";

   public static final String UPDATE = "UPDATE";

   public static final String DELETE = "DELETE";

   static final HashSet<String> actions = new HashSet<String>();

   private long timeMillis = System.currentTimeMillis();

   private User user = null;

   private String entity = "";

   private String field = "";

   private String identifier = "";

   private String beforeValue = "";

   private String afterValue = "";

   private String action = "";

   static
   {
      actions.add( INSERT );
      actions.add( UPDATE );
      actions.add( DELETE );
   }

   public AuditRecord()
   {
   }

   public AuditRecord( User user, Persistable persistable, String field,
                  String action, String beforeValue, String afterValue )
   {
      this.user = user;
      this.entity = persistable.getClass().getSimpleName();
      this.field = field;
      this.identifier = persistable.getIdentifier().toString();
      this.setAction( action );
      this.beforeValue = beforeValue;
      this.afterValue = afterValue;
   }

   public String getAfterValue()
   {
      return this.afterValue;
   }

   public void setAfterValue( String afterValue )
   {
      this.afterValue = afterValue;
   }

   public String getBeforeValue()
   {
      return this.beforeValue;
   }

   public void setBeforeValue( String beforeValue )
   {
      this.beforeValue = beforeValue;
   }

   public String getEntity()
   {
      return this.entity;
   }

   public void setEntity( String entity )
   {
      this.entity = entity;
   }

   public String getField()
   {
      return this.field;
   }

   public void setField( String field )
   {
      this.field = field;
   }

   public String getIdentifier()
   {
      return this.identifier;
   }

   public void setIdentifier( String identifier )
   {
      this.identifier = identifier;
   }

   public long getTimeMillis()
   {
      return this.timeMillis;
   }

   public void setTimeMillis( long timeMillis )
   {
      this.timeMillis = timeMillis;
   }

   public User getUser()
   {
      return this.user;
   }

   public void setUser( User user )
   {
      this.user = user;
   }

   public String getAction()
   {
      return this.action;
   }

   public void setAction( String action )
   {
      if ( actions.contains( action ) )
      {
         this.action = action;
      }
      else
      {
         throw new IllegalStateException( "Action must be one of: "
                                    + actions.toString() );
      }
   }

   public boolean equals( Object object )
   {
      if ( object == this ) return true;
      if ( !( object instanceof AuditRecord ) ) return false;
      AuditRecord that = (AuditRecord) object;
      return ( this.timeMillis == that.timeMillis
               && this.user.equals( that.user )
               && this.entity.equals( that.entity )
               && this.field.equals( that.field ) && this.identifier
         .equals( that.identifier ) );
   }

   public int hashCode()
   {
      return ( ( 37 + ( (int) ( timeMillis ^ ( timeMillis >>> 32 ) ) ) )
          + (user.hashCode() * entity.hashCode() * field.hashCode() * identifier.hashCode()) );
   }

   public String toString()
   {
      return "[" + timeMillis + "] [" + new Date(timeMillis).toString() + "] [" + user.toString() + "] ["
            + entity + "] [" + field + "] [" + identifier + "] [" + action
            + "] [" + beforeValue + "] [" + afterValue + "]";
   }
}

_________________
Stewart
London, UK


Top
 Profile  
 
 Post subject: Re: please post your implementation stewart
PostPosted: Wed Apr 19, 2006 5:57 am 
Regular
Regular

Joined: Sat Nov 19, 2005 2:46 pm
Posts: 69
pearl8 wrote:
hi stewart,

your implementation would help a great deal. please post it!

thanks.


Hmm, I should have asked you to start a new topic on this, so that you can assign me a credit :-)

_________________
Stewart
London, UK


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 20, 2006 2:04 pm 
Newbie

Joined: Thu Jul 20, 2006 1:56 pm
Posts: 4
We're looking for an audit interceptor solution for our new application at the persistence layer. Will this solution work? Can you include the associated hbn.xml file for the audit class also?
Thanks


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 20, 2006 2:11 pm 
Regular
Regular

Joined: Sat Nov 19, 2005 2:46 pm
Posts: 69
It did work, but we found it unacceptably slow, so we've since gone a slightly different route.

As for the mapping, I have to ask if you're serious.
The AuditRecord is a really simple bean. Can you really not work it out from the java class ?

Code:
  <class name="persistence.objects.AuditRecord" table="log_audit_record">
    <composite-id>
      <key-property name="timeMillis" type="long" column="time_millis" />
      <key-many-to-one name="user" class="objects.security.User" column="associate_id"/>
      <key-property name="entity" type="java.lang.String" column="entity" />
      <key-property name="field" type="java.lang.String" column="field" />
      <key-property name="identifier" type="java.lang.String" column="identifier" />
    </composite-id>
    <property name="beforeValue" type="java.lang.String" update="false" insert="true" column="before_value" />
    <property name="afterValue" type="java.lang.String" update="false" insert="true" column="after_value" />
    <property name="action" type="java.lang.String" update="false" insert="true" column="action" />
  </class>

_________________
Stewart
London, UK


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 20, 2006 2:14 pm 
Newbie

Joined: Thu Jul 20, 2006 1:56 pm
Posts: 4
I'm a newbie to Hibernate from a development standpoint. Sorry to bug you.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 20, 2006 2:21 pm 
Regular
Regular

Joined: Sat Nov 19, 2005 2:46 pm
Posts: 69
It's fair enough to be a newbie and it's not that you're bugging me.
Not at all.

It's just that an audit interceptor isn't exactly a "Hello World" tutorial.
I really would recommend taking baby steps and learning hibernate on a gradual, shallow gradient. You'll end up getting further faster than if you jump in the deep end.

Well, that's my 2p worth.
Good Luck,

_________________
Stewart
London, UK


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 20, 2006 2:24 pm 
Newbie

Joined: Thu Jul 20, 2006 1:56 pm
Posts: 4
If I could, I would but I don't have a choice right now...

If you would be able to share any of your new methods for implementing the interceptor, I'd appreciate it. If you want to post in a new topic, I'd be happy to support it.


Top
 Profile  
 
 Post subject: Any better solutions?
PostPosted: Tue Jul 10, 2007 11:35 am 
Newbie

Joined: Tue Jul 10, 2007 11:27 am
Posts: 2
This solution of using Hibernate Interceptors for auditing is very slow for practical use, is there any other way of auditing? Comparing all the fields to search for the ones to search for the modified fields is very slow. What about using event listeners?
Stewart, you talked about taking a different route, can you please elaborate on that?


Top
 Profile  
 
 Post subject: Re: Any better solutions?
PostPosted: Tue Jul 10, 2007 6:54 pm 
Regular
Regular

Joined: Sat Nov 19, 2005 2:46 pm
Posts: 69
amagrawa wrote:
Stewart, you talked about taking a different route, can you please elaborate on that?

I would love to, but this topic happened a full year ago, and I've since moved onto different projects and forgotten what I myself meant back then.
What I will say is that since that time, I have decided that an audit interceptor is not the best way to do audit logging.
These days I use Spring and Hibernate together. Using Spring with Java 5 generics, I would put audit logging code into a base abstract generic dao class, where I explicitly add a new audit record object to the session.

_________________
Stewart
London, UK


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 10, 2007 7:21 pm 
Newbie

Joined: Tue Jul 10, 2007 11:27 am
Posts: 2
thanks, its good to see prompt reply to a post after 1 year of last posting :-)
I am also using hibernate with spring... will try to implement your suggestion but before that will spend some more time to make Interceptor approach work.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 14, 2007 5:00 pm 
Newbie

Joined: Tue Aug 14, 2007 9:49 am
Posts: 1
I've only been looking at the audit interceptor for about a day now, but I'm wondering two things:

1. Why should the audit logs be recorded on a separate session? Is this to prevent infinite recursion (in that your log records will trigger the interceptor to call itself indirectly)? If that's the case there are checks to prevent that, so I'm guessing that's not the case.

2. Why would you query for the object from the database again in onFlushDirty() ? Why not just compare the oldValues and newValues parameters? Is this just to be safe in case you're trying to update a detached object (which wouldn't have oldValues stored in the session)?


Top
 Profile  
 
 Post subject:
PostPosted: Fri Nov 02, 2007 6:49 am 
Newbie

Joined: Fri Nov 02, 2007 6:47 am
Posts: 1
Hi Stewart!

What do you mean by "session startup code"?

Thanks!

stewart.cambridge wrote:
Note that in onFlushDirty() a session is started on the same connection as the parent, whilst in postFlush() a session is started afresh from the sessionFactory with its own transaction.

In session startup code:
Code:
    if(readOnly)
    {
      session = sessionFactory.openSession();
    }
    else
    {
      AuditInterceptor interceptor = new AuditInterceptor();

      session = sessionFactory.openSession(interceptor);

      interceptor.setUser(user);
      interceptor.setSessionFactory(sessionFactory);
      interceptor.setConnection(session.connection());
    }



Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 45 posts ]  Go to page Previous  1, 2, 3

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.