Manually override and launch quartz jobs…

Override quartz settings?

So you have a quartz job that’s chugging along nicely until you’re hit with the reality that the job details parameters change or the job needs to be suspended, or something happens that you end up having to recompile and redeploy your application just to update the packaged quartz job properties. This is no fun. You will undoubtedly have to take the updated code through the regular qa cycle, regress test, and then ultimately redeploy your code into the production environment. Surely there must be some way to address this problem when using Jboss…

One way I came up with was to divorce the job execution code from the job invocation, while making sure that the JobDetailsMap always checked an external resource before defaulting to loading the packaged resource within the deployed artifact. To allow for manual invocation, I also added a servlet that basically just wrapped the decoupled job invocation code in order to launch the quartz job. I also added a property to the JobDetailMap – “enable” which I used as a flag for whether the job should fire or not. Because it would try to load an external resource before defaulting, we were then able to have complete control over the quartz job’s properties. Note that you can’t change the cron fire date by using this method – the job itself is loaded in from the minute your application fires up – to reload the job you’d have to programatically find the existing job, destroy it and then create a new one based off the external properties. In my particular case we didn’t need to go that far but that option is available for those that need it.

The steps:

1) stick a copy of the quartz-config.xml file in the jboss.conf.dir location: maybe something like “/jboss/server/myInstance/conf/quartz-config.xml”. This conf directory is explored in depth in the related post Jboss System Properties.

2) Rig your quartz Job class so the execute(JobExecutionContext jobContext) method simply calls a plain launchJob() method. By doing this you end up separating the call that launches the job from the quartz specific entry point so any externally invoking code can call your launchJob() method directly without you having to figure out how to populate a JobExecutionContext object to pass into that execute() method:

public class QuartzJob implements Job {   
   
    public void execute(JobExecutionContext jobContext)   
        throws JobExecutionException {   
   
         log.info("launching regularly scheduled quartz job");   
        launch(); 
    }   
 
    public void launch() { 

         // your job code would go here

    }

}

3) Read in that quartz-config.xml file from the Jboss conf directory if one exists, and extract the properties from the xml file to populate your own JobDetailsMap object. Default it to read in the quartz-config.xml packaged in your war, jar or ear file:

public void launch() { 

  Document document = null; 
  SAXReader reader = new SAXReader(); 
  JobDataMap map = new JobDataMap(); 

  try { 

       // this section here extracts properties from the config file	    
       InputStream is = null; 
       String quartzConfig = "quartz-config.xml"; 


       try { 

	    String path = System.getProperty("jboss.server.config.url")
		 +quartzConfig;   
	    URL url = new URL(path); 

	    log.info("attempting to load " + quartzConfig + " file from: " + path); 
	    is = url.openStream(); 
	    log.info("loaded " + quartzConfig + " from URL: " + path);   

       } catch (Exception e) { 

	    is = this.getClass().getResourceAsStream("/" + quartzConfig); 
	    log.info("couldn't load " + quartzConfig + 
		 " from URL, loaded packaged from war: /"+quartzConfig); 

       } 

       document = reader.read(is); 

       String xPath =
            "/quartz/job/job-detail[name = 'myQuartzJob']/job-data-map/entry"; 
       List<Node> nodes = document.selectNodes(xPath); 
       for (Node node : nodes) { 
	    String key = ((Node) node.selectNodes("key").get(0)).getText(); 
	    String value = ((Node) node.selectNodes("value").get(0)).getText(); 

	    map.put(key, value); 
       }

    } catch (Exception e) { 
         e.printStackTrace(); 
    } 

    String enabled = map.getString("enabled");
    if(enabled  != null && enabled .equalsIgnoreCase("true") ) { 
  
  	    // your job code here...

    }
}

You could also just as well have hardcoded the location of your quartz-config.xml file into a java.net.URL object – and then grabbed that inputstream for xpath extraction.

4) Wrap your quartz Job class in an external servlet:

public class MyQuartzServlet extends GenericServlet { 
 
     private static final long serialVersionUID = 1L; 
     private static final Log log = LogFactory.getLog(MyQuartzServlet .class); 
 
     @Override 
     public void service(ServletRequest req, ServletResponse res)
          throws ServletException, IOException { 
 
          log.info("launching quartz job" from servlet); 
          QuartzJob  importJob = new QuartzJob (); 
          importJob.launch(); 
           
          // forward to some jsp, and/or add other job success/fail logic here
          getServletConfig().getServletContext()
                    .getRequestDispatcher("/servlet.jsp").forward(req,res); 
           
     } 
      
}

Of course you’d have to configure the servlet and servlet-mappings in your application’s web.xml, but that should be pretty straight forward.

Congrats, you now have a quartz job that loads an external configuration file, and that can also be invoked manually through a servlet. I’m not saying this is perfect and should be used in every possible quartz scenario, but this approach works well for quartz jobs where the properties might need overriding or temporary disabling. I can also understand the argument for why you would want to necessarily cycle every configuration change through qa. I hope at least this gives some folks idea outside the proverbial box. Now on to bigger fish to fry…

Comments (0)

› No comments yet.

Leave a Reply

Allowed Tags - You may use these HTML tags and attributes in your comment.

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Pingbacks (0)

› No pingbacks yet.