2

I am new to Spring Batch framework and quartz scheduler. My task is to schedule a new Spring Batch job dynamically using quartz scheduler. All new spring batch job's entries are in my database with trigger expression. Problem is that for every new spring batch job coming from database , we need to wrap it in quartz's scheduler job. so means as many spring batch job will be there that many batch job class should be there to wrap them and run by quartz scheduler.

quartz is storing all the job's and trigger entry into its own data base tables. which i have configure in config file. This job will always be quartz's job not spring batch job. here is my main method, here i will write my data base connection code to find out new springbatch job name and trigger expression and will bind them with quartz schedular

 public static void main(String[] args) {
      try {

        ApplicationContext context = new ClassPathXmlApplicationContext("quartz-context.xml");              
        JobLauncher launcher=(JobLauncher) context.getBean("jobLauncher");
        JobLocator locator= (JobLocator) context.getBean("jobRegistry");
        Scheduler schedulerFactoryBean=(Scheduler) context.getBean("quartzSchedulerFactoryBean");

        JobDetail job = newJob(SpringBatchJob.class).withIdentity("myJob001", "group1").build();

        Trigger trigger1 =newTrigger().withIdentity("myTrigger001", "group1").startNow().withSchedule(simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();

          schedulerFactoryBean.scheduleJob(job, trigger1);

           schedulerFactoryBean.start();              

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

Here we can see that we have jobDetail which is quartz job and whose execute method is use to run spring batch job.

springBatchjob.java

public class SpringBatchJob implements Job {

private String jobName;
private String batchJob;

private JobLocator jobLocator;

private JobLauncher jobLauncher;

private File contentDirectory;

private String directoryPath = "inputFiles";

public void init(){
    contentDirectory = new File(directoryPath);
}

boolean fileFound = false;


public void performJob(String str) {}


public String getJobName() {
    return jobName;
}

public void setBatchJob(String batchJob) {
    this.batchJob = batchJob;
}

public void setJobName(String jobName) {
    this.jobName = jobName;
}

public void setJobLocator(JobLocator jobLocator) {
    this.jobLocator = jobLocator;
}

public void setJobLauncher(JobLauncher jobLauncher) {
    this.jobLauncher = jobLauncher;
}

@Override
public void execute(JobExecutionContext arg0) throws org.quartz.JobExecutionException {
    JobParameter jb= new JobParameter(5L);
    Map<String, JobParameter> map= new HashMap<>();
    map.put(jobName,jb);
     ApplicationContext context = new ClassPathXmlApplicationContext("quartz-context.xml");             
     JobLauncher launcher=(JobLauncher) context.getBean("jobLauncher");
        JobLocator locator= (JobLocator) context.getBean("jobRegistry");
        setJobLauncher(launcher);
        setJobLocator(locator);
        setJobName("helloWorldJob");
    // TODO Auto-generated method stub
    JobExecution result = null;
    try {
        result = jobLauncher.run(jobLocator.getJob(jobName), new JobParameters(map));
    } catch (JobExecutionAlreadyRunningException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (JobRestartException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (JobInstanceAlreadyCompleteException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (JobParametersInvalidException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchJobException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } 
    System.out.println("ExamResult Job completetion details : "+result.toString());

}

here in setJobName method i am hard coding my spring batch job name , But in my project we are having nearly 800 jobs, so acc to approarch we need to make 800 wrapper classes.

Please help me , that how to resolve this solution by making generic class.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
Sanjay
  • 89
  • 1
  • 13

1 Answers1

6

I sincerely hope you aren't really using that class, it will eventually eat all your memory as you are recreating your application over and over.

Spring already has support for quartz and especially the construction of jobs in the form of the SpringBeanJobFactory which you can use to your advantage especially if you extend it with some auto wiring capabilities.

public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private ApplicationContext context;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {

        Object object = super.createJobInstance(bundle);
        context.getAutowireCapableBeanFactory().autowireBean(object);
        return object;
    } 

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.context=applicationContext;
    }
}

Then rewrite your job class as something like this

public class SpringBatchJob implements Job {

    private final Logger logger = LoggerFactory.getLogger(SpringBatchJob.class);

    private String jobName;

    @Autowired
    private JobLocator jobLocator;

    @Autowired
    private JobLauncher jobLauncher;

    @Override
    public void execute(JobExecutionContext context) throws org.quartz.JobExecutionException {

        JobDataMap JobDataMap = context.getMergedJobDataMap();
        JobParametersBuilder builder = new JobParametersBuilder();
        for (Map.Entry<String, Object) param : jobDataMap.entrySet()) {
            String key = param.getKey();
            Object val = param.getValue();
            builder.addString(key, String.valueOf(val)); // Or make it smarter by doing type detection.
        }

        Job jobToLaunch = jobLocator.getJob(jobName);
        JobExecution result;
        try {
            result = jobLauncher.run(jobToLaunch, builder.to);
        } catch (JobExecutionException e) {
            throw new org.quartz.JobExecutionException("Exception execution job '"+this.jobName+"'", e);
        } finally {
            logger.info("{}  Job completetion details ", this.jobName, result);
        }
    }
}

Now you need to configure the SchedulerFactoryBean to use the AutowiringSpringBeanJobFactory by setting the jobFactory property. When you done then you just need to configure the quartz jobs appropriately.

For the sample you posted the following will launch the helloWorldJob when it is triggered to launch.

public static void main(String[] args) {
      try {

        ApplicationContext context = new ClassPathXmlApplicationContext("quartz-context.xml");              
        JobLauncher launcher=(JobLauncher) context.getBean("jobLauncher");
        JobLocator locator= (JobLocator) context.getBean("jobRegistry");
        Scheduler schedulerFactoryBean=(Scheduler) context.getBean("quartzSchedulerFactoryBean");

        JobDetail job = newJob(SpringBatchJob.class)
                          .withIdentity("myJob001", "group1")
                          .usingJobData("jobName", "helloWorldJob")
                          .build();

        Trigger trigger1 =newTrigger().withIdentity("myTrigger001", "group1")
                          .startNow()
                          .withSchedule(simpleSchedule().withIntervalInSeconds(10).repeatForever())
                          .build();

          schedulerFactoryBean.scheduleJob(job, trigger1);
          schedulerFactoryBean.start();              

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

Notice the .usingJobData("jobName", "helloWorldJob"), the SpringBeanJobFactory will try to satisfy all setter methods on the SpringBatchJob class. This has a setJobName which will get injected with helloWorldJob when launched. The functionality is extended by the AutowiringSpringBeanJobFactory to also auto wire the needed infrastructure beans from Spring Batch.

If you need to pass additional properties to the Spring Batch job (like keys to use or other information, just add another usingJobData("your-property", your-value). The modified SpringBatchJob will map all quarts job parameters to Spring Batch parameters before launching the job.

Note: This was typed from the top of my head, I haven't actually tested this, but it should at least give you an idea on how to do this.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224