Quante volte vi e’ capitato di dover impostare una serie di JOB da eseguire periodicamente in determinati giorni e orari?
In questa micro guida cerco’ di spiegare velocemente un modo per utilizzare Quartz e la sintassi Unix-style che mette a disposizione questa fantastica libreria OpenSource inserendo i cron da eseguire direttamente in una tabella di un database, cosi da avere un sistema funzionante in meno di 10 minuti
Livello:
Medio, Avanzato
Per chi:
Ha dimestichezza con il linguaggio Java
Scenario:
Schedulare una serie di processi da eseguire periodicamente in una tabella
In questo esempio, per mia comodita’ ho sfruttato parti del codice di un mio applicativo, vedrete una chiamata ad un oggetto database mappato con Hibernate e eseguiro’ questo “RequestScheduler” tramite il servizio supervise(daemonstool) per il quale ho scritto un piccolo articolo qualche tempo fa.
Il database di appoggio e’ un MySql server e il controllo viene fatto ogni X minuti (leggere la NOTA in fondo a questo articolo)
In sostanza supervise avvia RequestScheduler, RequestScheduler esegue i suoi controlli e rimane attivo per X minuti dopodiche’ esce, supervise lo riavvia istantaneamente e cosi via…
Iniziamo con la creazione di una tabella che ci permetta di inserire i nostri job da schedulare, per velocita’ la tabella che creo conterra’ solo la frequenza per la schedulazione dei job ma si puo’ tranquillamente ampliare inserendo colonne che identificano l’oggetto da utilizzare e una colonna nella quale inserire gli eventuali parametri per tale oggetto, spazio alla fantasia…
CREATE TABLE `crontab` ( `id` INT NOT NULL AUTO_INCREMENT , `periodicity` VARCHAR( 50 ) NOT NULL , `status` TINYINT( 1 ) DEFAULT '0' NOT NULL , PRIMARY KEY ( `id` ) ); -- tutti i giorni alle 16:30 INSERT INTO `crontab` ( `id` , `periodicity` , `status` ) VALUES ( '', '0 30 16 * * ?', '1'); -- da lunedi a venerdi alle 16:30 INSERT INTO `crontab` ( `id` , `periodicity` , `status` ) VALUES ( '', '0 30 16 ? * 2-6', '1'); -- alle 16:30 ogni primo giorno del mese INSERT INTO `crontab` ( `id` , `periodicity` , `status` ) VALUES ( '', '0 30 16 1 * ?', '1');
Perfetto, ora abbiamo una situazione cosi:
mysql> select * from crontab ; +----+-----------------+--------+ | id | periodicity | status | +----+-----------------+--------+ | 1 | 0 30 16 * * ? | 1 | | 2 | 0 30 16 ? * 2-6 | 1 | | 3 | 0 30 16 1 * ? | 1 | +----+-----------------+--------+ 3 rows in set (0.00 sec) mysql>
Ora creiamo l’oggetto che dovra’ eseguire i job
import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import org.hibernate.criterion.Restrictions; import org.quartz.CronExpression; import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.impl.StdSchedulerFactory; public class RequestScheduler { @SuppressWarnings("unchecked") private void checkCrontab() throws Exception { // prelevo dalla tabella tutti i crontab attivi, quelli con status impostato a 1 List<Crontab> crontabs = session.createCriteria(Crontab.class).add(Restrictions.eq("status", true)).list(); System.out.println("numero di cron da valutare:" + crontabs.size()); SchedulerFactory sf = new StdSchedulerFactory(); Scheduler scheduler = sf.getScheduler(); String jobsGroup = Scheduler.DEFAULT_GROUP; // setup dell'arco di tempo da controllare Date currentDate = new Date(System.currentTimeMillis()); Calendar init = new GregorianCalendar(); init.setTime(currentDate); Calendar end = new GregorianCalendar(); end.setTime(currentDate); end.add(Calendar.MINUTE, 10); // ciclo sugli oggetti prelevati dalla tabella for (Crontab crontab : crontabs) { // verifico che l'espressione del crontab sia sintatticamente corretta if(CronExpression.isValidExpression(crontab.getPeriodicity())) { try { // valuto la periodicita' impostata in questo record // e verifico quando e' la prossima data in cui verra' eseguito CronExpression cron = new CronExpression(crontab.getPeriodicity()); Date nextValidDate = cron.getNextValidTimeAfter(currentDate); // se la prossima data di esecuzione rientra nell'arco di tempo // tra le due date precedentemente impostate if( nextValidDate.after(init.getTime()) && nextValidDate.before(end.getTime())) { String jobName = "Job_" + + crontab.getId(); String triggerName = "Trigger_" + + crontab.getId(); // preparo il job da eseguire JobDetail job = new JobDetail(jobName, jobsGroup, RequestSchedulerJob.class); // passo il parametro che mi serve all'oggetto del job RequestSchedulerJob job.getJobDataMap().put("crontab", crontab); // creo il trigger assegnandogli i parametri necessari CronTrigger trigger = new CronTrigger(); trigger.setName(triggerName); trigger.setGroup("SchedulerGroup"); trigger.setJobName(jobName); trigger.setJobGroup(jobsGroup); trigger.setCronExpression(cron); // aggiungo il job allo scheduler scheduler.addJob(job, true); System.out.println("aggiunto allo scheduler il crontabID:" + crontab.getId()); // schedulo il job appena impostato con il trigger creato scheduler.scheduleJob(trigger); } } catch (UnsupportedOperationException e) { // qualche cosa non andava nella periodicita // solitamente un crontrigger simile: "0 0 10 * * * // il quale viene validato da isValidExpression } } else { // sintassi crontab errata } } // faccio partire lo scheduler scheduler.start(); // ******** ATTENZIONE!!! LEGGI LA NOTA SOTTO *********** // rimango "vivo" per un tot di tempo cosi da dare la // possibilita' allo scheduler di inserire i job // presenti nell'arco di tempo controllato try { // differenza in millisecondi dell'arco di tempo controllato // verra' utilizzata per lo sleep dell'applicazione long spleepingTime = end.getTimeInMillis()-System.currentTimeMillis(); Thread.sleep(spleepingTime); } catch (InterruptedException e) { } // ******** ATTENZIONE!!! LEGGI LA NOTA SOTTO *********** // il true permette lo shutdown gracefull dello scheduler // quindi se ci sono job in fase di esecuzione lo shutdown attende // che siano finiti scheduler.shutdown(true); // esco, verro' riavviato istantaneamente da supervise System.exit(0); } public static void main(String[] args) { RequestScheduler requestScheduler = new RequestScheduler(); try { requestScheduler.checkCrontab(); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } }
E ora il job che deve essere eseguito:
import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class RequestSchedulerJob implements Job { private Crontab crontab; public RequestSchedulerJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { // prelevo il nome del job String jobName = context.getJobDetail().getFullName(); // prelevo i parametri passati a questo job JobDataMap data = context.getJobDetail().getJobDataMap(); crontab = (Crontab) data.get("crontab"); try { // faccio qualche cosa System.out.println("eseguo il crontabID: " + crontab.getId()); } catch (Exception e) { e.printStackTrace(); } } }
Come potete facilmente notare dal codice eseguo un controllo sulla tabella “crontab” prendendo tutti i job attivi valutando la colonna “status”, fatto questo scorro i record prendendo in considerazione SOLO i trigger che rientrano nell’arco di tempo tra “ora” e “ora + 10 minuti” e una volta fatto partire lo scheduler rimango in sleep per ~10 minuti cosi da dare la possibilita’ allo scheduler di inserire i job caricati.
NOTA:
Questa pratica, quella dello spleep e del controllo dei tempi fatto in questo modo, non e’ propriamente un sistema sicuro :), cosi facendo qualche elemento della tablla crontab potrebbe venir saltato e logicamente piu’ si abbassa lo sleep piu’ probabilita’ ci sono, soprattutto per esecuzioni nell’ordine del “secondo”.Ci sono sistemi decisamente piu’ stabili e affidabili che non ho riportato in questa sede dato che volevo creare due oggetti veloci e di facile interpretazione e con il minimo indispensabile per prendere confidenza con gli oggetti:
CronExpression;
CronTrigger;
JobDetail;
Scheduler;
SchedulerFactory;
StdSchedulerFactory;
Per finire
Questo tipo di operazioni possono essere rese decisamente piu’ sofisticate utilizzando, e studiando, tutta la libreria Quartz
Al solito spero di aver fatto cosa gradita alla comunita’ ;)
Ciao e alla prossima,
Cristian.
RSS feed for comments on this post · TrackBack URI
Leave a reply
You must be logged in to post a comment.