Audit Logging via Hibernate Interceptor (2/2)

Posted on Posted in Java, Technology Center

This is a continuation of the post “Audit Logging via Hibernate Interceptor (1/2)“.

Other helper functions are shown below:

CrudUtil is the class responsible for retrieving object values using Java reflections:

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import com.ideyatech.core.InvalidImplementationException;
import com.ideyatech.core.bean.Auditable;
import com.ideyatech.core.bean.BaseCriteria;
import com.ideyatech.core.bean.BaseEntity;

/**
 * @author allanctan
 *
 */
public class CrudUtil {

    private static Logger _log = Logger.getLogger(CrudUtil.class);

	private static final String SQL_PARAM = ":([^\\s]+)";
	private static final Pattern SQL_PARAM_PATTERN = Pattern.compile(
			SQL_PARAM, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

    /**
     * Creates the logging message for new audit logs
     * @param obj
     * @return
     */
    public static String buildCreateMessage(Auditable obj) {
    	StringBuffer message = new StringBuffer("Added ");
    	message.append(obj.getClass().getSimpleName())  // class name
    		.append(" ")
    		.append(obj.getPrimaryField())
    		.append(":");
    	Object value = retrieveObjectValue(obj, obj.getPrimaryField());
    	if (value!=null)
    		message.append(value.toString())
    		.append(" - ");
    	// loop through the fields list
		List auditFields = obj.getAuditableFields();
		int count = 0;
		for (String property:auditFields) {
			Object ret = retrieveObjectValue(obj, property);
			if (ret!=null && ret.toString().trim().length()>0) {
				if (count > 0)
					message.append(" and ");
				message.append(property)
					.append("=")
					.append(ret.toString());
				count++;
			}
		}

    	return message.toString();
    }

    /**
     * Creates the logging message for update audit logs
     * @param obj
     * @return
     */
    public static String buildUpdateMessage(Auditable oldObject, Auditable newObject) {

    	StringBuffer message = new StringBuffer("Changed ");
    	message.append(oldObject.getClass().getSimpleName())  // class name
    		.append(" ")
    		.append(oldObject.getPrimaryField())
    		.append(":");
    	Object value = retrieveObjectValue(oldObject, oldObject.getPrimaryField());
    	if (value!=null)
    		message.append(value.toString())
    		.append(" - ");
    	// loop through the fields list
		List auditFields = oldObject.getAuditableFields();
		int count = 0;
		for (String property:auditFields) {
			Object oldValue = retrieveObjectValue(oldObject, property);
			Object newValue = retrieveObjectValue(newObject, property);
			if (oldValue == null) oldValue = new String("");
			if (newValue == null) newValue = new String("");
			if (!oldValue.equals(newValue)) {
				if (count > 0)
					message.append(" and ");
				message.append(property)
					.append(" from '")
					.append(oldValue.toString())
					.append("' to '")
					.append(newValue.toString())
					.append("'");
				count++;
			}
		}

    	return message.toString();
    }

    /**
     * Creates the logging message for new audit logs
     * @param obj
     * @return
     */
    public static String buildDeleteMessage(Auditable obj) {
    	StringBuffer message = new StringBuffer("Deleted ");
    	message.append(obj.getClass().getSimpleName())  // class name
    		.append(" ")
    		.append(obj.getPrimaryField())
    		.append(":");
    	Object value = retrieveObjectValue(obj, obj.getPrimaryField());
    	if (value!=null)
    		message.append(value.toString());
    	return message.toString();
    }

    /**
     * Retrieves the property name for a method name.
     * (e.g. getName will return name)
     * @param methodName
     * @return
     */
    public static String getPropertyName(String methodName) {
    	if (StringUtil.isEmpty(methodName) || methodName.length()<=3)
    		return null;
    	if (methodName.startsWith("get") || methodName.startsWith("set")) {
    		String prop = methodName.substring(4);
    		char c = Character.toLowerCase(methodName.charAt(3));
    		return c+prop;
    	} else
    		return null;
    }
    /**
     * Retrieves the getter method name for a given property.
     * (e.g. name will return getName)
     * @param propertyName
     * @return
     */
    public static String getGetterMethodName(String propertyName) {
    	if (StringUtil.isEmpty(propertyName) || propertyName.length()<=0)
    		return null;
    	char c = Character.toUpperCase(propertyName.charAt(0));
    	return "get"+c+propertyName.substring(1);
    }

	/**
	 * This method retrieves the object value that corresponds to the property specified.
	 * This method can recurse inner classes until specified property is reached.
	 *
	 * For example:
	 * obj.firstName
	 * obj.address.Zipcode
	 *
	 * @param obj
	 * @param property
	 * @return
	 */
	public static Object retrieveObjectValue(Object obj, String property) {
		if (property.contains(".")) {
			// we need to recurse down to final object
			String props[] = property.split("\\.");
			try {
				Method method = obj.getClass().getMethod(getGetterMethodName(props[0]));
				Object ivalue = method.invoke(obj);
				if (ivalue==null)
					return null;
				return retrieveObjectValue(ivalue,property.substring(props[0].length()+1));
			} catch (Exception e) {
				_log.error("Failed to retrieve value for "+property);
				throw new InvalidImplementationException("CrudUtil","retrieveObjectValue",null,"", e);
			}
		} else {
			// let's get the object value directly
			try {
				Method method = obj.getClass().getMethod(getGetterMethodName(property));
				return method.invoke(obj);
			} catch (Exception e) {
				_log.error("Failed to retrieve value for "+property);
				throw new InvalidImplementationException("CrudUtil","retrieveObjectValue",null,"", e);
			}
		}
	}

	/**
	 * This method retrieves the object type that corresponds to the property specified.
	 * This method can recurse inner classes until specified property is reached.
	 *
	 * For example:
	 * obj.firstName
	 * obj.address.Zipcode
	 *
	 * @param obj
	 * @param property
	 * @return
	 */
	public static Class retrieveObjectType(Object obj, String property) {
		if (property.contains(".")) {
			// we need to recurse down to final object
			String props[] = property.split("\\.");
			try {
				Method method = obj.getClass().getMethod(getGetterMethodName(props[0]));
				Object ivalue = method.invoke(obj);
				return retrieveObjectType(ivalue,property.substring(props[0].length()+1));
			} catch (Exception e) {
				_log.error("Failed to retrieve value for "+property);
				throw new InvalidImplementationException("CrudUtil","retrieveObjectValue",null,"", e);
			}
		} else {
			// let's get the object value directly
			try {
				Method method = obj.getClass().getMethod(getGetterMethodName(property));
				return method.getReturnType();
			} catch (Exception e) {
				_log.error("Failed to retrieve value for "+property);
				throw new InvalidImplementationException("CrudUtil","retrieveObjectValue",null,"", e);
			}
		}
	}
}

HibernateUtil is a utility to retrieve hibernate session separate from the usual EntityManager.

import java.net.URL;
import java.util.Set;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.scannotation.AnnotationDB;
import org.scannotation.ClasspathUrlFinder;

/**
 * This utility allows creation of Hibernate session directly.
 * Used for logging purposes.
 *
 * @author allantan
 */
public class HibernateUtil {
    private static final SessionFactory sessionFactory;
    static {
        try {
        	URL[] urls = ClasspathUrlFinder.findResourceBases("META-INF/persistence.xml");
        	AnnotationDB db = new AnnotationDB();
        	db.scanArchives(urls);
        	Set entityClasses = db.getAnnotationIndex().get(javax.persistence.Entity.class.getName());
            // Create the SessionFactory
        	AnnotationConfiguration ac =  new AnnotationConfiguration();
            ac.setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/ideyatech");
            ac.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
            for (String clazz:entityClasses) {
            	ac.addAnnotatedClass(Class.forName(clazz));
            }
            sessionFactory = ac.buildSessionFactory();
        } catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
}

And finally, the sample implementation of an Auditable class:

public class InboundDocument implements Auditable {
	private static final long serialVersionUID = -7019861759834380358L;

	@Column(name = "DATE_RECEIVED")
	@Field(index = Index.UN_TOKENIZED, store = Store.YES)
	@DateBridge(resolution = Resolution.DAY)
	private Date dateReceived;

	@Column(name = "NUMBER_OF_DAYS")
	private Integer numberOfDays;

	@Column(name = "DATE_DUE")
	@Field(index = Index.UN_TOKENIZED, store = Store.YES)
	@DateBridge(resolution = Resolution.DAY)
	private Date dateDue;

	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "ACTION_NEEDED")
	@Field(index = Index.TOKENIZED)
	@FieldBridge(impl = SystemCodesBridge.class)
	private SystemCodes actionNeeded;

        ...
        ...

	public List getAuditableFields() {
		List props = new ArrayList();
		props.add("documentType");
		props.add("actionNeeded");
		props.add("dateReceived");
		props.add("dateDue");
		props.add("summary");
		return props;
	}

	public String getPrimaryField() {
		return "barcodeNumber";
	}
}

That’s it! Just implement all your auditable classes with Auditable interface. All the codes above are extracted from open-tides – an open-source web-foundation framework that can be used to quickly setup an enterprise project.



3 thoughts on “Audit Logging via Hibernate Interceptor (2/2)

  1. hi, can you provide the database script?
    or can you tell me what is the data type for object class?
    private Class entityClass;

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.