Hooking into Grails Domain object save() - grails

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']

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...

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

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.

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.

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.

Additional GORM methods are lazily initialized on first call to any of them.
To initialize them in doWithDynamicMethods simply call one of the static methods on your domain class(es):
def doWithDynamicMethods = { ctx ->
application.domainClasses.each { dc ->
// call any static method to initialize dynamic gorm methods
dc.clazz.count()
def grailsSave = dc.metaClass.pickMethod('save', [Map] as Class[])
//...
}
}
Your save() method will be available now. As this is called at start up a single count shouldn't be to much of a problem.

Related

Does findBy* require withTransaction?

I have the following services in my Grails application:
class Person {
String name
}
class Host {
String url
}
Then I have a method invoked by multiple concurrent threads:
def person = Person.findByName("Coco")
def host = Host.findByUrl("some_url")
Do I need to surround both queries with a *.withTransaction { } block? E.g.:
Person.withTransaction { def person = Person.findByName("Coco") }
Host.withTransaction { def host = Host.findByUrl("some url") }
I have read the findBy* documentation but couldn't find anything about DB transactional safety.
Thank you.
Do I need to surround both queries with a *.withTransaction { } block?
Whether or not you want that to be happening inside of a withTransaction block depends on potentially a number of factors in your app but to answer the question, no. Those queries can be executed anywhere in your app.
As Jeff already said, each query does need to be made transactional, but the whole context might well be.
the problem is, if you while not in TX-context do
def person = Person.findByName("Coco")
and then couple of lines down below:
def children = person.lazyLoadedChildren
then you would get LazyInitException
If you are creating your own threads then they need bounded the session. Otherwise you won't be able to use all the hibernate methods.
The best way I've found is to use the persistenceInterceptor:
import org.codehaus.groovy.grails.support.PersistenceContextInterceptor
PersistenceContextInterceptor persistenceInterceptor //get this from the context
void someMethod(){
persistenceInterceptor.init()
try {
//Here you can use dynamic finders and everything
} finally {
persistenceInterceptor.flush()
persistenceInterceptor.destroy()
}
}

Grails - how execute code before every save?

Is there a good/standard way to execute some common code before every save() invocation on domain classes?
For example, my domain
class Page {
String url
Boolean processed
Date date
Integer urlCrc
}
My form has only 3 first fields and I would like to calculate urlCrc every time the save() method is called. I cannot just override save method because it is injected.
You can use GORM events - see the docs. Since by default validate() is called before every save() I would use that.
class Page {
//your defs here
def beforeValidate() {
this.urlCrc = yourComputationHere
}
}
class Page {
def beforeInsert() {
this.beforeUpdate()
}
def beforeUpdate() {
this.urlCrc = 'calculate something'
}
}
This topic is covered in the GORM docs:
6.5 Advanced GORM Features
6.5.1 Events and Auto Timestamping

"Overloading" standard GORM CRUD methods

Wanna do the following:
BootStrap {
def init = {servletContext ->
........
MyDomainClass.metaClass.save = {->
delegate.extraSave()
//////// how to call original save() here?
}
}
.........
}
P.S. MyDomainClass#extraSave is defined as public void extraSave(){.....}
First of all, Bootstrap.groovy may not be the best place to do this kind of metaprogramming. The problem with this approach is that the changes to the classes will be applied when the application starts, but you may lose these changes when the application is reloaded. Obviously this is only an issue during development, and not an issue at all if you don't mind restarting the server every time you make a change, but I'll bet this would quickly become a major annoyance. In order to have the changes applied when app is reloaded as well, you should move the metaprogramming into a plugin, where you can hook into the onChange application lifecycle event.
So the steps are:
Create a plugin
Do the metaprogramming in the doWithDynamicMethods and onChange closures of the plugin descriptor
Here's a complete example where I "override" the chain() method on all the controller classes. The code to do likewise for the save() method of domain classes should only require some obvious replacements, e.g. use application.domainClasses instead of application.controllerClasses
def doWithDynamicMethods = {ctx ->
application.controllerClasses.each {controller ->
replaceChain(controller)
}
}
def onChange = {event ->
if (application.isArtefactOfType(ControllerArtefactHandler.TYPE, event.source)) {
def clz = application.getControllerClass(event.source?.name)
replaceChain(clz)
}
}
private replaceChain(controllerClass) {
// Save a reference to the grails chain() method
def grailsChain = controllerClass.metaClass.pickMethod("chain", [Map] as Class[])
controllerClass.metaClass.chain = {Map params ->
println "My code to execute before chain goes here"
// Invoke the grails chain() method
grailsChain.invoke(delegate, [params] as Object[])
println "My code to execute after chain goes here"
}
}
why not leveraging the GORM events for this purpose? In the Domain class:
def extraSave() {
// ...
}
def beforeInsert = {
extraSave()
}
def beforeUpdate = {
extraSave()
}
IMHO this a cleaner approach. Documentation can be found here
Not sure if the following works, but this might be a solution:
MyDomainClass.metaClass.origSave = MyDomainClass.metaClass.save
MyDomainClass.metaClass.save = {->
delegate.extraSave()
delegate.origSave()
}
Please give me feedbeck if the above worked...

Add methods to controllers

In a Grails application I would like to add a foo() method to all my controller classes. I know that I can do this inside a plugin's doWithDynamicMethods closure using code like:
application.controllerClasses.toList()*.metaClass*.foo = { println 'foo called' }
However, I don't want to create a plugin just for this purpose. Is there anywhere else I can do this. I suspect it might be possible within the init closure of BootStrap.groovy, but I don't know how to get access to the GrailsApplication instance in this closure.
Thanks,
Don
def grailsApplication = org.codehaus.groovy.grails.commons.ApplicationHolder.application
The question was asked a long time ago, so my answer might not have been possible back then - but now it's April 2014. This code shows the most straightforward way of adding a method to all your controllers (from within BootStrap.init):
grailsApplication.controllerClasses.each { controllerClass ->
if (controllerClass.clazz.name.contains("org.idurkan.foo")) {
controllerClass.metaClass.respondError = { String message, int status = 404 ->
response.status = status
render([errorMessage: message] as JSON)
}
}
}
In BootStrap.groovy, inject grailsApplication like this: def grailsApplication
Make a call like mine above, substituting your own package name - to avoid messing around with plugins' classes.
Now in any controller you can use the repondError closure (invoke it like a method).
Note this does not add an action! It's just a utility closure/method available in every
controller.
class BootStrap {
def grailsApplication
def init = { servletContext ->
...
}
}

Grails Webflow - keeping things *out* of flow scope

I'm missing something....
I have a Grails webflow that looks like this:-
def childFlow = {
start {
action {
def targets = []
Target.list().each {target ->
targets.add(new TargetCommand(name: target.name, id: target.id))
}
log.debug "targets are $targets"
[children: targets]
}
on('success').to('selectChild')
}
...
TargetCommand is serializable. but I get this error:-
Caused by: java.io.NotSerializableException: com.nerderg.groupie.donate.Target
For some reason the "target" object that is inside the Target.list().each {} closure is getting put into the flow scope, and I can't figure out how to mark it as transient.
I have some code in a Service that has objects placed in the flow scope when I don't want them to too.
How do I stop local transient variables in closures being put in the flow scope?
Refining the above answer instead of clearing the persistenceContext we simply evict the instances as we finish with them, like so:
Target.list().each {
targets.add(new TargetCommand(name: it.name, id: it.id))
flow.persistenceContext.evict(it)
}
This is still a work-around for not being able to mark the closure variables as transient
The answer to my question is:
the flow object is a map that contains a reference to the "persistenceContext" which is a org.hibernate.impl.SessionImpl so the flow tries to store the entire session, even if the objects are not changed (for context I suppose)
this incorrect example from grails 1.1.x doc gives us a clue what to do:
processPurchaseOrder {
action {
def a = flow.address
def p = flow.person
def pd = flow.paymentDetails
def cartItems = flow.cartItems
flow.clear()
def o = new Order(person:p, shippingAddress:a, paymentDetails:pd)
o.invoiceNumber = new Random().nextInt(9999999) cartItems.each { o.addToItems(it) }
o.save()
[order:o] }
on("error").to "confirmPurchase"
on(Exception).to "confirmPurchase"
on("success").to "displayInvoice"
}
The flow.clear() clears the entire flow map including the persistenceContext or the session, which then makes the whole flow fail due to lack of a session.
so the intermediate "solution" is to use the persistenceContext and in this case clear it. So this works:-
def childFlow = {
start {
action {
sponsorService.updateTargetsFromTaggedContent()
def targets = []
Target.list().each {
targets.add(new TargetCommand(name: it.name, id: it.id))
}
flow.persistenceContext.clear()
[children: targets]
}
on('success').to('selectChild')
on(Exception).to 'finish'
}
The obvious problem with this is that the session is cleared completely, instead of just keeping out things I don't want in the flow.
for want of a better way, here is a generalised solution that removes any non Serializable objects from the persistenceContext of the flow. This could be a service method given the flow:-
def remove = []
flow.persistenceContext.getPersistenceContext().getEntitiesByKey().values().each { entity ->
if(!entity instanceof Serializable){
remove.add(entity)
}
}
remove.each {flow.persistenceContext.evict(it)}
If like me you need to evict all maybe you like to do
flow.persistenceContext.flush()
flow.persistenceContext.persistenceContext.clear()

Resources