views:

869

answers:

6

I'm writing a grails plugin and I need to hook into the domain save() method to do some logic after the save. I need to do this across multiple domain classes. I'm trying to avoid hibernate events in the cases where a plugin user is not using hibernate with GORM.

I've tried many thing but below is what I think should have had the best chance at working. In all cases grailsSave is null. How can I do this?

def doWithDynamicMethods = { ctx ->
    application.domainClasses.each { dc ->
        def grailsSave = dc.metaClass.pickMethod('save', [Map] as Class[])

        domainClass.metaClass.save = { Map params ->
     grailsSave.invoke(delegate, [params] as Object[])
     println "Saved object, now do my thing"
     //...
        }
    }
}

I have the following set in my *Plugin.groovy class:

def dependsOn = [domainClass: '1.1 > *', hibernate: '1.1 > *']
def loadAfter = ['hibernate']
A: 

Wouldn't this best be added to the service class that owns the unit of work? That's where the usual Spring/Grails idiom would have such logic. You needn't modify the save at all.

duffymo
Though I'm building a pluging to be used by others. My logic may very well be in a service but the point it needs to fire is on the save. Thanks
mbrevoort
The idea of delegating a bunch of logic directly relating to the domain to another tier is one of the biggest annoyances in Spring, and it's why Java has a reputation for fetishisizing complexity.
Robert Fischer
How is an an individual domain object to know when it's part of a larger unit of work and when it's not?
duffymo
*If* it's actually part of a conceptually larger unit of work and *if* there is no "owning" domain object, then that's fine to drop that into a service. But to answer "I want to extend what 'save' means for domain objects" with "create a service!" is a hack, not an answer.
Robert Fischer
"Owning" domain object? What does that mean? Hack? Your opinion, nothing more.
duffymo
+1  A: 

There are three different version of save added to the metaClass,

save(Map)
save(Boolean)
save()

Which one are you calling in your testing? You'll need to add you code to each one.

Another thing to check is whether your plugin is running after the hibernate plugin which adds the three methods to the metaClass

cheers

Lee

leebutts
Thanks Lee,I think I'm having problem forcing plugin to load after hibernate. If I run in doWithDynamicMethods all attempts to call pickMethod are null. If I run in grails console they are not null. I have this in my plugin but no luck:def dependsOn = [domainClass: '1.1 > *', hibernate: '1.1 > *']def loadAfter = ['hibernate']
mbrevoort
Hmmm loadAfter is the one you want - does it need to be static loadAfter = ['hibernate']
leebutts
static didn't work either. Strange I just noticed Robert Fischer had tried to do similar things with save() in his GORM Labs plugin but had commented the code out in previous versions and doesn't exist in the most recent. Any ideas for how to approach this differently?
mbrevoort
What about some kind of Hibernate interceptor? I don't know the API that well but it might provide the ability to plugin in a global interceptor/listener?
leebutts
That's what I ended up doing, creating a listener for the PostInsertEventListener, PostUpdateEventListener and PostDeleteEventListener. I tried with everything I had to wrap the save() methods and tap into the afterUpdate() type events but then after digging deeper found that all of that logic was dependent upon hibernate anyway so I might as well go the hibernate listener route. Thanks Lee.
mbrevoort
No worries, feel free to accept my answer to give me some points :D
leebutts
+1 — you do need to handle all three map implementations. Which is kinda annoying.The reason the save stuff is commented out in GORM Labs is because it got into Grails 1.2, so it's not necessary. I should just wipe it at this point. And provide some better hooks into extending GORM.
Robert Fischer
+1  A: 

Have a look at the Falcone Util plugin. This plugin allows you to hook into Hibernate events (see documentation at the bottom of the page). I don't know if this is exactly what you want, but you might get some hints.

Ps! I don't think the plugin works with Grails 1.2 yet.

Kimble
Thanks mate, that plugin looks excellent. Would be awesome if this type of event capability was available in the core.
mbrevoort
+3  A: 

I was unable to successfully get a reference to the save() methods during plugin/app initialization; I don't know why. Instead, I decided to create a listener for the hibernate events after insert, update, and deletes. This post by Sean Hartsock regarding the Audit Logging plugin was a perfect primer for doing that.

Here's the gist of the Listener:

class MyListener implements PostInsertEventListener, PostUpdateEventListener, PostDeleteEventListener, Initializable {

        public void onPostInsert(final PostInsertEvent event) {
            // logic after insert
            return
        }

        public void onPostUpdate(final PostUpdateEvent event) {
            // logic after update
            return
        }

        public void onPostDelete(final PostDeleteEvent event) {
            // logic after delete
            return
        }


        public void initialize(final Configuration config) {
            return
        }   
    }

Then in the *GrailsPlugin.groovy:

def doWithApplicationContext = { applicationContext ->

    // add the event listeners for reindexing on change
    def listeners = applicationContext.sessionFactory.eventListeners
    def listener = new MyListener()

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

}


// copied from http://hartsock.blogspot.com/2008/04/inside-hibernate-events-and-audit.html
private 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
}

Fairly simple at the end of the day...

mbrevoort
Shawn and his Audit Logging plugin rocks.
Robert Fischer
Thanks for sharing! I've been to lazy to look this up my self.
Kimble
+1  A: 

This is an issue of premature optimization: older versions of Groovy seriously penalized MetaClass mangling, and so GORM does not add all of its magic until it detects the need to.

Easiest solution is to have your plugin dependOn GORM Labs (I work around it there). The alternative solution is to trigger methodMissing manually (which would be duplicating the work I did). See the GORM Labs documentation for details on how I accomplished that.

Robert Fischer
A: 
sbange