Hooking into the Instance methods of the GORM API

30 / Jan / 2016 by Sandeep Poonia 0 comments

In my grails plugin I was needed to add some fields that were common to a set of domains. For eg: for some domains we wanted to store fields like createdBy and lastUpdatedBy to keep track of users who created and last updated each record in that domain.

Grails framework provides timestamping using which we can keep track of the time a record was created and last modified, but doesn’t provide something for user stamping. Although there is a grails plugin named Audit Logging Plugin, that provides this feature. But this plugin has its own limitations and was not a perfect choice for our case. I’ll discuss the issues with this plugin and the possible solutions in my next blog. Apart from these two fields you may want to add some other fields that are common to multiple domains and their values are populated based on some business logic.

So how to handle this:
One way to handle this scenario is by extending the GormInstanceApi. This is the class that contains all the GORM persistence methods which are available to each domain in Grails application. An instance of GormInstanceApi is injected to each domain class which is used to invoke the persistence methods. We’ll use the decorator design pattern here to override the default instance of GormInstanceApi in each domain.

  • Create a new class GormInstanceApiDecorator that extends GormInstanceApi.
  • Decorate instance of GormInstanceApi with GormInstanceApiDecorator and inject it to each domain.

Code for GormInstanceApiDecorator:

package org.tothenew.datastore.gorm

import org.codehaus.groovy.grails.orm.hibernate.HibernateDatastore
import org.grails.datastore.gorm.GormInstanceApi
import org.grails.datastore.mapping.core.Session
import org.grails.datastore.mapping.core.SessionCallback

class GormInstanceApiDecorator<D> extends GormInstanceApi<D> {

	private GormInstanceApi gormInstanceApi

	GormInstanceApiDecorator(GormInstanceApi gormInstanceApi) {
		super(gormInstanceApi.persistentClass, gormInstanceApi.datastore as HibernateDatastore)
		this.gormInstanceApi = gormInstanceApi
	}

	@Override
	D save(D instance) {
		save(instance, Collections.emptyMap())
	}

	@Override
	D save(D instance, boolean validate) {
		save(instance, [validate: validate])
	}

	@Override
	D save(D instance, Map params) {
		execute({ Session session ->
			doSave instance, params, session
		} as SessionCallback)
	}

	@Override
	protected D doSave(D instance, Map params, Session session, boolean isInsert = false) {
		//method that updates the common properties before instance is saved
		updateStamp instance
		gormInstanceApi.doSave(instance, params, session, isInsert)
	}

	private updateStamp(Object instance) {
		String actor = "Sandeep Poonia" //fetch the user who is currently logged in

		//set createdBy- first time only
		if (!instance.id) {
			//check if domain has createdBy property
			if(instance.hasProperty("createdBy"){
				instance.setProperty("createdBy", actor)
			}
		}
		//check if domain has lastUpdatedBy property
        if(instance.hasProperty("lastUpdatedBy"){
        	instance.setProperty("lastUpdatedBy", actor)
        }
	}
}

Here I’m taking the example of save method only. If required same can be done with other methods. Before updating the property, I’m checking whether that property exists in domain or not. This can be done in a different way or you can modify the dafault instance of GromInstanceApi for those domains only which has these properties.

To inject the GormInstanceApiDecorator instance in each Domain:
Add below code inside doWithDynamicMethods of the plugin-

def doWithDynamicMethods = { context ->
	context.grailsApplication.domainClasses.findAll {
		DefaultGrailsDomainClass domainClass ->
			//Domain class should have a db mapping
			domainClass.mappingStrategy in [GrailsDomainClass.GORM, GrailsDomainClass.ORM_MAPPING]
	}.each {
		DefaultGrailsDomainClass domainClass ->
			def gormInstanceApi = domainClass.clazz.currentGormInstanceApi()
			GormInstanceApiDecorator decoratedInstance = new GormInstanceApiDecorator(gormInstanceApi)
			domainClass.clazz.setInstanceGormInstanceApi(decoratedInstance)
	}
}
FOUND THIS USEFUL? SHARE IT

Leave a comment -