Using a Hibernate Interceptor To Set Audit Trail Properties

Posted on August 27, 2008 by Scott Leberknight

In almost every application I've done, the database tables have some kind of audit trail fields. Sometimes this is a separate "audit log" table where all inserts, updates, deletes, and possibly even queries are logged. Other times there are the four typical audit trail fields in each table, for example you might have created_by, created_on, updated_by, and updated_on fields in each table. The goal in the latter case is to update those four fields with the appropriate information as to who created or updated a record and when they did it. Using s simple Hibernate Interceptor this can be accomplished with no changes to your application code (with several assumptions which I'll detail next). In other words, you won't need to and definitely should not be manually setting those audit properties littered around your application code.

The basic assumptions I'll make for this simple audit interceptor are that: (1) model objects contain the four audit properties mentioned above, and (2) there is an easy way to obtain the current user's information from anywhere in the code. The first assumption is needed since you need some way to identify which properties constitute the audit trail properties. The second assumption is required because you need some way to obtain the credentials of the person making the change in order to set the createdBy or updatedBy property in your Hibernate Interceptor class.

So, for reference purposes, assume you have a (Groovy) base entity like this with the four audit properties:

@MappedSuperclass
class BaseEntity implements Serializable {
  String createdBy
  Date createdOn
  String updatedBy
  Date updatedOn
}

I'm using the Hibernate ImprovedNamingStrategy so that camel case names are translated to underscored names, e.g. "createdBy" becomes "created_by". Next assume there is a BlogEntry entity class that extends BaseEntity and inherits the audit trail properties:

@Entity
class BlogEntry extends BaseEntity {
  @Id @GeneratedValue (strategy = GenerationType.IDENTITY)
  Long id

  @Version
  Long version

  String title

  @Column (name = "entry_text")
  String text

  @Temporal (TemporalType.TIMESTAMP)
  Date publishedOn
}

To implement the interceptor, we need to implement the aforementioned Interceptor interface. We could do this directly, but it is better to extend EmptyInterceptor so we need only implement the methods we actually care about. Without further ado, here's the implementation (excluding package declaration and imports):

class AuditTrailInterceptor extends EmptyInterceptor {

  boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
                      Object[] previousState, String[] propertyNames,
                      Type[] types) {
    setValue(currentState, propertyNames, "updatedBy", UserUtils.getCurrentUsername())
    setValue(currentState, propertyNames, "updatedOn", new Date())
    true
  }

  boolean onSave(Object entity, Serializable id, Object[] state,
                 String[] propertyNames, Type[] types) {
    setValue(state, propertyNames, "createdBy", UserUtils.getCurrentUsername())
    setValue(state, propertyNames, "createdOn", new Date())
    true
  }

  private void setValue(Object[] currentState, String[] propertyNames,
                        String propertyToSet, Object value) {
    def index = propertyNames.toList().indexOf(propertyToSet)
    if (index >= 0) {
      currentState[index] = value
    }
  }
}

So what did we do? First, we implemented the onFlushDirty and onSave methods because they are called for SQL updates and inserts, respectively. For example, when a new entity is first saved, the onSave method is called, at which point we want to set the createdBy and createdOn properties. And if an existing entity is updated, onFlushDirty is called and we set the updatedBy and updatedOn.

Second, we are using the setValue helper method to do the real work. Specfically, the only way to modify the state in a Hibernate Interceptor (that I am aware of anyway) is to dig into the currentState array and change the appropriate value. In order to do that, you first need to trawl through the propertyNames array to find the index of the property you are trying to set. For example, if you are updating a blog entry you need to set the updatedBy and updatedOn properties within the currentState array. For a BlogEntry object, the currentState array might look like this before the update (the updated by and on propertes are both null in this case because the entity was created by Bob but has not been updated yet):

{ 
  "Bob",
   2008-08-27 10:57:19.0,
   null, 
   null, 
   2008-08-27 10:57:19.0, 
   "Lorem ipsum...",
   "My First Blog Entry",
   0
}

You then need to look at the propertyNames array to provide context for what the above data represents:

{
  "createdBy",
  "createdOn",
  "updatedBy",
  "updatedOn",
  "publishedOn",
  "text",
  "title",
  "version"
}

So in the above updatedBy is at index 2 and updatedOn is located at index 3. setValue() works by finding the index of the property it needs to set, e.g. "updatedBy," and if the property was found, it changes the value at that index in the currentState array. So for updatedBy at index 2, the following is the equivalent code if we had actually hardcoded the implementation to always expect the audit fields as the first four properties (which is obviously not a great idea):

// Equivalent hard-coded code to change "updatedBy" in above example
// Don't use in production!
currentState[2] = UserUtils.getCurrentUsername()

To actually make your interceptor do something, you need to enable it on the Hibernate Session. You can do this in one of several ways. If you are using plain Hibernate (i.e. not with Spring or another framework) you can set the interceptor globally on the SessionFactory, or you can enable it for each Session as in the following example code:

// Configure interceptor globally (applies to all Sessions)
sessionFactory =
  new AnnotationConfiguration()
    .configure()
    .setNamingStrategy(ImprovedNamingStrategy.INSTANCE)
    .setInterceptor(new AuditTrailInterceptor())
    .buildSessionFactory()

// Enable per Session
Session session = getSessionFactory().openSession(new AuditTrailInterceptor())

If you enable the interceptor globally, it must be thread-safe. If you are using Spring you can easily configure a global interceptor on your session factory bean:

<bean id="sessionFactory"
      class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
  <property name="entityInterceptor">
    <bean class="com.nearinfinity.hibernate.interceptor.AuditTrailInterceptor"/>
  </property>
  <!-- additional Hibernate configuration properties -->
</bean>

On the other hand, if you would rather enable the interceptor per session, you either need to use the openSession(Interceptor) method to open your sessions or alternatively implement your own version of CurrentSessionContext to use the getCurrentSession() method in order to set the interceptor. Using getCurrentSession() is preferable anyway since it allows several different classes (e.g. DAOs) to use the same session without needing to explicitly pass the Session object around to each object that needs it.

At this point we're done. But, if you know about the Hibernate eventing system (e.g. you can listen for events such as inserts and updates and define event listener classes to respond to those events), you might be wondering why I didn't use that mechanism rather than the Interceptor. The reason is that, to the best of my current knowledge, you cannot alter state of objects in event listeners. So for example you would not be able to change an entity's state in a PreInsertEventListener implementation class. If anyone knows this is incorrect or has implemented it, I'd love to hear about it. Until next time, happy auditing!

Cloud-Oriented Architecture (COA)

Posted on August 18, 2008 by Scott Leberknight

With all the hype this year about cloud computing and things like Amazon EC2/S3 as well as Google App Engine and Bigtable, you can feel it coming. Soon vendors will be peddling COA (Cloud-Oriented Architecture) solutions, probably combining them with their SOA solution and somehow probably getting their ESB solution into the mix as well. This past weekend at the Enterprise Architecture BOF at the Southern Ohio Software Symposium, we had a discussion about cloud computing among other things. Ted Neward even coined the term "Enterprise Service Cloud" and I came up with "Cloud Service Bus," surely the next Big Thing. Any vulture (I mean, venture) capitalists out there want to invest in my new Cloud Service Bus company? I have a pretty brochure ready to go!

The big difference I see with regard to cloud computing is the fact that, unlike your typical ESB/SOA peddling vendors, companies like Amazon and Google already have cloud or cloud-like solutions in place a la Amazon EC2/S3 and , and Google App Engine and Bigtable. Now all of those things just mentioned are not "the cloud" (whatever "the cloud" actually is defined to be), but it doesn't matter because the point is these companies have designed, implemented, and most importantly, run their critical business operations on these platforms. That in and of itself is more important than all the vaporware and marketing hype any other vendor comes up with. Rather than having to get customers to believe that a solution works via marketing and then force it down their IT staff's throats, Google and Amazon are basically saying "Hey why not use stuff that we use and have proven can scale up to handle huge loads and huge amounts of data?"

To me as a developer this is a much more appealing approach for several reasons. First, it means there won't be (or shouldn't need to be) any "golf-course deals" where the vendor sales guys and customer CIOs/CTOs/CEOs meet up and decide on the technology stack independent of any real technical analysis, investigation, or input of the people who will be charged with implementing the vendor stack (and they better do it well else it's their job on the line to boot).

Second, I can base my decision to use a Google or Amazon service based on their actual track record in delivering these services and eating their own dog food, since they are trying to monetize their existing investment in proven highly distributed and scalable infrastructures. Yes, there have been Amazon outages this year and whenever it happens it is big news because it is right there out in the public, as opposed to a company whose IT operations are totally in-house and which isn't going to publicize their downtime statistics. I'd wager on Amazon and Google's availability over probably most other companies. Of course I have no way to prove that last statement, but the mere fact that I can get objective statistics on their services helps in my decision making process and planning.

Last, I can decide how much or how little to outsource to the Amazon or Google infrastructure; for example some organizations might choose to keep their most sensitive data (e.g. customer information, credit card numbers, etc.) in-house but outsource everything else to, say, an Amazon EC2/S3 infrastructure. There is still some level of vendor lock-in here, but there is with anything else short of you implementing your own solution from scratch. And if, by leveraging proven solutions by companies like Amazon and Google, you are able to deliver real value to your customers faster and are able to scale up, out, and beyond without needing to build that infrastructure yourself, then I'd say that could potentially equate to a big win.

So when that vendor comes calling with their shiny new COA solution, be very afraid, and make sure you know your options and present them objectively. We as an industry have more buzzwords and hype (at least from my perspective) than almost any other, and this causes more money than I can possibly imagine to be wasted every year on solutions that don't (and never will) work as advertised. Developers often have a feeling that the VDD (vendor-driven development) solutions just won't work, but cannot convince their managers or their managers' managers of this fact, which is why communications skills are critical in today's world. I don't know about you, but I don't want to be the person who becomes responsible for implementing a solution I don't believe in.

"The Cloud" and cloud computing are definitely here to stay forever, and as Amazon and Google have proven, can add huge amounts of value to businesses. I am sure there will be other companies perhaps trying to implement similar strategies and monetizing their investment in their own infrastructure, and that will be mostly a good thing to have different options and competition to further push the Cloud Service Providers (CSPs) to continually improve their offerings. Get ready, because our toolboxes have just become a lot bigger.

This is the final (and way, way overdue) article in a series of blogs describing how you can effectively use Hibernate validators. The fifth article described how to bypass Hibernate validation in specific use cases, for example if you need to save a "draft" object that should not be validated yet. In this article I'll describe how the Hibernate Validator can be integrated into web applications so that validation errors propagate seamlessly from the data access code back up through the web tier and to the end user as nicely formatted error messages. For this article I'm using Spring MVC, but the basic concept should be applicable no matter which of the 100 billion Java web frameworks you are using.

The basic concept is this: When a user submits a form, you first want to bind the form values to your domain object (making sure of course that you only allow user-editable fields to be bound to the object). You then want to validate the domain object with the updated values. Finally, you want the Hibernate Validator validation errors translated into your web framework's native error validation mechanism so it can inform the user of the errors and do things like display the errors to the user next to the problematic fields. Stated more succinctly, the following steps must occur: submit form, data binding to domain object, validate domain object using Hibernate Validator, translate Hibernate Validator errors to web framework errors, re-display form with nicely formatted error messages to user for correction.

The only piece we don't have is the translation of Hibernate Validator errors into web framework errors. Since I'm using Spring MVC in this case, I'll need to take the Hibernate Validator errors and translate them into a Spring Errors object. For this I can use Spring MVC's Validator interface to implement the error translation in a generic fashion. For this blog the implementation is going to be simple and naive, and won't take into account things like nested properties or internationalization because I want to keep things relatively simple.

So, let's look at the HibernateAnnotationSpringValidator class which is responsible for validating any of our domain objects (which are all assumed to ultimately extend from a custom BaseEntity class for this example) and then translating the Hibernate Validator InvalidValue objects into Spring MVC Errors.

package com.nearinfinity.common.spring.validation.hibernate;

// imports...

public class HibernateAnnotationSpringValidator implements Validator {

  private Map validatorCache = new HashMap();

  public boolean supports(Class clazz) {
    return BaseEntity.class.isAssignableFrom(clazz);
  }

  @SuppressWarnings(CompilerWarnings.UNCHECKED)
  public void validate(Object value, Errors errors) {
    Class type = value.getClass();
    ClassValidator validator = validatorCache.get(type);
    if (validator == null) {
      validator = new ClassValidator(type);
      validatorCache.put(type, validator);
    }
    InvalidValue[] invalidValues = validator.getInvalidValues(value);
    translateToSpringValidationErrors(invalidValues, errors);
  }

  private void translateToSpringValidationErrors(InvalidValue[] invalidValues, Errors errors) {
    for (InvalidValue invalidValue : invalidValues) {
      String propertyName = invalidValue.getPropertyName();
      if (propertyName == null) {
        errors.reject(null, invalidValue.getMessage());
      }
      else {
        String titleCasedPropertyName = StringUtils.camelCaseToTitleCase(propertyName);
        String errorMessage = titleCasedPropertyName + " " + invalidValue.getMessage();
        errors.rejectValue(invalidValue.getPropertyPath(), null, errorMessage);
      }
    }
  }

}

The most important things in the above code are the validate and translateToSpringValidationErrors methods. As expected, validate expects a domain object that extends from BaseEntity and uses Hibernate Validator to validate it. This validator caches Hibernate ClassValidator instances and so one instance of HibernateAnnotationSpringValidator could be used in all of your Spring MVC controllers if desired. It then validates the object and gets back an array of InvalidValue objects from Hibernate Validator. Finally, it calls translateToSpringValidationErrors.

The translateToSpringValidationErrors method iterates through the InvalidValues and transforms them into Spring MVC errors. This implementation is very simplistic and not i18n-ready as it uses the domain object property names to create the error messages using a utility method I wrote called camelCaseToTitleCase. For example, if the property name is "firstName" then the "titleCasedPropertyName" is simply "First Name." So, if the InvalidValue's getMessage() method returns "is required" then the error message would be "First Name is required." Obviously for production use you'll want to make something more robust than this and support i18n if you need to.

Now you have all the pieces and simply need to plug-in the HibernateAnnotationSpringValidator and use as you would any Spring Validator which is documented extensively in the Spring docs and lots of books, blogs, twitters, etc. Obviously this example only works with Spring MVC. To make it work for the web framework you are using, you'll need to hook into your framework's validation mechanism and do something similar. Again, the two most important things are (1) using Hibernate Validator to validate an object (doesn't even need to be a domain object for that matter since Hibernate Validator can be used on its own independent of Hibernate even) and (2) translating the Hibernate validation errors into your web framework's native errors. That's it!