DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
View Events Video Library
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Integrating PostgreSQL Databases with ANF: Join this workshop to learn how to create a PostgreSQL server using Instaclustr’s managed service

Mobile Database Essentials: Assess data needs, storage requirements, and more when leveraging databases for cloud and edge applications.

Monitoring and Observability for LLMs: Datadog and Google Cloud discuss how to achieve optimal AI model performance.

Automated Testing: The latest on architecture, TDD, and the benefits of AI and low-code tools.

Related

  • Clustered Quartz Scheduler With Spring Boot and MongoDB
  • Adding a RESTful API for the Quartz Scheduler
  • Spring and Quartz Integration That Works Together Smoothly
  • Atomic Habits and Software Engineering: Unlocking Career Success

Trending

  • Revolutionizing Software Testing
  • DZone's Article Submission Guidelines
  • Agile Estimation: Techniques and Tips for Success
  • Monkey-Patching in Java
  1. DZone
  2. Culture and Methodologies
  3. Career Development
  4. Job Chaining in Quartz and Obsidian Scheduler

Job Chaining in Quartz and Obsidian Scheduler

Carey Flichel user avatar by
Carey Flichel
·
Mar. 10, 13 · Interview
Like (1)
Save
Tweet
Share
15.51K Views

Join the DZone community and get the full member experience.

Join For Free

n this post i’m going to cover how to do job chaining in quartz versus obsidian scheduler . both are java job schedulers, but they have different approaches so i thought i’d highlight them here and give some guidance to users using both options.

it’s very common when using a job scheduler to need to chain one job to another. chaining in this case refers to executing a specific job after a certain job completes (or maybe even fails). often we want to do this conditionally, or pass on data to the target job so it can receive it as input from the original job.

we’ll start with demonstrating how to do this in quartz, which will take a fair bit of work. obsidian will come after since it’s so simple.

chaining in quartz

quartz is the most popular job scheduler out there, but unfortunately it doesn’t provide any way to give you chaining without you writing some code. quartz is a low-level library at heart, and it doesn’t try to solve these types of problems for you, which in my mind is unfortunate since it puts the onus on developers. but despite this, many teams still end up using quartz, so hopefully this is useful to some of you.

i’m going to outline probably the most basic way to perform chaining. it will allow a job to chain to another, passing on its jobdatamap (for state). this is simpler than using listeners, which would require extra configuration, but if you want to take a look, check out this listener for a starting point.

sample code

this will rely on an abstract class that will provided basic flow and chaining functionality to any subclasses. it acts as a very simple template class.

first, let’s create the abstract class that gives us chaining behaviour:

import static org.quartz.jobbuilder.newjob;
import static org.quartz.triggerbuilder.newtrigger;
import org.quartz.*;
import org.quartz.impl.*;

public abstract class chainablejob implements job {
   private static final string chain_job_class = "chainedjobclass";
   private static final string chain_job_name = "chainedjobname";
   private static final string chain_job_group = "chainedjobgroup";
   
   @override
   public void execute(jobexecutioncontext context) throws jobexecutionexception {
      // execute actual job code
      doexecute(context);

      // if chainjob() was called, chain the target job, passing on the jobdatamap
      if (context.getjobdetail().getjobdatamap().get(chain_job_class) != null) {
         try {
            chain(context);
         } catch (schedulerexception e) {
            e.printstacktrace();
         }
      }
   }
   
   // actually schedule the chained job to run now
   private void chain(jobexecutioncontext context) throws schedulerexception {
      jobdatamap map = context.getjobdetail().getjobdatamap();
      @suppresswarnings("unchecked")
      class jobclass = (class) map.remove(chain_job_class);
      string jobname = (string) map.remove(chain_job_name);
      string jobgroup = (string) map.remove(chain_job_group);
      
      
      jobdetail jobdetail = newjob(jobclass)
            .withidentity(jobname, jobgroup)
            .usingjobdata(map)
            .build();
         
      trigger trigger = newtrigger()
            .withidentity(jobname + "trigger", jobgroup + "trigger")
                  .startnow()      
                  .build();
      system.out.println("chaining " + jobname);
      stdschedulerfactory.getdefaultscheduler().schedulejob(jobdetail, trigger);
   }

   protected abstract void doexecute(jobexecutioncontext context) 
                                    throws jobexecutionexception;
   
   // trigger job chain (invocation waits for job completion)
   protected void chainjob(jobexecutioncontext context, 
                          class jobclass, 
                          string jobname, 
                          string jobgroup) {
      jobdatamap map = context.getjobdetail().getjobdatamap();
      map.put(chain_job_class, jobclass);
      map.put(chain_job_name, jobname);
      map.put(chain_job_group, jobgroup);
   }
}

there’s a fair bit of code here, but it’s nothing too complicated. we create the basic flow for job chaining by creating an abstract class which calls a doexecute() method in the child class, then chains the job if it was requested by calling chainjob() .

so how do we use it? check out the job below. it actually chains to itself to demonstrate that you can chain any job and that it can be conditional. in this case, we will chain the job to another instance of the same class if it hasn’t already been chained, and we get a true value from new random().nextboolean() .

import java.util.*;
import org.quartz.*;

public class testjob extends chainablejob {

   @override
   protected void doexecute(jobexecutioncontext context) 
                                   throws jobexecutionexception {
      jobdatamap map = context.getjobdetail().getjobdatamap();
      system.out.println("executing " + context.getjobdetail().getkey().getname() 
                         + " with " + new linkedhashmap(map));
      
      boolean alreadychained = map.get("jobvalue") != null;
      if (!alreadychained) {
         map.put("jobtime", new date().tostring());
         map.put("jobvalue", new random().nextlong());
      }
      
      if (!alreadychained && new random().nextboolean()) {
         chainjob(context, testjob.class, "secondjob", "secondjobgroup");
      }
   }
   
}

the call to chainjob() at the end will result in the automatic job chaining behaviour in the parent class. note that this isn’t called immediately, but only executes after the job completes its doexecute() method.

here’s a simple harness that demonstrates everything together:

import org.quartz.*;
import org.quartz.impl.*;

public class test {
   
   public static void main(string[] args) throws exception {

      // start up scheduler
      stdschedulerfactory.getdefaultscheduler().start();

      jobdetail job = jobbuilder.newjob(testjob.class)
             .withidentity("firstjob", "firstjobgroup").build();

      // trigger our source job to triggers another
      trigger trigger = triggerbuilder.newtrigger()
            .withidentity("firstjobtrigger", "firstjobbtriggergroup")
            .startnow()
            .withschedule(
                  simpleschedulebuilder.simpleschedule().withintervalinseconds(1)
                  .repeatforever()).build();

      stdschedulerfactory.getdefaultscheduler().schedulejob(job, trigger);
      thread.sleep(5000);   // let job run a few times

      stdschedulerfactory.getdefaultscheduler().shutdown();
   }
   
}

sample output

executing firstjob with {}
chaining secondjob
executing secondjob with {jobvalue=5420204983304142728, jobtime=sat mar 02 15:19:29 pst 2013}
executing firstjob with {}
executing firstjob with {}
chaining secondjob
executing secondjob with {jobvalue=-2361712834083016932, jobtime=sat mar 02 15:19:31 pst 2013}
executing firstjob with {}
chaining secondjob
executing secondjob with {jobvalue=7080718769449337795, jobtime=sat mar 02 15:19:32 pst 2013}
executing firstjob with {}
chaining secondjob
executing secondjob with {jobvalue=7235143258790440677, jobtime=sat mar 02 15:19:33 pst 2013}
executing firstjob with {}

deficiencies

well, we’re up and chaining, but there are some problems with this approach:

  • it doesn’t integrate with a container like spring to use configured jobs. more code would be required.
  • it forces you to know up front which jobs you want to chain, and write code for it.
  • configuration is fixed, unless, once again, you write more code.
  • no real-time changes (unless you write more code).
  • a fair bit of code to maintain , and high likelihood you will have to expand it for more functionality.

the theme here is that it’s doable, but it’s up to you to do the work to make it happen. obsidian avoids these problems by making chaining configurable, instead of it being a feature of the job itself. read on to find out how.

chaining in obsidian

in contrast to quartz, chaining in obsidian requires no code and no up-front knowledge of which jobs will chain or how you might want to chain them later. chaining is a form of configuration, and like all job-related configuration in obsidian, you can make live changes at any time without a build or any code at all. job configuration can use a native rest api or the web ui that’s included with obsidian.

the following chaining features are available for free:

  • no code and no redeploy to add or remove chains.
  • you can chain specific configurations of job classes.
  • you can chain only on certain states, including failure.
  • chain conditionally based on source job saved state (equivalent to quartz’s jobdatamap), including multiple conditions. regexp/equals/greater than, etc.
  • chain only when matching a schedule.

check out the feature and ui documentation to find out more.

now that we know what’s possible, let’s see an example. once you have your jobs configured , just create a new chain using the ui. rest api support will be here shortly but as of 1.5.1 chaining isn’t included in the api. if you need to script this right now, we can provide pointers .

in the ui, it looks like the following:

chaining ui

easy, huh? all configuration is stored in a database, so it’s easy to replicate it in various environments or to automate it via scripting. as a bonus, obsidian tracks and shows you all chaining state including what job triggered a chained job. it will even tell you why a job chain didn’t fire, whether it’s because the job status didn’t match, or one of your conditions didn’t.

conclusion

that summarizes how you can go about chaining in quartz and obsidian. quartz definitely has a minimalist approach, but that leaves developers with a lot of work to do.

meanwhile, obsidian provides rich functionality out of the box to keep developers working on their own rich functionality, instead of the plumbing that so often seems to consume their time. if you have any suggestions or feature requests for obsidian, drop us a note by leaving a comment or by contacting us .

career Quartz (scheduler) job scheduling code style

Published at DZone with permission of Carey Flichel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Clustered Quartz Scheduler With Spring Boot and MongoDB
  • Adding a RESTful API for the Quartz Scheduler
  • Spring and Quartz Integration That Works Together Smoothly
  • Atomic Habits and Software Engineering: Unlocking Career Success

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: