-->
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.  [ 7 posts ] 
Author Message
 Post subject: Save only change propertys onFlushDirty
PostPosted: Mon Jul 04, 2005 8:00 am 
Newbie

Joined: Tue May 31, 2005 8:48 am
Posts: 11
Location: Blumenau - Santa Catarina - Brasil
Hello

I am use Interceptor to create audit log, but i want save only change propertys in may table. In event OnflushDirty, the parameter propertyNames contain all propertys (both property not change). How do to save only change propertys?

Thanks


Top
 Profile  
 
 Post subject:
PostPosted: Sun Aug 07, 2005 4:13 am 
Newbie

Joined: Tue May 24, 2005 7:08 pm
Posts: 17
Location: Melbourne
You should be using the newValues / oldValues array rather than the properties array. Having said that, I believe there are still bugs in the interceptor, especially when it comes to capturing updates. It has been suggested in other posts that setting select-before-update on your mappings will provide the pre-update values but my experience has been that it does not work.

I have a solution that explicitly gets the current object from the database and compares values to create the audit history. This is achieved using reflection so that any class can be audited regardless of its id type. It can also cater for component mappings and although I haven't tested it I can't see any reason why it shouldn't work for polymorphic mappings as well. It 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.

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 methods but if all you 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.

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 you're hibernate ampped files regardless if they are actually mapped or not. If you add other properties for other reasons there is not differentiation being done on them. I'm sure it's possible to overcome this but I haven't given it too much thought yet.

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

Please be aware this code is not finished and can probably be cleaned up a bit. If anyone can see any big problems with this approach please let me know. I'm pretty new to Java in general so there's potential for misuse of things like reflection in this code.


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 their it would be useful to know how ?

Hope this helps,
rob



Code:
/*
* Created on May 18, 2005
*
* Created By Rob Monie
*/
package com.mycompay.model.audit;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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.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 - Ie.  A component mapped class such as address for user.
* @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();     
      Field[] fields = objectClass.getDeclaredFields();

      // 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);
          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.mycompay.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);

              }

             
          }
      }
}

 

    /**
     * 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();

            if (auth.getPrincipal() != null) {
                return auth.getPrincipal().toString();
            } else {
                return "AnonymousUser";
            }
        } else {
            return "AnonymousUser";
        }
    }

}



Top
 Profile  
 
 Post subject:
PostPosted: Sun Aug 07, 2005 7:29 am 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
It would be great if you can post this on the Wiki, otherwise it will get lost quickly.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Aug 07, 2005 7:28 pm 
Newbie

Joined: Tue May 24, 2005 7:08 pm
Posts: 17
Location: Melbourne
No problems Christian. I've added it here >> http://www.hibernate.org/318.html

There is another entry in the Wiki called History Interceptor which this could possibly have been added to but I have no edit access to that so I added a new entry.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Aug 07, 2005 7:34 pm 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
Thanks


Top
 Profile  
 
 Post subject: getEntity() method?
PostPosted: Tue Aug 30, 2005 5:37 am 
Regular
Regular

Joined: Fri Nov 07, 2003 6:31 am
Posts: 104
Location: Beijing, China
I not sure to understand the usage of the Interceptor getEntity() methods.

Shouldn't it be used to load instance of entity like you do in the onFlushDirty() method:
Code:
Object preUpdateState = session.get(objectClass,  persistedObjectId);


About getEntity method, the doc says
Quote:
Get a fully loaded entity instance that is cached externally

Does that means from the 2nd level cache?

_________________
/nodje


Top
 Profile  
 
 Post subject:
PostPosted: Thu Sep 01, 2005 7:34 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
no, a third party cache system you want to plug.

_________________
Emmanuel


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 7 posts ] 

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.