Showing posts with label hibernate. Show all posts
Showing posts with label hibernate. Show all posts

2010-10-14

On the topic of Grails Audit Logging Plugin version 0.5.4

Last night, I released a new version of my Audit Logging Plugin. Release notes are here. There had been an outstanding compatibility issue with Grails 1.3.x based on JUnit 4's Integration tests. The bug documented here has been open for several months as I haven't had time to come back to the project to work on it.

Normally I don't blog about tickets but I figure this one requires some attention because I've run into a few plugins that I need in my day job that are "broken" in 1.3.x and I'm not sure who else is having these troubles. In the interest of helping the community as a whole I want to cover my experience.

It turns out that GRAILSPLUGINS-2243 (titled "Integration tests with auditable domain class do not observe test transaction boundary") is actually a more accurate description of the actual bug. The symptom is during integration testing you can't delete entities that are audited. If you take the same code and run it in development or production mode you can delete the objects.

The issue with my code is in this block... (extraneous logging and debugging code removed)

Session session = sessionFactory.openSession()
def trans = session.beginTransaction()
def saved = session.merge(auditLogObject)
session.flush()
trans.commit()
session.close()


Keep in mind this block functions exactly as expected in Grails 1.2 under all environments I know of. The one environment this code is horribly wrong is Grails 1.3.x in Integration testing. Only at that time does this code cause a problem.

So let me ask you... what's wrong with this code?

Before addressing the real issue, questions I anticipate:

  1. Why are you opening your own session?

    Because on a transactional database in some cases the audit log must fire after the database insert so that I can log the automatically assigned object Id. This means you can't use the open session without invalidating it.


  2. Why are you using merge and not save?

    Because there are edge cases where the AuditLog object will be saved twice and I don't want to dictate the logic for that in this block to keep it simple


  3. Why are you flushing the session?

    Because we want to make sure the audit log is being written outside the regular transaction cycle and this particular session is going to perform no other job we want to ensure the log is written out to the database



So ... what is wrong with this code again?


def trans = session.beginTransaction()


Apparently, under integration testing in Grails 1.3.x if you start a transaction in the way I have you will cause a problem with the other independent transactions in the rest of the application. This is simply not the behavior I see in my production systems under Grails 1.2 or in my staging systems running Grails 1.3 for that matter.

My solution is a bit of a work around. In the application context initialization for the Audit Logging plugin (the bit that runs just as you start up your grails application I have a block that does this...


if( Environment.getCurrent() != Environment.PRODUCTION && ConfigurationHolder.config.auditLog?.transactional == null ) {
transactional = false
}


... which simply says in environments that are not production (Notice I'm using the Grails 1.1 API to find that breaking compatibility with Grails 1.0) the boolean flag "transactional" is set to false. I then wrapped the begin and commit calls to the transaction around a check on this boolean. For good measure, I gave the end developer the ability to override this behavior using the setting "auditLog.transactional" in their Config.groovy file. (It would figure that I would release this as a feature and someone needs to use those transactions in their integration testing because they run their testing environment against Oracle or something so here's an out for you to get the old behaviors)

What took so much time to find this bug? It's not clear cut basically. I stumbled on this answer by blindly trying different things until I found a combination that worked. Then I tried to build a theory around this result that fit the facts that I had on hand.

My theory is the JUnit 4 integration environment does something to the session factory so it does not really produce new sessions such that the session that I get in my plugin's listener code is not really a separate session or transaction. I call commit on this transaction and when the session is returned to the normal GORM code it is already committed so the actual entity delete call can't happen and is swallowed.

What is disturbing about this is I didn't invent this technique for Audit Logging I'm implementing here. I created this code after being educated by the Hibernate wiki. This means if my theory holds it's not just a Grails application that will observe this strange-ness ... pretty much any Hibernate application with this kind of Audit Logging will be affected when integration tested under JUnit 4.

Beyond just this anyone developing applications that open their own sessions independent of the GORM session may potentially see bugs during integration testing that do not manifest in other environments. That can be a very difficult situation to deal with. So I've over documented things here for the benefit of the community at large.

In other news...

In other places I've noticed a change in Groovy 1.6 and 1.7's builder API. In particular I use RichUI in one project which makes calls like...

builder.yield(" var ${attrs.yuiVariableName}DataSource = new YAHOO.util.XHRDataSource(\"${attrs?.action}\");\n", false)

... under Groovy 1.6 yeild was a builder method.



But, under Groovy 1.7 this method is not present. That means the HTML rendered by code that used the yield and yieldUnescaped calls now create tags <yield> and <yieldUnescaped> which breaks some pages (but not all).

So far these are the only surprising issues I've run into that has kept Grails 1.3.x off of my production servers. I haven't yet seen and great benefit to 1.3 so I can't say I'm deeply compelled to move to the new version.

I am looking to rewrite the plugin in light of GRAILS-5725 but this will be a breaking change. Frankly, it's such a large change I'm surprised it was not pushed to Grails 1.4 ... however, it does not appear to interfere with the operation of plugins using the listener injection technique I am also using so no harm no foul.

I see I've lost a whole star in my plugin ratings so, my apologies to my end users who have not had a plugin that works with your Grails 1.3 integration tests. I sincerely hope I can earn back that extra star from you. Thanks for your support and help. I am reviewing code submissions for another version of the plugin coming up and I greatly appreciate your support.

2008-05-27

A terrible hibernate hack... for Grails...

I'm still "under water" this week but I felt this really deserved an entry. I found this blog entry by Sorin Postelnicu that solved a problem I was having with my Grails configuration. I've set up my database to use utf8 everywhere but my tables are still getting created as latin1 tables. What to do... using Sorin's example I did this:

package org.hibernate.dialect;
/**
* Extends MySQL5InnoDBDialect and sets the default charset to be UTF-8
* @author Sorin Postelnicu
* @since Aug 13, 2007
*/
public class MySQL5UTF8InnoDBDialect extends MySQL5InnoDBDialect {
public String getTableTypeString() {
return " ENGINE=InnoDB DEFAULT CHARSET=utf8";
}
}

The trick here is that Sorin had over-ridden the default table string from hibernate which says "CHARSET=latin1". I've just named the class differently. I put this class in the right package in my Grails project's "src/java" directory and modify my Datasource.groovy to read like this:

dataSource {
configClass = GrailsAnnotationConfiguration.class
pooled = false
driverClassName = "com.mysql.jdbc.Driver"
dialect= org.hibernate.dialect.MySQL5UTF8InnoDBDialect
url = "jdbc:mysql://localhost:3306/grails?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8"
username = "grails"
password = "grails"
dbCreate = "update"
}

... now my GORM generated tables show up in UTF8. But my question to you is ... is this a plugin? is this a patch to hibernate? Is this a hack? You tell me.

2008-04-21

Learning more about Hibernate: Grails Audit Logging v0.2

Very important changes in version 0.2, the mapping method I had written did this:
def nameMap = [:]
for(int ii=0; ii def obj = newState[ii]
entity.properties.each({ key,val ->
if(val.is(obj)) {
nameMap[ii] = key
}
})
}

... but I found that I could do this instead ...
def persister = event.getPersister()
def nameMap = persister.getPropertyNames()


This fixes a bug in the onChange event handler that meant you couldn't see the old value on properties that had been set to null. Hit this in one of my test cases Friday afternoon.

Other tests show that it's impossible to fire onUpdate and not onChange so I've backed out the onUpdate event handler. In my working version I've also allowed each of the other events to catch either old or new values as a LinkedHashMap object.

Other annoyances were that when the version changed this was logged as an event by when the Audit Logger was set to verbose mode. (The version I posed had 'detailed = false' which disabled verbose logging.) I've made 'version' a special property that is not logged or reported as a change.

Some of the changes have the tidy side-effect of making things faster.

2008-04-18

Inside Hibernate Events and Audit Logging with Grails Plugins

Previously, I wrote about how I was able to take components posted on hibernate.org and reference the configuration files of other plug-ins to craft a plug-in for Grails that used the Hibernate Events model to create an Audit Logging system for my Grails projects. I've posted the Grails Audit Logging Plugin that I'm using in my own mission critical projects. You can download and unzip the plug-in (or just install it into a Grails project) to read every delicious line of source code if you want to.

Before yesterday, I had no experience with Hibernate event listeners and no experience with Grails plug-ins yet in a single day I was able to create what might be a rather sophisticated plug-in for Grails using internet available resources. I'd love to know if anyone else uses this project or is inspired by it to do some other work based on it. This is my detailed write-up of the experience.

When I wanted audit logging in Grails, I wanted something that looked like other Grails features. I wanted to create a class that looked like:

class Person {
static auditable = true

Long id
Long version

String firstName
String middleName
String lastName

String email

def onChange = { oldMap,newMap ->
println "Person was changed ..."

oldMap.each({ key, oldVal ->

def newVal = newMap[key]
if(oldVal != newVal) {
println " * $key from $oldVal to $newVal "
}

})
}


}

... and would automatically get Audit Logging and onChange event handlers that could examine the old and new values and act accordingly. (Audit Logging makes our DBA happy. And, we need to have different behaviors based on exactly how a value changed.) It was surprisingly easy to extend Grails to add these behaviors and I can't believe I was able to learn the plug-in architecture, learn Hibernate Events, and create a usable plug-in in only a day. If I can do it you can too.

In this post I'd like to dissect what I did to create an Audit Logging plug-in for Grails. It should shed some light on a few things. Hopefully it will inspire developers who are more talented than I am to do some really great work. There's really not that much to it and you can easily do this for yourself.

Firstly, creating Grails plug-ins is absolutely a joy. Just like creating any other project in grails you start with the command line. To create a plug-in project...

$ grails create-plugin audit-logging

... and the result is a Grails project. All the conventions you would be familiar with in creating a Grails application are there. It's the same set of folders with an additional file... AuditLoggingGrailsPlugin.groovy that appears by magic in the root of the project. The file name is a simple variation of the name you gave the plug-in. This file is one of the things that distinguishes this project as a plug-in project. We'll come back to this file later. But remember this is the file that tells Grails what to do with your plug-in and how to install it into the Spring run-time.

In my project I wanted to create a Groovy class that could be wired into the Hibernate Events Model. I had played with a Hibernate Interceptor but the Hibernate Event model does what I need so well there's really no need for the interceptor. Either way, it really pays to carefully select what events you want to listen to. Certain events will have data that certain other events don't.

After some trial and error I found that for the purpose of an audit log the events that we care about are just before an entity is deleted, just after it is inserted, and just after it has been updated.

  • You want to be invoked before the entity is deleted so you can optionally log all the values that the entity used to have before it was destroyed.

  • You want to be invoked after the entity is inserted so you can reference any auto generated id numbers or version numbers and log them.

  • You want to be invoked after an update because the PostUpdateEvent object will have the old state and the current state (or new state) of the entity in it and you can log any changes.


So let's first create a Listener class that implements these listeners:

class AuditLogListener implements PreDeleteEventListener,
PostInsertEventListener, PostUpdateEventListener, Initializable {


Each of those interfaces will demand their own methods... specifically...

public boolean onPreDelete(final PreDeleteEvent event) {

...

public void onPostInsert(final PostInsertEvent event) {

... and ...

public void onPostUpdate(final PostUpdateEvent event) {

we want to go "static typed" here since the classes calling us are Java classes and we want to be very clear which method it is that implements the interface.

While I've only used three event listeners here, it's true of all the pre-event listeners in hibernate that they return true if and only if the alter something in the event. So to be extra cautious here we've declared our parameters final. We won't be mucking about with event objects before or after the fact. The final keyword makes this explicit.

Next, we'll want to record log entries from the listener class. To do this my plug-in introduces an additional domain class. If the root folder of our project is audit-logging then the domain class is at audit-logging/grails-app/domain/AuditLogEvent.groovy and looks like this:

class AuditLogEvent {
Long id
Long version
Date dateCreated
Date lastUpdated

String actor
String className
String persistedObjectId

String eventName
String propertyName
String oldValue
String newValue

static constraints = {
actor(nullable:true)
className(nullable:true)
persistedObjectId(nullable:true)
eventName(nullable:true)
propertyName(nullable:true)
oldValue(nullable:true)
newValue(nullable:true)
}

static mapping = {
table 'audit_log'
}
}

... this class isn't special. It's just another domain class like any other GORM domain class. In fact if I wanted to be evil I could label the AuditLogEvent class as an "auditable" class for my plug-in later. That would mean I would get trapped in an infinite recursion of audit logging. The first logged insert into the database would trigger an audit log insert which would trigger an audit log insert which would trigger an audit log insert... and so on...

I elected to map this domain class to the table 'audit_log' and that's just a personal taste issue. I feel that one row in the audit log is a single event but the whole list of events is a log. I hope it doesn't confuse anyone. It does read better from SQL for our DBA.

Grails will allow us to call save on a new object of class AuditLogEvent from inside the listener so we don't have to mess around with pulling out session factories from our event objects. This is a really nice feature of working with GORM in Grails.

To do really simple audit logging from any of our events all we need to do in the Grails environment is:

def entity = event.getEntity()

def className = entity.getClass().getName()
def eventName = 'INSERT'
def persistedObjectId = getEntityId(event)

new AuditLogEvent(
className:className,
eventName:eventName,
persistedObjectId:persistedObjectId.toString()
).save()

In a straight Spring environment we would have a great deal more code to write. Take a look at what is essentially the same event handler in straight Java and Hibernate and compare that to the Grails listener in my project, it's much more work to handle the event in straight Java and Hibernate. Groovy and Grails make the job much easier.

One of the things that was much easier for example was to have the onChange handler receive the "old" and "new" states as a Map objects. Firstly, the map objects are at least more mnemonic to work with than arrays of Objects secondly... creating the maps was much simpler in Groovy. Let me explain...

The onPostUpdateEvent event provides us with two arrays of objects. First is the OldState array and second is the State array. I could not find an API that could tell me what parameter is in what ordinal position in these Object arrays. That meant that I could see what things changed from and to... but not which things... when I reference the entity it is the entity as it exists in its new state. But, there's no corresponding old state. So I wrote a block of Groovy to create a mapping of ordinal positions to property key names. It was surprisingly easy going take a look...

def nameMap = [:]
for(int ii=0; ii<newState.length; ii++) {
def obj = newState[ii]
entity.properties.each({ key,val ->
if(val.is(obj)) {
nameMap[ii] = key
}
})
}

... at the end of that I have a mapping of numbers to keys. I do this by using the Groovy is method. This is the Groovy identity comparison. Remember, since equality in Groovy means value equality (an intuitive meaning) you need a different operator to compare identity. Now I can build my newMap and my oldMap like so...

for(int ii=0; ii<newState.length; ii++) {
oldMap[nameMap[ii]] = oldState[ii]
newMap[nameMap[ii]] = newState[ii]
}

... and the maps are ready to be used as parameters to my logger method so it can record what column changed and what the new and old values are. And as a nice side effect I can now invoke the onChange handler with old and new maps representing the old and new parameters respectively.

I could go line by line through the event listener but I don't think I'll do that in this post. Instead I'll post the listener and let the interested pick it apart. Remember you can see the source code in the plug-in download too...

/**
* @author shawn hartsock
*
* Grails Hibernate Interceptor for logging
* saves, updates, deletes and acting on
* individual properties changes... and
* delegating calls back to the Domain Class
*
* This class is based on the post at
* http://www.hibernate.org/318.html
*
* 2008-04-17 created initial version 0.1
*/

import org.hibernate.EntityMode;
import org.hibernate.StatelessSession;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration;
import org.hibernate.event.Initializable;
import org.hibernate.event.PreDeleteEvent;
import org.hibernate.event.PreDeleteEventListener;
import org.hibernate.event.PostInsertEvent;
import org.hibernate.event.PostInsertEventListener;
import org.hibernate.event.PostUpdateEvent;
import org.hibernate.event.PostUpdateEventListener;

public class AuditLogListener implements PreDeleteEventListener,
PostInsertEventListener, PostUpdateEventListener, Initializable {
static detailed = false // TODO back off into configuration file

// only returns auditable entities
def isAuditableEntity(event) {
if(event && event.getEntity()) {
def entity = event.getEntity()
if(entity.metaClass.hasProperty(entity,'auditable') && entity.'auditable') {
return true
}
}
return false
}

/**
* we allow user's to specify
* static auditable = [handlersOnly:true]
* if they don't want us to log events for them and instead have their own plan.
*/
boolean callHandlersOnly(entity) {
if(entity && entity.metaClass.hasProperty(entity,'auditable') && entity.'auditable') {
if(entity.auditable.getClass() == java.util.LinkedHashMap.class &&
entity.auditable.containsKey('handlersOnly')) {
return (entity.auditable['handlersOnly'])?true:false
}
return false
}
return false
}

def getEntityId(event) {
if(event && event.entity) {
def entity = event.getEntity()
if(entity.metaClass.hasProperty(entity,'id') && entity.'id') {
return entity.id.toString()
}
else {
// trying complex entity resolution
return event.getPersister().hasIdentifierProperty() ? event.getPersister().getIdentifier(event.getEntity(), event.getPersister().guessEntityMode(event.getEntity())) : null;
}
}
return null
}

/**
* We must use the preDelete event if we want to capture
* what the old object was like.
*/
public boolean onPreDelete(final PreDeleteEvent event) {
try {
if(isAuditableEntity(event) && ! callHandlersOnly(event.getEntity())) {
def entity = event.getEntity()
def actor = "" // Need to figure out how to get the actor id somehow...
def entityName = entity.getClass().getName()
def entityId = getEntityId(event)
logChanges(null,event.getEntity().properties,null,entityId,'DELETE',entityName)
}
executeSimpleHandler(event,'onDelete')
} catch (HibernateException e) {
println "Audit Plugin unable to process DELETE event"
e.printStackTrace()
}
return false
}

/**
* I'm using the post insert event here instead of the pre-insert
* event so that I can log the id of the new entity after it
* is saved. That does mean the the insert event handlers
* can't work the way we want... but really it's the onChange
* event handler that does the magic for us.
*/
public void onPostInsert(final PostInsertEvent event) {
try {
if(isAuditableEntity(event) && ! callHandlersOnly(event.getEntity())) {
def entity = event.getEntity()
def actor = "" // Need to figure out how to get the actor id somehow...
def entityName = entity.getClass().getName()
def entityId = getEntityId(event)
logChanges(event.getEntity().properties,null,null,entityId,'INSERT',entityName)
}
executeSimpleHandler(event,'onSave')
} catch (HibernateException e) {
println "Audit Plugin unable to process INSERT event"
e.printStackTrace()
}
return;
}

/**
* Now we get fancy. Here we want to log changes...
* specifically we want to know what property changed,
* when it changed. And what the differences were.
*
* This works better from the onPreUpdate event handler
* but for some reason it won't execute right for me.
* Instead I'm doing a rather complex mapping to build
* a pair of state HashMaps that represent the old and
* new states of the object.
*
* The old and new states are passed to the object's
* 'onChange' event handler. So that a user can work
* with both sets of values.
*
* Needs complex testing BTW.
*/
public void onPostUpdate(final PostUpdateEvent event) {
def entity = event.getEntity()
def entityName = entity.getClass().getName()
def entityId = event.getId()

// object arrays representing the old and new state
def oldState = event.getOldState()
def newState = event.getState()

// The event API only gives us an array of
// objects representing the new and old states
// so we need to use the identity comparison to
// create a name mapping for each state's
// ordinal positions.
def nameMap = [:]
for(int ii=0; ii<newState.length; ii++) {
def obj = newState[ii]
entity.properties.each({ key,val ->
if(val.is(obj)) {
nameMap[ii] = key
}
})
}
def oldMap = [:]
def newMap = [:]
for(int ii=0; ii<newState.length; ii++) {
oldMap[nameMap[ii]] = oldState[ii]
newMap[nameMap[ii]] = newState[ii]
}

// allow user's to over-ride whether you do auditing for them.
if(! callHandlersOnly(event.getEntity())) {
logChanges(newMap,oldMap,event,entityId,'UPDATE',entityName)
}

executeSimpleHandler(event,'onUpdate')
if(hasChanged(oldState,newState)) {
executeComplexHandler(event,'onChange',oldMap,newMap)
}
}

private boolean hasChanged(oldState,newState) {
for(int ii = 0; ii if(oldState[ii] != newState[ii]) {
return true
}
}
return false
}

public void initialize(final Configuration config) {
// TODO Auto-generated method stub
return
}

static excludedProps = [
'id','version',
'log','class','errors',
'auditable','constraints',
'mapping','searchable'
]
def logChanges(newMap,oldMap,parentObject,persistedObjectId,eventName,className) {
if(newMap && oldMap) {
newMap.each({key,val->
if(!excludedProps.contains(key)) {
if(val != oldMap[key]) {
def audit = new AuditLogEvent(
actor:'', // TODO figure out how to log the actor later
className:className,
eventName:eventName,
persistedObjectId:persistedObjectId.toString(),
propertyName:key,
oldValue:oldMap[key].toString(),
newValue:newMap[key].toString()
)
if(!audit.save()) {
println audit.errors
}
}
}
})
}
else if(newMap && detailed) {
newMap.each({key,val->
if(!excludedProps.contains(key)) {
def audit = new AuditLogEvent(
actor:'', // TODO figure out how to log the actor later
className:className,
eventName:eventName,
persistedObjectId:persistedObjectId.toString(),
propertyName:key,
oldValue:null,
newValue:val.toString(),
)
if(!audit.save()) {
println audit.errors
}
}
})
}
else if(oldMap && detailed) {
oldMap.each({key,val->
if(!excludedProps.contains(key)) {
def audit = new AuditLogEvent(
actor:'', // TODO figure out how to log the actor later
className:className,
eventName:eventName,
persistedObjectId:persistedObjectId.toString(),
propertyName:key,
oldValue:val.toString(),
newValue:null
)
if(!audit.save()) {
println audit.errors
}
}
})
}
else {
// I think this is dead code now...
def audit = new AuditLogEvent(
actor:'', // TODO figure out how to log the actor later
className:className,
eventName:eventName,
persistedObjectId:persistedObjectId.toString()
)
if(!audit.save()) {
// TODO don't use println...
println audit.errors
}
}
return
}

def executeSimpleHandler(event,handler) {
def entity = event.getEntity()
if(isAuditableEntity(event) && entity.metaClass.hasProperty(entity,handler)) {
try {
entity."${handler}"(getEntityId(event))
} catch (groovy.lang.MissingMethodException mme) {
// TODO don't use println here...
println "${handler} has to take no parameters!"
}
}
}

def executeComplexHandler(event,handler,oldState,newState) {
def entity = event.getEntity()
if(isAuditableEntity(event) && entity.metaClass.hasProperty(entity,handler)) {
try {
entity."${handler}"(oldState,newState)
} catch (groovy.lang.MissingMethodException mme) {
try {
entity."${handler}"()
} catch (groovy.lang.MissingMethodException mme2) {
// TODO don't use println here...
println "${handler} has to take two or no parameters!"
}
}
}
}
}

Now once you've got your listener in just the shape you need it in. Take a look at the plug-in file AuditLoggingGrailsPlugin.groovy again.

There are several closures that while not documented extensively are rather self explanatory. In our example plug-in project we will are hooking into the GORM session factory. That means we want to work in the doWithApplicationContext closure.

This closure takes a parameter which by default is set up as the applicationContext if you take a look at the source code for The Hibernate Events Plugin you can see in the file HibernateEventsGrailsPlugin.groovy how Keving Burke has used the application context to get the sessionFactory and then registered the listeners with the session factory. I've only slightly adapted this here...

def doWithApplicationContext = { applicationContext ->
def listeners = applicationContext.sessionFactory.eventListeners
def listener = new AuditLogListener()

['preDelete','postInsert', 'postUpdate',].each({
addEventTypeListener(listeners, listener, it)
})

}

This closure is very interesting, it is pulling the sessionFactory out of the applicationContext and getting the array of eventListeners. Then we create an instance of our new event listener... finally we register the event listener with the session factory for each type of event it can accept.

The next method is very Groovy we are creating a string name for the particular type of event listener and then using Groovy's Meta features to extract the array of listeners of that type. The rest of the method is about manipulating the object array and registering the new listener.
 
def addEventTypeListener(listeners, listener, type) {
def typeProperty = "${type}EventListeners"
def typeListeners = listeners."${typeProperty}"

def expandedTypeListeners = new Object[typeListeners.length + 1]
System.arraycopy(typeListeners, 0, expandedTypeListeners, 0, typeListeners.length)
expandedTypeListeners[-1] = listener

listeners."${typeProperty}" = expandedTypeListeners
}

.. this is a very clever twist that injects a set of new listeners into the already created sessionFactory and does so using Groovy specific programming tricks. That call to listeners."${typeProperty}" is dynamically getting at "preDeleteEventListeners" and "postUpdateEventListeners" each in turn. That's very groovy. The method's result is a modified and registered listener array inside a running sessionFactory. Very cool.

Now that we have a listener and code to register the listener we need to run the packager to get the plug-in ready to be installed in another Grails project. Inside the project folder just run...

$ grails package-plugin

... in my example project this command produces the zip file grails-audit-logging-0.1.zip. Now to install your new plug-in in any Grails project just specify the path and use the grails plugin-install command. For example from another project you would install the audit-logging plug-in with this command...

$ grails install-plugin ../audit-logging/grails-audit-logging-0.1.zip

Now when you run your grails application you should see that the plug-in is installed and you can start using the plug-in's domain class conventions.

NOTE: I've posted the grails-audit-logging-0.2.zip plugin here and I've written it up here if you are just interested in audit logging.

You've seen how easy it is create a grails plug-in that can do very sophisticated things using Java, Hibernate, and Spring API's. This type of project shows the real strength of Groovy and Grails by allowing you to leverage existing Java libraries and code to create new kinds of features rapidly and easily that would have been more work in some other frameworks.

I hope this inspires you to create your own plug-ins that do amazing things and add to the power of the Grails framework. Happy coding!

2008-04-16

Audit Logging in Grails

EDIT: Audit Logging is now available via the grails command line tool
$ grails install-plugin audit-logging


Here's the story of how I ended up writing an AuditLogging plugin for Grails. I've posted the Grails Audit Logging plugin here. And I'll do a more formal write-up later. A demo project is here if you want to see it in action.

So, my story.

I have a project that I needs audit logging for certain objects in my domain. As usual (in the last six months) I started the project using Grails because of it's powerful and full featured GORM tool. GORM in case you didn't already know is built on top of Hibernate. So that means deep down we have the powerful hibernate events model to hook into.

A couple of years ago on the LAMP stack I would have written triggers in my database to handle the changes to certain tables. This would have the distinct disadvantage of not having any knowledge of the actual domain model that the table was supporting.

Today I can hook into Hibernate and get the same results keeping my domain class and triggers nice and close to each other.

Honestly, I would have probably went with the in database triggers and felt bad about it. Thank goodness I stumbled on this blog entry by Kevin Burke I would have had a much less interesting and satisfying project. Instead Kevin's influence caused me to create the AuditLogging plugin for Grails.

Kevin had the need to hook into the Hibernate Events model, and had already written the Grails Hibernate Events Plugin. I was still a few weeks off from needing this plugin but I was glad to see it existed. Frankly, I'd be utterly lost without Kevin's plugin to show me how to work with Grails and Hibernate Events.

In true groovy fashion Kevin's work leans heavily on closures. He's added ten new conventions to GORM domain classes for use in the events plugin. I won't list them all there but the ones I was interested in were the afterInsert, afterUpdate, and afterDelete events and their matching closures.

When fired the event handler will call each closure from the matching org.hibernate.event.*Listener class. Kevin's project doesn't need to see the old values and compare them to the new values. So he doesn't pass these back down to the handlers defined in the class. Unfortunately for me. That's not going to work. I need audit logging and I need to be able to do custom things for some domain classes when certain values change. Pretty tall order actually.

I spent some time reading up on events in Chapter 12 of the Hibernate 3 documentation. And taking a look at the Hibernate documentation I can see that any of the event objects nicely expose the Hibernate internals. In specific using the event object I can snag the EntityPersister that fired the event. That's interesting since I need to be able to see specifically what changed and throw one of 35 different event objects (that's a specific detail of my current project) out into GORM when it happens.

In particular I want to have a very groovy plugin that can be turned on and off for a given domain class using a convention like searchable does... I want it to be able to specify an "onChange" handler that can see old values and new values and do different things depending on what changed and how it changed... yeah... a pretty tall order.

That's why I'm so surprised at how easy this was to write. I was lucky to have the work of Rob Monie and even Kevin's Hibernate Events plugin to reference. The result is a plugin that you can use pretty simply.

To install my plugin just:

$ grails install-plugin http://shawn.hartsock.googlepages.com/grails-audit-logging-0.1.zip


Then in your favorite domain class do this:

class MyClass {

static auditable = true
Long id
Long version


String someValue

def onChange = { newMap, oldMap ->
oldMap.each({ key, oldVal ->
if(oldVal != newMap[key]) {
println " * $key changed from $oldVal to " + newMap[key]
}
})
}


}


Now when the class is acted on a new AuditLogEvent record will be inserted. The record will record when the event happened and what changed. If it's an insert or a delete then it will only record that the event happened.

What if you don't want every detail logged but only wanted that onChange handler? Then you can specify

static auditable = [handlersOnly:true]


Now only the handlers you specify will be called. My plugin handles onSave, onUpdate, onDelete, and two types of onChange. The first type of onChange takes two parameters. The second kind takes none. In practice the no parameter onChange is identical to the onUpdate.

I'm putting this plugin to use in my project now. Hopefully someone else will find this useful. This work is still very rough but I will be torture testing the plugin in my own project which will have thousands of events per second. Any input is appreciated.

EDIT see also Grails Audit Logging Plugin

2008-01-27

Paradox/Parabox of Choice

A late night post due to a late night. Some things are bothering me and I decided to put them in words.

Consider this post on slashdot this is problem is real and a real burden to many shops. If you were a LAMP shop and that 'P' was Perl and then you add PHP because marketing wants something... and then you add RoR because somebody thought it was cool. Then you add Java to the mix. How many platforms are you supporting? How many different server configurations... and what is that costing you?

Even if you are a Java shop which kind of Java shop are you?

Both Neal Ford and Scott Davis talk about this idea called "The Paradox of Choice." The problem is that in Java land there are far too many choices between frameworks for anyone to comprehend and deal with. The paradox is that when given too many choices people can't make any choice.

Java has so many frameworks for so many different environments that it has become a joke amongst techies: "How many Java frameworks are there? Dunno, a new one comes out every second so what time is it?"

In typical tongue-in-cheek fashion I will instead refer to The Farnsworth Parabox of choice. This is a technique that I think will help. See the Parabox is a box that Professor Farnsworth put the universe inside... or is it a box with a universe inside it? Anyway at the end of the episode the Professor had created a box that contained our universe. Grails is your box. Grails contains the universe of Java but it fits neatly into a box.

Think inside the box. Check to see what works well with Grails. No seriously. If you use the Grails "defaults" you can effectively eliminate the need for making dozens of choices. Which framework to use? Well, what is the Grails default? Spring it is. Which application server to use? Which one comes with Grails? Jetty it is.

When you reach the edge of the box or can't fit a need you have into that Groovy/Grails box that's okay because the whole universe of Java is inside that box too. All you have to do is reach down deeper beneath the Groovy skin and dig into Hibernate, Spring, or EJB3 and that whole cosmos is ready and waiting for you. Conversely, if you are comfortable floating around in the gigantic universe of Java then you can reach down into the box and take out what you need.

Grails is the Java universe in a box.

2007-10-22

Issues of Concurrency

I have a problem. I have two databases. One old one and one new one. The new one is fully normalized and the old one is not. Both databases drive their own front ends. One is a Perl front end the other is a Spring Java front end. The data between both interfaces has to stay in sync. Our DBA has elected to use triggers to keep the databases in sync.

Hibernate uses its own Version column to keep up with which version got committed to the database and when it happened. There are articles dealing with JPA, Hibernate, and Concurrency... my problem is a little different.

Here's my example of the problem.

Let's say you have a web application using an ORM engine like Hibernate. There is Entity version 1 in the database. User Alice checks out version 1. User Bob checks out version one.

Alice edits her copy of version 1 and sends it back. The ORM engine checks the object version against the database version and they match and on commit of Alice's changes the database version number is set to 2.

Bob makes changes to his copy of version 1 of the Entity and checks his version in. The ORM checks the database version and sees version 2 in the database. Version 1 is old and Bob gets an error reporting mid-air collision.

Now lets say you introduce a second database. This second database uses different Relational mappings but is itself an OLTP. Let's pretend both databases have exact copies of the data initially and an update trigger mechanism keeps the two OLTP's in sync.

If Alice checks out version 1 from the first database and the first interface and Bob checks out version 1 from the second database and the second interface. When Alice makes her change and creates first version 2 (2' or two prime) and Bob commits his changes to create second version 2 or (2'' or two double prime) the trigger from the first database to update the second will fire simultaneously as the trigger from the second.

In other words Alice's OLTP is unaware of changes made on Bob's OLTP and both changes are in route to the other's.

How should the two databases proceed?

Should last saved win? Should both databases error and roll back? Should the systems attempt to "merge" changes? Should one system be elected the "authority" for this data electing that Alice's interface and OLTP are always the right ones?

2007-08-13

Hibernate3 evil bug

The version of hibernate that I'm using has this bug. I had one query in my code base with " foo =: bar " instead of " foo=:bar " and all my JUnit tests started failing. Remarkable that something so small and silly could destroy an entire code base. There is no warning in the trace to point you toward this syntax... a single space blows up your whole project... evil... just pure evil.

2007-05-30

Hibernate3 is not EJB3

My first and most painful lesson in my year with J5EE was about Hibernate, Hibernate3, annotations, and EJB3. I had been lead to believe by an evil leprechaun that EJB3 was just Hibernate. I learned by actual experience in creating applications that this is simply not true. In fact EJB3 is a standard that Hibernate3 implements.

Why is that an important distinction?

Because EJB3 features are a proper subset of Hibernate3 features since Hibernate "embraces and extends" the EJB3 standard. It also means that if you do intend on using those cute little "at" signs in a "portable" way you'll have to figure out what EJB3 annotation to use to solve the Hibernate trace you just saw.

EJB3 doesn't require XML sit-ups and the EJB3 implementation in JBoss that sits on top of the Hibernate3 layer hides those gut busting crunches from your beady little eyes. So using the EJB3 persistence feature is far less painful than using the Hibernate 2 XML workout routine. That would typically mean that you'd want to use the EJB3 model whenever possible.

The problem is that Annotations for persistence and XML for persistence can get into fistycuffs. If you set up a Hibernate SessionFactory (even if it's a version 3 session factory) you may use XML docs _or_ annotations... theory may say otherwise but when I sat down to mix my annotated classes with XML configured classes the programmatic changes to the configuration wouldn't stick.

Hibernate3 is not EJB3.

2007-05-18

JAX-RPC is killin' me.

No collections?!? WTF! It seems that the JBossWS product put out by JBoss does not allow for collections over the wire as SOAP... yet my XJC tool will turn an DTO into POJOs using java.util collections. What gives!

We live in the 21st century, application developers should have dynamic data structures like double ended queues, vectors, and hash-maps to work with. If you can't do a container then your serialization program just isn't ready for prime time.

Damn it. Now I'm going to have to pour through all that autogen code and rewrite it to use plain arrays. I wonder how Hibernate will feel about this.

2007-05-16

jBPM, EJB3 Persistence, Hibernate....

I think I'm going to go nuts. jBPM, EJB3 Persistence, and Hibernate... all playing nice-nice together? Is it possible? Apparently not.