Abasourdi
Abasourdi

Reputation: 103

Groovy method interception

In my Grails app I've installed the Quartz plugin. I want to intercept calls to every Quartz job class' execute method in order to do something before the execute method is invoked (similar to AOP before advice).

Currently, I'm trying to do this interception from the doWithDynamicMethods closure of another plugin as shown below:

def doWithDynamicMethods = { ctx ->
    // get all the job classes
    application.getArtefacts("Job").each { klass ->

        MetaClass jobMetaClass = klass.clazz.metaClass

        // intercept the methods of the job classes
        jobMetaClass.invokeMethod = { String name, Object args ->

            // do something before invoking the called method
            if (name == "execute") {
                println "this should happen before execute()"
            }

            // now call the method that was originally invoked
            def validMethod = jobMetaClass.getMetaMethod(name, args)

            if (validMethod != null) {
                validMethod.invoke(delegate, args)
            } else {
                jobMetaClass.invokeMissingMethod(delegate, name, args)
            }
        }
    }
}

So, given a job such as

class TestJob {
    static triggers = {
      simple repeatInterval: 5000l // execute job once in 5 seconds
    }

    def execute() {
        "execute called"
    }
}

It should print:

this should happen before execute()
execute called

But my attempt at method interception seems to have no effect and instead it just prints:

execute called

Perhaps the cause of the problem is this Groovy bug? Even though the Job classes don't explicitly implement the org.quartz.Job interface, I suspect that implicitly (due to some Groovy voodoo), they are instances of this interface.

If indeed this bug is the cause of my problem, is there another way that I can do "before method interception"?

Upvotes: 10

Views: 1481

Answers (4)

Umair Saleem
Umair Saleem

Reputation: 1063

For method interception implement invokeMethod on the metaclass. In my case the class was not of third party so I can modify the implementation.

Follow this blog for more information.

Upvotes: 1

moskiteau
moskiteau

Reputation: 1102

You are not getting the job classes like that. If you refer to the Quartz plugin, you can get them by calling jobClasses:

application.jobClasses.each {GrailsJobClass tc -> ... }

see https://github.com/nebolsin/grails-quartz/blob/master/QuartzGrailsPlugin.groovy

If you actually look, you can see that they are almost doing what you are trying to acheive without the need to use aop or anything else.

Upvotes: 3

dmahapatro
dmahapatro

Reputation: 50245

You can have your customized JobListener registered in the application to handle logics before execute() is triggered. You can use something like:-

public class MyJobListener implements JobListener {
    public void jobToBeExecuted(JobExecutionContext context) {
        println "Before calling Execute"
    }

    public void jobWasExecuted(JobExecutionContext context,
            JobExecutionException jobException) {}

    public void jobExecutionVetoed(JobExecutionContext context) {}
}

Register the customized Job Listener to Quartz Scheduler in Bootstrap:-

Scheduler scheduler = ctx.getBean("quartzScheduler") //ctx being application context
scheduler.getListenerManager().addJobListener(myJobListener, allJobs())

resources.groovy:-

beans = {
    myJobListener(MyJobListener)
}
  • One benefit I see here using this approach is that we don't need the second plugin used for method interception any more.
  • Second, we can register the listener to listen all jobs, specific jobs, and jobs in a group. Refer Customize Quartz JobListener and API for JobListener, TriggerListener, ScheduleListener for more insight.
  • Obviously, AOP is another approach if we do want want to use Quartz API.

Upvotes: 4

Dónal
Dónal

Reputation: 187529

Because all the job classes are Spring beans you can solve this problem using Spring AOP. Define an aspect such as the following (adjust the pointcut definition so that it matches only your job classes, I've assumed they are all in a package named org.example.job and have a class name that ends with Job).

@Aspect
class JobExecutionAspect {

  @Pointcut("execution(public * org.example.job.*Job.execute(..))")
  public void executeMethods() {}

  @Around("executeMethods()")
  def interceptJobExecuteMethod(ProceedingJoinPoint jp) {
    // do your stuff that should happen before execute() here, if you need access
    // to the job object call jp.getTarget()

    // now call the job's execute() method
    jp.proceed() 
  }
}

You'll need to register this aspect as a Spring bean (it doesn't matter what name you give the bean).

Upvotes: 4

Related Questions