Unlock the Power of Quartz Plugins: 4 Hidden Gems to Boost Productivity https://carsnewstoday.com Quartz plugins are often overlooked, despite their immense potential to streamline your workflow. In this article, we'll delve into the existing plugins that come bundled with Quartz, exploring their capabilities and benefits.

LoggingTriggerHistoryPlugin: Uncover the Secrets of Job Execution

To understand the significance of plugins, let's first consider the two primary abstractions in Quartz: jobs and triggers. A job represents a piece of code that you wish to schedule, while a trigger dictates when this code should be executed. You can associate multiple triggers with a single job, making it a flexible and powerful tool. Surprisingly, Quartz doesn't provide built-in logging or monitoring of executed jobs and triggers by default. Although an API is available, no logging is implemented, making it difficult to track job execution. To address this, you can add the following lines to your quartz.properties file:
org.quartz.plugin.triggerHistory.class=org.quartz.plugins.history.LoggingTriggerHistoryPlugin
 
org.quartz.plugin.triggerHistory.triggerFiredMessage=Trigger [{1}.{0}] fired job [{6}.{5}] scheduled at: {2, date, dd-MM-yyyy HH:mm:ss.SSS}, next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}
 
org.quartz.plugin.triggerHistory.triggerCompleteMessage=Trigger [{1}.{0}] completed firing job [{6}.{5}] with resulting trigger instruction code: {9}. Next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}
 
org.quartz.plugin.triggerHistory.triggerMisfiredMessage=Trigger [{1}.{0}] misfired job [{6}.{5}]. Should have fired at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}
The first line loads the plugin class LoggingTriggerHistoryPlugin, while the remaining lines configure the plugin, customizing the logging messages. By adding these extra few lines, you can make debugging and monitoring significantly easier, as demonstrated in the example below:
LoggingTriggerHistoryPlugin | Trigger [Demo.Every-few-seconds] fired job [Demo.Print-message] scheduled at:  04-04-2012 23:23:47.036, next scheduled at:  04-04-2012 23:23:51.036
//...job output
LoggingTriggerHistoryPlugin | Trigger [Demo.Every-few-seconds] completed firing job [Demo.Print-message] with resulting trigger instruction code: DO NOTHING. Next scheduled at:  04-04-2012 23:23:51.036

You now grasp the significance of assigning descriptive names to your triggers (Demo.Every-few-seconds) and jobs (Demo.Print-message), which greatly facilitates their identification.

LoggingJobHistoryPlugin

Another valuable plugin related to logging is worth exploring:

org.quartz.plugin.jobHistory.class=org.quartz.plugins.history.LoggingJobHistoryPlugin
org.quartz.plugin.jobHistory.jobToBeFiredMessage=Job [{1}.{0}] scheduled to be fired by trigger [{4}.{3}], re-fire: {7}
org.quartz.plugin.jobHistory.jobSuccessMessage=Job [{1}.{0}] execution completed successfully and reports: {8}
org.quartz.plugin.jobHistory.jobFailedMessage=Job [{1}.{0}] execution failed with exception: {8}
org.quartz.plugin.jobHistory.jobWasVetoedMessage=Job [{1}.{0}] was vetoed. It was to be fired by trigger [{4}.{3}] at: {2, date, dd-MM-yyyy HH:mm:ss.SSS}

The underlying principle is the same – plugin + extra configuration. For more details and possible placeholders, refer to the JavaDoc of LoggingJobHistoryPlugin. A quick glance at the logs reveals very descriptive output:

Trigger [Demo.Every-few-seconds] fired job [Demo.Print-message] scheduled at:  04-04-2012 23:34:53.739, next scheduled at:  04-04-2012 23:34:57.739
Job [Demo.Print-message] to be fired by trigger [Demo.Every-few-seconds], re-fire: 0
//...job output
Job [Demo.Print-message] execution complete and reports: null
Trigger [Demo.Every-few-seconds] completed firing job [Demo.Print-message] with resulting trigger instruction code: DO NOTHING. Next scheduled at:  04-04-2012 23:34:57.739

I find it puzzling that these plugins aren’t enabled by default. After all, if you don’t want such a verbose output, you can simply turn it off in your logging framework. Nevertheless, I believe it’s a good idea to have them in place when troubleshooting Quartz execution.

XMLSchedulingDataProcessorPlugin

This comprehensive plugin is particularly useful. It reads an XML file (by default named quartz_data.xml) containing jobs and triggers definitions and adds them to the scheduler. This is especially useful when you have a global job that you need to add once. The plugin can either update the existing jobs/triggers or ignore the XML file if they already exist – very useful when JDBCJobStore is used.

org.quartz.plugin.xmlScheduling.class=org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin

In the aforementioned article, we manually added a job to the scheduler:

val trigger = newTrigger().
        withIdentity("Every-few-seconds", "Demo").
        withSchedule(
            simpleSchedule().
                    withIntervalInSeconds(4).
                    repeatForever()
        ).
        build()

val job = newJob(classOf[PrintMessageJob]).
        withIdentity("Print-message", "Demo").
        usingJobData("msg", "Hello, world!").
        build()

scheduler.scheduleJob(job, trigger)

An alternative approach to achieve the same outcome is by configuring XML, which involves placing the quartz_data.xml file in your CLASSPATH as follows:


 
    
        false
        true
    
 
    
        
            
                Every-few-seconds
                Demo
                Print-message
                Demo
                -1
                4000
            
        
 
        
            Print-message
            Demo
            com.blogspot.nurkiewicz.quartz.demo.PrintMessageJob
            
                
                    msg
                    Hello, World!
                
            
        
 
    
 
 

This XML file supports both simple and CRON triggers, and its structure is thoroughly documented using XML Schema.

Additionally, it is possible to reference XML files located in the file system and periodically scan them for changes (note the use of XMLSchedulingDataProcessorPlugin.setScanInterval()). Interestingly, Quartz utilizes its own scheduling mechanism for periodic scanning.

org.quartz.plugin.xmlScheduling.fileNames=/etc/quartz/system-jobs.xml,/home/johnny/my-jobs.xml
org.quartz.plugin.xmlScheduling.scanInterval=60

ShutdownHookPlugin

Lastly, there is the ShutdownHookPlugin, a compact yet useful plugin that registers a shutdown hook in the JVM, enabling a gentle stop of the scheduler. However, it is advisable to disable cleanShutdown – if the system is already attempting to abruptly terminate the application (typically, scheduler shutdown is triggered by Spring via SchedulerFactoryBean) or the user presses Ctrl+C, waiting for currently running jobs seems ill-advised. After all, perhaps we are terminating the application because some jobs are running for too long or hanging?

org.quartz.plugin.shutdownHook.class=org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownHook.cleanShutdown=false

Evidently, Quartz boasts an array of captivating plugins. Although they lack comprehensive documentation, they operate exceptionally and prove to be a valuable asset to the scheduler.

The source code, incorporating these plugins, is accessible on GitHub.