Développons en Java 2.30 | |
Copyright (C) 1999-2022 Jean-Michel DOUDOUX | (date de publication : 15/06/2022) |
|
Niveau : | Intermédiaire |
Il est fréquent de devoir exécuter des tâches planifiées :
La planification de tâches peut requérir différentes fonctionnalités :
Le JDK propose des solutions basiques pour planifier l'exécution de tâches. Pour des besoins plus évolués, il est nécessaire d'utiliser une solution tierce : une des plus utilisée est l'API open source Quartz.
Ce chapitre contient plusieurs sections :
Le JDK propose deux solutions pour la planification basique de l'exécution de tâches :
La planification peut être pour une exécution unique ou pour une exécution répétée périodiquement avec ou sans délai d'attente avant la première exécution.
Depuis Java 1.3, la classe java.util.Timer permet d'exécuter des traitements de manière périodique : elle joue le rôle d'un scheduler simple.
Elle offre une solution simple, fournie en standard dans le JDK, pour exécuter des tâches de manière répétée.
La tâche à exécuter doit être encapsulée dans une classe qui hérite de la classe TimerTask.
La classe abstraite java.util.TimerTask encapsule les traitements d'une tâche qui sera exécutée par un Timer. Elle implémente l'interface Runnable.
Son exécution pourra alors être planifiée en utilisant une instance de type Timer.
La classe java.util.TimerTask permet la planification d'une seule exécution ou de plusieurs. La classe TimerTask est ajoutée à partir de Java 1.3.
Elle ne possède qu'un seul constructeur qui n'attend aucun paramètre. Elle possède plusieurs méthodes :
Méthode |
Rôle |
boolean cancel() |
Annuler la planification des prochaines exécutions. Cette méthode n'interrompt pas les exécutions en cours. Elle renvoie un booléen qui précise si au moins une exécution a été annulée |
abstract void run() |
Contenir les traitements à exécuter |
long scheduledExecutionTime() |
Renvoyer l'heure planifiée de la prochaine exécution ou de l'exécution courante si elle est en cours |
Pour créer une tâche, il faut créer une classe fille qui hérite de la classe TimerTask.
Exemple ( code Java 1.3 ) : |
package fr.jmdoudoux.dej.timer;
import java.util.Date;
import java.util.TimerTask;
public class MaTask extends TimerTask {
@Override
public void run() {
System.out.println(new Date() + " Execution de ma tache");
}
} |
La classe Timer est un scheduler basique qui permet la planification de l'exécution d'une tâche pour une seule exécution ou pour une exécution répétée à intervalles réguliers. Elle utilise pour cela un thread dédié.
La classe Timer permet de planifier l'exécution d'une tâche pour :
Les possibilités de planification du Timer sont donc très limitées : les répétitions ne peuvent se faire que sur l'attente d'un délai exprimé en millisecondes. Il n'est par exemple pas possible de définir des planifications quotidiennes à heures fixes, mensuelles, ... Il faut dans ce cas utiliser une autre solution proposée par un tiers.
Elle possède plusieurs constructeurs :
Constructeur |
Rôle |
Timer() |
Construire une instance, le thread associé n'est pas un démon |
Timer(boolean isDaemon) |
Construire une instance en précisant si le thread associé au timer est un démon |
Timer(String name) |
Construire une instance en précisant le nom du thread associé au timer (depuis Java 5) |
Timer(String name, boolean isDaemon) |
Construire une instance en précisant le nom du thread et si celui-ci est un démon (depuis Java 5) |
Tous ces constructeurs lancent le thread qui leur est associé.
Elle possède plusieurs méthodes :
Méthode |
Rôle |
void cancel() |
Terminer le timer : toutes les planifications sont supprimées |
int purge() |
Retirer de la file toutes les tâches annulées ce qui les rend éligibles pour le ramasse-miettes |
void schedule(TimerTask task, Date time) |
Planifier l'exécution de la tâche à la date/heure fournies en paramètres. Si celle-ci est dans le passé alors la tâche est exécutée immédiatement |
void schedule(TimerTask task, Date firstTime, long period) |
Planifier l'exécution répétée de la tâche : la première exécution est prévue à firstTime et le délai entre chaque exécution est précisé par le paramètre period en millisecondes |
void schedule(TimerTask task, long delay) |
Planifier l'exécution de la tâche après avoir attendu delay millisecondes |
void schedule(TimerTask task, long delay, long period) |
Planifier l'exécution répétée de la tâche : la première exécution intervient après avoir attendu delay millisecondes et le délai entre chaque exécution est précisé par le paramètre period en millisecondes |
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) |
Planifier l'exécution répétée de la tâche : la première exécution est prévue à firstTime et le délai entre chaque exécution est précisé par le paramètre period en millisecondes |
void scheduleAtFixedRate(TimerTask task, long delay, long period) |
Planifier l'exécution répétée de la tâche : la première exécution intervient après avoir attendu delay millisecondes et le délai entre chaque exécution est précisé par le paramètre period en millisecondes |
La planification peut se faire de deux manières en utilisant une des surcharges des méthodes :
Exemple ( code Java 1.3 ) : |
package fr.jmdoudoux.dej.timer;
import java.util.Timer;
public class TestTimer {
public static void main(final String[] args) {
Timer timer;
timer = new Timer();
timer.schedule(new MaTask(), 1000, 5000);
}
} |
Résultat : |
Sun Jun 15 14:00:11 CEST 2014 Execution de ma tache
Sun Jun 15 14:00:16 CEST 2014 Execution de ma tache
Sun Jun 15 14:00:21 CEST 2014 Execution de ma tache
Sun Jun 15 14:00:26 CEST 2014 Execution de ma tache
Sun Jun 15 14:00:31 CEST 2014 Execution de ma tache
Sun Jun 15 14:00:36 CEST 2014 Execution de ma tache
Sun Jun 15 14:00:41 CEST 2014 Execution de ma tache
Sun Jun 15 14:00:46 CEST 2014 Execution de ma tache |
Si une tâche est en cours d'exécution par le thread, le Timer attend la fin de son exécution pour exécuter la planification suivante même si le délai est largement dépassé.
Exemple ( code Java 1.3 ) : |
package fr.jmdoudoux.dej.timer;
import java.util.Date;
import java.util.TimerTask;
public class MaTache extends TimerTask {
@Override
public void run() {
System.out.println("Debut execution tache " + new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Fin execution tache " + new Date());
}
} |
Exemple ( code Java 1.3 ) : |
package fr.jmdoudoux.dej.timer;
import java.util.Timer;
import java.util.TimerTask;
public class TestTimer {
public static void main(final String[] args) {
TimerTask timerTask = new MaTache();
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(timerTask, 0, 2000);
System.out.println("Lancement execution");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
timer.cancel();
System.out.println("Abandon execution");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
|
Résultat : |
Lancement execution
Debut execution tache Sun Jun 15 14:25:12 CET 2014
Fin execution tache Sun Jun 15 14:25:17 CET 2014
Debut execution tache Sun Jun 15 14:25:17 CET 2014
Fin execution tache Sun Jun 15 14:25:22 CET 2014
Debut execution tache Sun Jun 15 14:25:22 CET 2014
Fin execution tache Sun Jun 15 14:25:27 CET 2014
Debut execution tache Sun Jun 15 14:25:27 CET 2014
Abandon execution
Fin execution tache Sun Jun 15 14:25:32 CET 2014 |
Dans l'exemple ci-dessus, l'exécution de la tâche est prévue toutes les deux secondes mais son temps d'exécution est de 5 secondes. Il faut s'assurer que le délai d'exécution de la tâche est inférieur au délai d'attente entre deux exécutions, sinon les exécutions vont s'empiler dans la queue.
Une surcharge de la méthode schedule() qui attend en paramètres un objet de type TimerTask et un de type Date permet de demander l'exécution de la tâche à la date/heure fournie en paramètre. Si celle-ci est dans le passé alors elle est exécutée immédiatement.
Exemple ( code Java 1.3 ) : |
package fr.jmdoudoux.dej.timer;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
public class TestTimer {
public static void main(final String[] args) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 18);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
Date time = calendar.getTime();
Timer timer = new Timer();
timer.schedule(new MaTask(), time);
}
}
|
Une surcharge de la méthode schedule() qui attend en paramètres un objet de type TimerTask, un de type Date et un de type long permet de demander l'exécution répétée de la tâche à la date/heure fournie en paramètre. Le délai d'attente entre deux exécutions est précisé en millisecondes.
Exemple ( code Java 1.3 ) : |
package fr.jmdoudoux.dej.timer;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
public class TestTimer {
public static final long VINGT_QUATRE_HEURES = 1000 * 60 * 60 * 24;
public static void main(final String[] args) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 18);
calendar.set(Calendar.MINUTE, 00);
calendar.set(Calendar.SECOND, 0);
Date time = calendar.getTime();
Timer timer = new Timer();
timer.schedule(new MaTask(), time, VINGT_QUATRE_HEURES);
}
}
|
La méthode cancel() permet d'arrêter le timer.
La classe Timer est thread-safe. Elle ne possède qu'un seul thread pour exécuter ses tâches planifiées. Il est donc important que le temps d'exécution des tâches soit le plus rapide possible pour ne pas éventuellement bloquer l'exécution d'autres tâches.
Le type de thread utilisé par le Timer influe sur la capacité de la JVM à s'arrêter :
Si le thread du Timer n'est pas un démon, alors il est nécessaire pour arrêter l'application d'invoquer la méthode cancel() : celle-ci va supprimer toutes les planifications et permettre au thread de s'arrêter. Une fois la méthode cancel() invoquée, il n'est plus possible de replanifier l'exécution de tâches par le Timer : par exemple, l'invocation de la méthode schedule() lèvera une exception de type IllegalStateException.
L'utilisation d'un Timer est simple mais présente quelques limitations :
Java 5.0 propose, dans l'API Executor, le ScheduledExecutorService qui permet l'exécution planifiée et éventuellement répétée de tâches en utilisant un pool de threads.
Cette solution dispose de plusieurs avantages par rapport à l'utilisation d'un Timer :
L'interface java.util.concurrent.ScheduledExecutorService hérite de l'interface ExecutorService. Elle définit les fonctionnalités pour planifier l'exécution d'une tâche après un certain délai ou son exécution répétée avec un intervalle de temps fixe.
L'exécution d'une tâche se fait de manière asynchrone dans un thread dédié.
La classe Executors est une fabrique qui permet de créer des instances de type Executor. Elle propose notamment plusieurs méthodes pour créer une instance de type ScheduledExecutorService.
Méthode |
Rôle |
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) |
Renvoyer une instance de type ScheduledExecutorService qui utilise un pool de threads dont la taille est fournie en paramètre |
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) |
Renvoyer une instance de type ScheduledExecutorService qui utilise un pool de threads dont la taille et la fabrique sont fournies en paramètres |
static ScheduledExecutorService newSingleThreadScheduledExecutor() |
Renvoyer une instance de type ScheduledExecutorService qui utilise un seul thread pour l'exécution des tâches |
static ScheduledExecutorService newSingleThreadScheduledExecutor( ThreadFactory threadFactory) |
Renvoyer une instance de type ScheduledExecutorService qui utilise un seul thread pour l'exécution des tâches |
static ScheduledExecutorService unconfigurableScheduledExecutorService( ScheduledExecutorService executor) |
Renvoyer une instance de type ScheduledExecutorService qui délègue les invocations de ses méthodes vers celles de l'instance fournie en paramètre |
Il est possible d'utiliser la méthode execute() héritée de l'interface Executor ou des surcharges de la méthode submit() héritée de l'interface ExecutorService pour demander l'exécution immédiate d'une tâche.
Elle définit aussi plusieurs méthodes qui peuvent avoir en paramètres un délai et ou une période relative mais pas de valeur absolue comme une date ou un timestamp :
Méthode |
Rôle |
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) |
Planifier l'exécution du Callable fourni en paramètre après le délai précisé |
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) |
Planifier l'exécution du Runnable fourni en paramètre après le délai précisé |
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) |
Planifier l'exécution du Runnable fourni en paramètre après le délai précisé. La prochaine exécution est déterminée en ajoutant l'initialDelay et la période multipliée par le nombre d'exécutions |
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) |
Planifier l'exécution du Runnable fourni en paramètre après le délai précisé. La prochaine exécution est déterminée en ajoutant le delay au timestamp de la fin de la dernière exécution |
Les méthodes qui permettent de demander une planification renvoient une instance de type ScheduledFuture. Cette interface hérite des interfaces Delayed et Future : elle permet de demander l'annulation de l'exécution, de déterminer si elle est terminée et d'obtenir le résultat de l'exécution.
Lors de l'exécution d'un Runnable, comme il ne peut pas renvoyer de valeur, l'invocation de la méthode get() du ScheduledFuture renvoie toujours null lorsque l'exécution de la tâche est terminée.
Cette interface hérite des interfaces Comparable<Delayed>, Delayed et Future<V>.
Une instance de type ScheduledFuture permet de gérer le statut de l'exécution d'une tâche planifiée : déterminer si elle est terminée, obtenir le résultat, annuler l'exécution, ... C'est notamment une instance de ce type qui est retournée par les méthodes de l'interface ScheduledExecutorService.
Java 5.0 propose la classe java.util.concurrent.ScheduledThreadPoolExecutor qui implémente l'interface ScheduledExecutorService et hérite de la classe ThreadPoolExecutor.
Elle permet d'exécuter des tâches après un délai d'attente ou de manière périodique en utilisant un pool de threads.
La classe ScheduledExecutorService est un ExecutorService qui peut planifier l'exécution de tâches après un délai d'attente ou l'exécution répétée de tâches avec un intervalle de temps fixe entre chaque exécution. Les tâches sont exécutées de manière asynchrone par un des threads du pool.
Pour obtenir une instance de type ScheduledExecutorService, il est possible :
Exemple : |
package fr.jmdoudoux.dej.executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
public class TestScheduledExecutorService {
public static void main(String[] args) {
ScheduledExecutorService ses1 = new ScheduledThreadPoolExecutor(10);
ScheduledExecutorService ses2 = Executors.newScheduledThreadPool(10);
}
} |
La méthode schedule() permet de demander l'exécution d'une tâche éventuellement différée d'un certain délai initial.
Exemple ( code Java 5.0 ) : |
package fr.jmdoudoux.dej.executor;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
public class TestScheduledExecutorService {
public static void main(String[] args) {
final ScheduledExecutorService executor = Executors
.newScheduledThreadPool(2);
final Runnable maTache = new Runnable() {
public void run() {
System.out.println("Ma tache (" + Thread.currentThread().getName()
+ ") " + new Date());
}
};
ScheduledFuture<String> maTacheFuture = executor.schedule(
new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("Execution de la tache ("
+ Thread.currentThread().getName() + ") " + new Date());
return "Resultat de la tache";
}
}, 10, SECONDS);
System.out.println("Autre traitement (" + Thread.currentThread().getName()
+ ") " + new Date());
try {
System.out.println("Resultat = " + maTacheFuture.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
}
}
|
Résultat : |
Autre traitement (main) Sun Jun 15 17:42:45 CET 2014
Execution de la tache (pool-1-thread-1) Sun Jun 15 17:42:55 CET 2014
Resultat = Resultat de la tache |
La méthode scheduleAtFixedRate() permet de demander l'exécution périodique d'une tâche. La première exécution peut être différée d'un certain délai. La période est utilisée à partir de la première exécution pour déterminer l'exécution suivante en ajoutant la période à l'heure de début de l'exécution. Si le temps d'exécution de la tâche est supérieur à la période lors l'exécution suivante est effectuée immédiatement. La tâche ne sera exécutée que par un seul thread.
La tâche est exécutée périodiquement jusqu'à ce qu'une des exécutions lève une exception ou que la méthode shutdown() ou shutdownNow() du ScheduleExecutorService soit invoquée.
Exemple ( code Java 5.0 ) : |
package fr.jmdoudoux.dej.executor;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
public class TestScheduledExecutorService {
public static void main(String[] args) {
final ScheduledExecutorService executor = Executors
.newScheduledThreadPool(2);
final Runnable maTache = new Runnable() {
public void run() {
System.out.println("Ma tache (" + Thread.currentThread().getName()
+ ") " + new Date());
}
};
final ScheduledFuture<?> maTacheFuture = executor.scheduleAtFixedRate(
maTache, 10, 10, SECONDS);
final ScheduledFuture<?> maTacheFuture2 = executor.scheduleAtFixedRate(
maTache, 5, 10, SECONDS);
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
executor.shutdown();
}
});
}
}
|
Pour planifier l'exécution d'une tâche de manière répétée durant une certaine période, il faut planifier l'exécution de la tâche et l'exécution d'une autre tâche déclenchée à la fin du délai dont le but est d'arrêter l'exécution de la tâche planifiée.
Exemple ( code Java 5.0 ) : |
package fr.jmdoudoux.dej.executor;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
public class TestScheduledExecutorService {
public static void main(String[] args) {
final ScheduledExecutorService executor = Executors
.newScheduledThreadPool(2);
final Runnable maTache = new Runnable() {
public void run() {
System.out.println("Ma tache " + new Date());
}
};
final ScheduledFuture<?> maTacheFuture = executor.scheduleAtFixedRate(
maTache, 10, 10, SECONDS);
executor.schedule(new Runnable() {
public void run() {
maTacheFuture.cancel(true);
executor.shutdown();
System.out.println("Arret de l'executor");
}
}, 60, SECONDS);
}
}
|
La méthode scheduleAtFixedDelay() permet de demander l'exécution périodique d'une tâche. La première exécution peut être différée d'un certain délai initial. La prochaine exécution est déterminée en ajoutant le délai à l'heure de fin d'exécution de la tâche.
Les exécutions successives d'une tâche planifiée en utilisant les méthodes scheduleAtFixedRate() ou scheduleWithFixedDelay() ne se chevauchent pas.
Comme tout ExecutorService, il est important d'invoquer la méthode shutdown() ou shutdownNow() lorsqu'il n'est plus utilisé pour arrêter le thread qui lui est associé.
Par défaut, si une tâche est annulée avant son invocation alors son exécution est supprimée mais elle reste dans la pile des tâches. Pour supprimer les tâches annulées de la pile, il faut au préalable avoir invoqué la méthode setRemoveOnCancelPolicy() en lui passant la valeur true en paramètre.
Quartz est une bibliothèque open source largement utilisée pour planifier l'exécution de tâches. C'est une des bibliothèques de planification open source les plus populaires.
Quartz utilise plusieurs concepts
Un job peut être associé à plusieurs triggers mais un trigger n'est associé qu'à un seul job.
Quartz peut être téléchargé à l'url : http://www.quartz-scheduler.org/downloads/
Quartz possède une dépendance sur la bibliothèque slf4j.
Plusieurs versions de Quartz ont été diffusées :
Attention : il existe de grosses différences entre les API de la version 1.x et 2.x de Quartz notamment des classes qui sont devenues des interfaces.
Cet exemple va simplement afficher un message sur la console toutes les 5 secondes.
Il faut ajouter les dépendances requises (quartz, log4j et slf4j-log4j) au classpath, par exemple avec Maven.
Exemple : |
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>fr.jmdoudoux.dej.quartz</groupId>
<artifactId>TestQuartz</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.6</version>
</dependency>
</dependencies>
</project>
|
Il faut définir les traitements à exécuter dans une classe qui implémente l'interface org.quartz.Job.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class MonJob implements Job {
@Override
public void execute(final JobExecutionContext context) throws JobExecutionException {
System.out.println("Execution de mon job");
}
} |
La mise en oeuvre de Quartz pour exécuter une tâche de manière répétitive suit plusieurs étapes :
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import java.util.Date;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.impl.JobDetailImpl;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.triggers.SimpleTriggerImpl;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetailImpl jobDetail = new JobDetailImpl();
jobDetail.setName("Mon job");
jobDetail.setJobClass(MonJob.class);
final SimpleTriggerImpl simpleTrigger = new SimpleTriggerImpl();
simpleTrigger.setStartTime(new Date(System.currentTimeMillis() + 1000));
simpleTrigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
simpleTrigger.setRepeatInterval(5000);
simpleTrigger.setName("Trigger execution toutes les 5 secondes");
scheduler.start();
scheduler.scheduleJob(jobDetail, simpleTrigger);
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
Quartz propose une API dont les principales classes et interfaces sont :
Depuis la version 2.0, Quartz propose plusieurs builders qui facilitent la création d'instances de différents types grâce à l'utilisation de fluent API :
Chaque builder propose une ou plusieurs méthodes statiques. Il est possible de faire un import static de ces méthodes pour faciliter leur utilisation :
Exemple : |
import static org.quartz.JobBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.CalendarIntervalScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.DateBuilder.*; |
La classe DateBuilder propose des méthodes pour faciliter la création d'instances de type java.util.Date : elle est particulièrement utile pour créer une instance de type Date en fonction de différents critères et/ou de données fournies ce qui est pratique lors de la définition de triggers notamment pour le calcul de dates de début et de fin.
Méthode |
Rôle |
static Date dateOf(int hour, int minute, int second) |
Obtenir une instance de type Date à partir de l'heure précisée en paramètre |
static Date dateOf(int hour, int minute, int second, int dayOfMonth, int month) |
Obtenir une instance de type Date à partir des éléments précisés en paramètres et l'année courante |
static Date dateOf(int hour, int minute, int second, int dayOfMonth, int month, int year) |
Obtenir une instance de type Date à partir des différents éléments passés en paramètre |
static Date evenHourDate(Date date) |
Renvoyer la prochaine heure à partir de celle fournie en paramètre |
static Date evenHourDateAfterNow() |
Renvoyer la prochaine heure à partir de maintenant |
static Date evenHourDateBefore(Date date) |
Renvoyer la précédente heure à partir de celle fournie en paramètre |
static Date evenMinuteDate(Date date) |
Renvoyer la prochaine minute à partir de celle fournie en paramètre |
static Date evenMinuteDateAfterNow() |
Renvoyer la prochaine minute à partir de celle fournie en paramètre |
static Date evenMinuteDateBefore(Date date) |
Renvoyer la précédente minute à partir de celle fournie en paramètre |
static Date evenSecondDate(Date date) |
Renvoyer la prochaine seconde à partir de celle fournie en paramètre |
static Date evenSecondDateAfterNow() |
Renvoyer la prochaine seconde à partir de maintenant |
static Date evenSecondDateBefore(Date date) |
Renvoyer la précédente seconde à partir de celle fournie en paramètre |
static Date futureDate(int interval, DateBuilder.IntervalUnit unit) |
Obtenir la date correspondant à maintenant plus l'intervalle de temps fourni en paramètre (durée et unité) |
static Date translateTime(Date date, TimeZone src, TimeZone dest) |
Obtenir une instance de type Date qui encapsule la date fournie en paramètre relative au fuseau horaire passé en paramètre |
Généralement, les builders proposent une valeur par défaut pour les propriétés qui ne sont pas explicitement valorisées.
Le scheduler est le coeur de Quartz. Il stocke un ensemble de JobDetail et de Trigger : il a la charge de déclencher les Trigger selon leur configuration et d'exécuter les Job qui lui sont associés.
Ces fonctionnalités sont décrites dans l'interface org.quartz.Scheduler. Elle propose plusieurs méthodes pour :
Pour obtenir une instance de type Scheduler, il faut utiliser une fabrique de type SchedulerFactory.
Quartz fournit en standard la classe org.quartz.impl.StdSchedulerFactory qui implémente l'interface SchedulerFactory pour créer des instances à partir d'une configuration sous la forme d'un fichier properties. Cette fabrique recherche un fichier de configuration pour paramétrer l'instance créée. Si ce fichier facultatif n'est pas trouvé alors c'est une instance avec la configuration par défaut qui est créée.
La configuration peut aussi être fournie à la fabrique en invoquant une des surcharges de la méthode initialize() qui attend en paramètre :
A sa création, une instance de type Scheduler est en mode stand-by : elle doit être démarrée en invoquant la méthode start().
Exemple : |
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start(); |
Tant que le scheduler n'est pas démarré aucun trigger n'est déclenché.
Pour arrêter un scheduler, il faut invoquer une des surcharges de la méthode shutdown() :
Dès qu'un scheduler est éteint en invoquant sa méthode shutdown(), il ne peut pas être redémarré : il est nécessaire de créer une nouvelle instance.
Il est possible de mettre le scheduler en pause en utilisant la méthode standby(). Pour le réactiver, il faut de nouveau invoquer la méthode start().
Pour démarrer Quartz dans une webapp, il y a deux solutions :
La première solution consiste à déclarer un ServletContextListener de type QuartzInitializerListener. Plusieurs paramètres de configuration peuvent être définis dans les paramètres du contexte.
Exemple : |
<context-param>
<param-name>quartz:config-file</param-name>
<param-value>/mawebapp/config/webapp_quartz.properties</param-value>
</context-param>
<context-param>
<param-name>quartz:shutdown-on-unload</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>quartz:wait-on-shutdown</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>quartz:start-scheduler-on-load</param-name>
<param-value>true</param-value>
</context-param>
<listener>
<listener-class>
org.quartz.ee.servlet.QuartzInitializerListener
</listener-class>
</listener>
|
Plusieurs paramètres d'initialisation peuvent être définis dans le contexte :
La seconde solution est de déclarer une servlet de type org.quartz.ee.servlet.QuartzInitializerServlet en demandant son chargement au démarrage de la webapp.
Exemple : |
<servlet>
<servlet-name>QuartzInitializer</servlet-name>
<servlet-class>org.quartz.ee.servlet.QuartzInitializerServlet</servlet-class>
<init-param>
<param-name>shutdown-on-unload</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
|
La servlet va stocker une instance de type StdSchedulerFactory dans le contexte associé par défaut à la clé QuartzFactoryServlet.QUARTZ_FACTORY_KEY.
Exemple : |
StdSchedulerFactory factory = (StdSchedulerFactory) ctx
.getAttribute(QuartzFactoryServlet.QUARTZ_FACTORY_KEY); |
La servlet possède plusieurs paramètres :
Les Triggers permettent de définir la condition de déclenchement de l'exécution d'un job. Un trigger peut être associé à un objet de type JobDataMap qui permet de passer à un job des paramètres spécifiques au déclenchement.
Quartz propose plusieurs implémentations de l'interface Trigger notamment SimpleTrigger et CronTrigger.
La classe SimpleTrigger permet de définir la condition :
La classe CronTrigger permet de définir des conditions d'exécution basées sur le calendrier. Par exemple : exécution tous les jours à 08:00, exécution tous les lundis à 23:00, ...
Pour respecter le principe SOC (Separation of Concern), l'interface Job encapsule les traitements et l'interface Trigger encapsule les conditions de déclenchement d'une ou plusieurs exécutions.
Un Job peut être créé et stocké dans le scheduler sans être associé à un Trigger. Cela permet par exemple de conserver un job dans le scheduler après que tous les triggers qui lui étaient associés soient expirés : il est possible de les replanifier en les associant à de nouveaux triggers. Plusieurs triggers peuvent être associés à un même job.
Les jobs et les triggers sont identifiés par une clé (respectivement JobKey et TriggerKey) lorsqu'ils sont enregistrés dans le scheduler.
Ils peuvent être regroupés dans des catégories par exemple « reporting », « mise à jour », « maintenance », ...
Le nom de la clé doit être unique dans un même groupe.
Les traitements à exécuter sont encapsulés dans une classe qui implémente l'interface org.quartz.Job.
L'interface Job ne définit qu'une seule méthode execute().
Exemple : |
package fr.jmdoudoux.dej.quartz;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class MonJob implements Job {
@Override
public void execute(final JobExecutionContext context) throws JobExecutionException {
System.out.println("Execution de mon job");
}
} |
Il ne faut pas créer une instance de type Job directement mais créer une instance de type JobDetail qui va permettre à Quartz de créer et gérer une instance.
La classe JobDetailImpl implémente l'interface JobDetail.
Exemple : |
final JobDetailImpl jobDetail = new JobDetailImpl();
jobDetail.setName("Mon job");
jobDetail.setJobClass(MonJob.class); |
Il faut au minimum définir le nom qui est l'identifiant et la classe de type Job qui encapsule les traitements.
Il est possible d'associer des paramètres au job en utilisant une instance de type org.quartz.JobDataMap.
Exemple : |
JobDataMap map = new JobDataMap();
map.put("monParametre","12345");
jobDetail.setJobDataMap(map); |
Il est alors possible de récupérer ces paramètres dans la méthode execute() du job en invoquant les méthodes getJobDetail() et getJobDataMap() du paramètre de type JobExecutionContext.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class MonJob implements Job {
@Override
public void execute(final JobExecutionContext context) throws JobExecutionException {
final JobDataMap map = context.getJobDetail().getJobDataMap();
final String monParametre = map.getString("monParametre");
System.out.println("Execution de mon job pour " + monParametre);
}
} |
La classe JobDetailImpl possède plusieurs constructeurs qui sont tous deprecated sauf le constructeur par défaut. Plutôt que d'utiliser ces constructeurs, il est préférable d'utiliser la classe JobBuilder qui implémente le motif de conception monteur.
Elle définit plusieurs méthodes pour configurer et obtenir l'instance de type JobDetail.
Méthode |
Rôle |
JobDetail build() |
Obtenir l'instance de type JobDetail |
static JobBuilder newJob() |
Fabrique pour obtenir l'instance de type JobBuilder |
static JobBuilder newJob(Class<? extends Job> jobClass) |
Fabrique pour obtenir l'instance de type JobBuilder en passant en paramètre la classe qui implémente l'interface Job |
JobBuilder ofType(Class<? extends Job> jobClazz) |
Préciser la classe qui implémente l'interface Job |
JobBuilder requestRecovery() |
Préciser au scheduler de réexécuter le job si une situation de recovery ou de fail-over survient |
JobBuilder requestRecovery(boolean jobShouldRecover) |
Préciser au scheduler de réexécuter ou non le job si une situation de recovery ou de fail-over survient |
JobBuilder setJobData(JobDataMap newJobDataMap) |
Préciser les paramètres associés au job |
JobBuilder storeDurably() |
Préciser au scheduler de conserver le job si celui-ci n'est plus associé à un trigger |
JobBuilder storeDurably(boolean jobDurability) |
Préciser au scheduler de conserver ou non le job si celui-ci n'est plus associé à un trigger |
JobBuilder usingJobData(JobDataMap newJobDataMap) |
Ajouter les paramètres fournis à ceux associés au job |
JobBuilder usingJobData(String dataKey, Boolean value) |
Ajouter un paramètre au JobDataMap |
JobBuilder withDescription(String jobDescription) |
Préciser une description |
JobBuilder withIdentity(JobKey jobKey) |
Préciser l'identifiant du job |
JobBuilder withIdentity(String name) |
Préciser le nom du job qui sera utilisé dans son identifiant |
JobBuilder withIdentity(String name, String group) |
Préciser le nom du job et le nom de son groupe qui seront utilisés dans son identifiant |
La méthode newJob() est une fabrique pour créer une instance de type JobBuilder. Il suffit alors d'invoquer les différentes méthodes pour configurer le builder et invoquer sa méthode build() pour obtenir l'instance correspondante.
Exemple : |
final JobDetail jobDetail = JobBuilder
.newJob(MonJob.class)
.withIdentity("monJob", "groupe_1")
.usingJobData("monParametre", "12345")
.build(); |
Les données contenues dans le JobDataMap sont partagées entre les différentes exécutions d'un même Trigger. Il est donc nécessaire de s'assurer de la concurrence d'accès pour mettre à jour ces données.
Depuis la version 2.0, il faut annoter la classe du job avec l'annotation @PersistJobDataAfterExecution pour préciser que le job met à jour les données partagées dans son contexte.
L'interface StatefulJob doit être implémentée par la classe d'un job pour préciser qu'il n'est pas possible d'exécuter le job en parallèle. Si plusieurs Triggers déclenchent l'exécution du job en même temps, ceux-ci seront exécutés les uns après les autres.
Depuis la version 2.0, cette interface est deprecated : il est préférable d'annoter la classe du Job avec l'annotation @DisallowConcurrentExecution. Pour maintenir la compatibilité, l'interface StatefulJob est annotée avec @DisallowConcurrentExecution et @PersistJobDataAfterExecution.
La classe JobExecutionContext encapsule des informations liées à l'exécution du job notamment en permettant un accès aux instances du Scheduler, du Trigger et du JobDetail.
La classe JobDetail encapsule la configuration du job à fournir au scheduler.
A chaque fois que l'exécution d'un job est déclenchée, le moteur de Quartz va créer une nouvelle instance à partir du type fourni en paramètre dans le JobDetail en utilisant par défaut la fabrique JobFactory.
C'est la raison pour laquelle, la classe qui implémente :
L'échange de données entre plusieurs exécutions peut se faire en utilisant une instance de type JobDataMap.
Toutes les données stockées dans un JobDataMap doivent être sérialisables.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
public class MonJob implements Job {
@Override
public void execute(final JobExecutionContext context) throws JobExecutionException {
final JobDataMap map = context.getJobDetail().getJobDataMap();
final String monParametre = map.getString("monParametre");
final JobKey key = context.getJobDetail().getKey();
System.out.println("Execution de '" + key + "' pour " + monParametre);
}
} |
Dans la classe qui encapsule le job, il est possible de définir des setters pour des propriétés correspondants aux noms des paramètres passés. L'implémentation de JobFactory va alors par introspection invoquer ses setters en leur passant en paramètres les valeurs correspondantes au moment de la création de l'instance. Ceci évite d'avoir à obtenir l'instance de type JobDataMap du context et d'extraire les valeurs.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
public class MonJob implements Job {
private String monParametre;
@Override
public void execute(final JobExecutionContext context) throws JobExecutionException {
final JobKey key = context.getJobDetail().getKey();
System.out.println("Execution de '" + key + "' pour " + this.monParametre);
}
public void setMonParametre(final String monParametre) {
this.monParametre = monParametre;
}
} |
Les données contenues dans une instance de type JobDataMap obtenue à partir du contexte contiennent les paramètres du JobDataMaps, du JobDetail et du Trigger. Si deux paramètres ont le même nom c'est celui du trigger qui est prioritaire.
L'annotation @DisallowConcurrentExecution utilisée sur une classe qui encapsule un job permet de préciser à Quartz de ne pas exécuter de manière concurrente plusieurs instances d'un même JobDetail utilisant la classe. La discrimination ne se fait donc pas sur une instance de la classe mais sur une instance de type JobDetail.
L'annotation @PersistJobDataAfterExecution utilisée sur une classe qui encapsule un job permet de préciser à Quartz de mettre à jour les données de la copie de JobDataMap après que la méthode execute() a été correctement exécutée (aucune exception n'est levée durant ses traitements). La prochaine exécution du JobDetail recevra les données mises à jour dans son instance de type JobDataMap. Cette annotation s'utilise sur la classe du job mais s'applique par instance de type JobDetail.
Lorsque l'annotation @PersistJobDataAfterExecution est utilisée, il est fortement recommandé d'utiliser aussi l'annotation @DisallowConcurrentExecution pour éviter des problèmes liés aux valeurs sauvegardées dans le JobDataMap à la fin de l'exécution concurrente de JobDetail.
Un JobDetail contient plusieurs propriétés :
Par défaut, le scheduler supprime les jobs qui ne sont plus associés à aucun trigger : pour demander au scheduler de les conserver, il faut invoquer la méthode setDurability(true) du JobDetail correspondant.
En cas de soucis dans les traitements de la méthode execute(), il faut uniquement lever une exception de type JobExecutionException. Généralement le code de la méthode est contenu dans un try/catch global à la méthode pour catcher les exceptions et les chainer dans une nouvelle exception de type JobExecutionException.
L'exception JobExecutionException possède trois propriétés qui seront utilisées par le scheduler soit pour réexécuter le JobDetail soit pour annuler la planification:
Si la propriété refireImmediately est à true alors les deux autres propriétés sont ignorées.
Quartz fournit en standard plusieurs implémentations de type Job permettant de répondre à quelques besoins basiques.
Pour utiliser ces jobs, il faut ajouter la bibliothèque quartz-jobs-x.y.z.jar au classpath.
La classe org.quartz.jobs.ee.mail.SendMailJob est une implémentation d'un job qui permet l'envoi d'un mail.
Exemple : |
JobDetail jobDetail = new JobDetail("emailJob", Scheduler.DEFAULT_GROUP, SendMailJob.class);
JobDataMap map = new JobDataMap();
map.put(SendMailJob.PROP_SMTP_HOST,"smtp.test.fr");
map.put(SendMailJob.PROP_SENDER,"emetteur@test.fr");
map.put(SendMailJob.PROP_RECIPIENT,"destinataire@test.fr");
map.put(SendMailJob.PROP_SUBJECT,"Sujet du mail");
map.put(SendMailJob.PROP_MESSAGE,"Corps du mail");
jobDetail.setJobDataMap(map); |
L'utilisation de ce job requiert que les bibliothèques javamail et activation soient incluses dans le classpath.
La classe org.quartz.jobs.NoOpJob est un Job qui ne fait rien. Il peut être utile pour permettre de déclencher des traitements exécutés par des listeners sur le trigger ou le job.
La classe org.quartz.jobs.NativeJob permet d'exécuter une commande sur le système d'exploitation.
Exemple : |
final JobDataMap map = new JobDataMap();
map.put(org.quartz.jobs.NativeJob.PROP_COMMAND, "c:\\windows\\notepad.exe");
map.put("PROP_WAIT_FOR_PROCESS", "true");
final JobDetail jobDetail = JobBuilder.newJob(NativeJob.class)
.withIdentity("monJob").usingJobData(map).build(); |
La classe org.quartz.jobs.ee.ejb.EJBInvokerJob permet d'invoquer un EJB.
Les informations nécessaires pour connaitre l'EJB à invoquer doivent être fournies dans le JobDataMap associé au job en utilisant les clés :
La classe org.quartz.jobs.ee.jmx.JMXInvokerJob permet d'invoquer un MBean.
Les informations nécessaires pour connaitre le MBean à invoquer doivent être fournies dans le JobDataMap associé au job en utilisant les clés : JMX_OBJECTNAME, JMX_METHOD et JMX_PARAMDEFS.
Attention : ce job ne peut invoquer qu'un MBean qui soit enregistré sur un serveur de MBean de la même JVM que le scheduler.
Les classes org.quartz.jobs.ee.jms.SendDestinationMessageJob, org.quartz.jobs.ee.jms.SendQueueMessageJob et org.quartz.jobs.ee.jms.SendTopicMessageJob permettent d'envoyer un message JMS respectivement vers une destination, une queue ou un topic.
Les informations nécessaires à l'émission du message doivent être fournies dans le JobDataMap associé au job.
Attention : cette classe n'est utilisable qu'à partir de JMS 1.1.
La classe org.quartz.jobs.FileScanJob permet de vérifier si la date de dernière modification d'un fichier a été modifiée par rapport à la précédente invocation. Si le fichier a été modifié, un FileScanListener est invoqué.
Les informations nécessaires à l'exécution de ce job doivent être fournies dans le JobDataMap associé au job en utilisant les clés :
Quartz propose plusieurs implémentations de type Trigger pour répondre à différents besoins de planifications. Les triggers possèdent un certain nombre de propriétés qui permettent de définir les critères de déclenchement.
Chaque trigger est identifié de manière unique par une instance de type TriggerKey.
Plusieurs propriétés sont communes à tous les types de Trigger :
Propriété |
Rôle |
jobKey |
Identité du job qui sera exécuté lors du déclenchement |
startTime |
Date/heure à partir de laquelle le déclenchement d'événements peut débuter. Ceci permet de définir des triggers par avance |
endTime |
Date/heure de fin de déclenchement du trigger |
priority |
Priorité du trigger. Au cas où Quartz ne possède plus de threads pour exécuter des jobs déclenchés au même moment. Dans ce cas, Quartz prend d'abord les triggers ayant la priorité la plus élevée. La valeur peut être négative ou positive. L'heure de déclenchement est toujours prioritaire |
misfireInstruction |
Une misfire instruction permet de préciser au scheduler comment il doit se comporter si le déclenchement d'un trigger est raté. Le déclenchement raté d'un trigger peut avoir plusieurs origines notamment :
Chaque trigger possède aussi des misfire instructions qui lui sont propres. L'instruction MISFIRE_INSTRUCTION_SMART_POLICY définit celle par défaut pour un type de trigger. |
A partir de la version 2.0 de Quartz, il faut créer des instances de type Trigger en utilisant la classe TriggerBuilder. La classe TriggerBuilder est un builder qui facilite la création d'une instance de type Trigger.
Elle possède plusieurs méthodes pour configurer le trigger et obtenir une instance à partir de cette configuration :
Méthode |
Rôle |
T build() |
Obtenir l'instance de type Trigger |
TriggerBuilder<T> endAt(Date endTime) |
Définir la date/heure de fin de déclenchement du trigger |
TriggerBuilder<T> forJob(JobDetail jobDetail) |
Définir le job associé au trigger qui sera obtenu à partir du JobDetail fourni en paramètre |
TriggerBuilder<T> forJob(JobKey jobKey) |
Définir le job associé au trigger qui sera celui correspondant au JobKey fourni en paramètre |
TriggerBuilder<T> forJob(String jobName) |
Définir le job associé au trigger qui sera celui correspondant au JobKey dont le nom est fourni en paramètre avec le nom du groupe par défaut |
TriggerBuilder<T> forJob(String jobName, String jobGroup) |
Définir le job associé au trigger qui sera celui correspondant au JobKey dont le nom et le nom du groupe sont fournis en paramètres |
TriggerBuilder<T> modifiedByCalendar(String calendarName) |
Définir le nom du Calendar qui sera appliqué pour déterminer les déclenchements |
static TriggerBuilder<Trigger> newTrigger() |
Obtenir une nouvelle instance de type TriggerBuilder |
TriggerBuilder<T> startAt(Date startTime) |
Définir la date/heure de début de déclenchement du trigger avec celle fournie en paramètre |
TriggerBuilder<T> startNow() |
Définir la date/heure de début de déclenchement du trigger à maintenant |
TriggerBuilder<T> usingJobData(JobDataMap newJobDataMap) |
Définir l'instance de type JobDataMap associée au trigger |
TriggerBuilder<T> usingJobData(String key, Boolean value) |
Ajouter une paire clé/valeur fournie en paramètres dans l'instance de type JobDataMap |
TriggerBuilder<T> withDescription(String description) |
Définir la description du trigger |
TriggerBuilder<T> withIdentity(String name) |
Définir l'identité du trigger : l'instance de type TriggerKey sera composée du nom fourni en paramètre et du nom du groupe par défaut |
TriggerBuilder<T> withIdentity(String name, String group) |
Définir l'identité du trigger : l'instance de type TriggerKey sera composée du nom et du nom du groupe fournis en paramètres |
TriggerBuilder<T> withIdentity(TriggerKey key) |
Définir l'identité du trigger : l'instance de type TriggerKey est celle fournie en paramètre |
TriggerBuilder<T> withPriority(int priority) |
Définir la priorité du trigger |
<SBT extends T> TriggerBuilder<SBT> withSchedule(ScheduleBuilder<SBT> scheduleBuilder) |
Définir l'instance de type ScheduleBuilder qui permettra de définir la planification des déclenchements |
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetail jobDetail = JobBuilder.newJob(MonJob.class)
.withIdentity("monJob").build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("monTrigger", "monGroup")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInHours(1).repeatForever())
.build();
scheduler.start();
scheduler.scheduleJob(jobDetail, trigger);
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
Une instance de type JobDataMaps peut être associée à un trigger pour passer des données sous la forme de paires clé/valeur disponibles à chaque déclenchement.
Lorsqu'un déclenchement est raté (le job associé ne peut pas être exécuté au moment prévu), le trigger passe à l'état misfired. Ceci peut par exemple survenir lorsque tous les threads du pool sont déjà occupés à exécuter d'autres jobs déclenchés.
Il est possible de préciser au scheduler le comportement qu'il doit avoir dans une telle circonstance à l'aide d'une misfire instruction. Chaque type de trigger possède un ensemble défini de misfire instructions.
Lorsque le scheduler démarre, il recherche si le déclenchement de triggers persistants a été raté et pour ceux concernés il applique le comportement défini par la misfire instruction configurée dans le trigger.
Quartz propose trois implémentations de l'interface Trigger :
Un SimpleTrigger permet de planifier :
L'implémentation de SimpleTrigger possède plusieurs propriétés qui permettent de définir la ou les conditions de déclenchement :
Propriété |
Rôle |
repeatCount |
Définir le nombre de déclenchements souhaité avant la suppression du trigger. Les valeurs possibles sont zéro, une valeur positive ou la constante SimpleTrigger.REPEAT_INDEFINITELY |
endTime |
Date/heure de fin des déclenchements et de la suppression du trigger. Si elle est définie, elle prend le pas sur la propriété repeatCount. Ceci permet d'éviter d'avoir à calculer le nombre de répétitions voulues durant la plage de dates. Dans ce cas, la propriété repeatCount doit être supérieure au nombre de déclenchement qui doivent survenir avant la date/heure de fin |
repeatInterval |
Définir l'intervalle en millisecondes entre chaque répétition. Les valeurs possibles sont zéro ou une valeur positive. La valeur zéro provoque tous les déclenchements de manière concurrente ou quasi concurrente selon les capacités du Scheduler |
startTime |
Date/heure de début du premier déclenchement |
Avant la version 2.0 de Quartz, org.quartz.SimpleTrigger est une classe que l'on peut instancier en invoquant un de ses constructeurs.
Exemple : |
SimpleTrigger trigger = new SimpleTrigger("monTrigger",
Scheduler.DEFAULT_GROUP, SimpleTrigger.REPEAT_INDEFINITELY, 30000); |
Il est possible d'invoquer le constructeur par défaut et d'utiliser ses setters pour configurer l'instance.
Exemple : |
SimpleTrigger trigger = new SimpleTrigger();
trigger.setName("monTrigger");
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
trigger.setRepeatInterval(30000); |
A partir de la version 2.0 de Quartz, org.quartz.SimpleTrigger est une interface. La classe SimpleTriggerImpl implémente cette interface. Elle possède plusieurs constructeurs qui sont tous deprecated sauf le constructeur par défaut.
Plutôt que d'utiliser ces constructeurs, il faut utiliser la classe TriggerBuilder qui implémente le motif de conception monteur. Pour obtenir une instance de type SimpleTrigger, il faut lui associer un SimpleScheduleBuilder.
La classe SimpleScheduleBuilder implémente le motif de conception monteur pour faciliter la création d'une instance configurée de type SimpleTrigger.
Elle possède plusieurs méthodes pour configurer et obtenir une instance :
Méthode |
Rôle |
MutableTrigger build() |
Obtenir l'instance de type Trigger selon la configuration courante : ne devrait pas être invoquée par le développeur mais uniquement par un TriggerBuilder |
SimpleScheduleBuilder repeatForever() |
Préciser que la répétition des déclenchements est infinie |
static SimpleScheduleBuilder repeatHourlyForever() |
Créer une instance configurée pour une exécution infinie à une heure d'intervalle |
static SimpleScheduleBuilder repeatHourlyForever(int hours) |
Créer une instance configurée pour une exécution infinie au nombre d'heures passé en paramètre d'intervalle |
static SimpleScheduleBuilder repeatHourlyForTotalCount(int count) |
Créer une instance configurée pour une exécution du nombre de fois passé en paramètre à une heure d'intervalle |
static SimpleScheduleBuilder repeatHourlyForTotalCount(int count, int hours) |
Créer une instance configurée pour une exécution du nombre de fois passé en paramètre au nombre d'heures passé en paramètre d'intervalle |
static SimpleScheduleBuilder repeatMinutelyForever() |
Créer une instance configurée pour une exécution infinie à une minute d'intervalle |
static SimpleScheduleBuilder repeatMinutelyForever(int minutes) |
Créer une instance configurée pour une exécution infinie au nombre de minutes passé en paramètre d'intervalle |
static SimpleScheduleBuilder repeatMinutelyForTotalCount(int count) |
Créer une instance configurée pour une exécution du nombre de fois passé en paramètre à une minute d'intervalle |
static SimpleScheduleBuilder repeatMinutelyForTotalCount(int count, int minutes) |
Créer une instance configurée pour une exécution du nombre de fois passé en paramètre au nombre de minutes passé en paramètre d'intervalle |
static SimpleScheduleBuilder repeatSecondlyForever() |
Créer une instance configurée pour une exécution infinie à une seconde d'intervalle |
static SimpleScheduleBuilder repeatSecondlyForever(int seconds) |
Créer une instance configurée pour une exécution infinie au nombre de secondes passé en paramètre d'intervalle |
static SimpleScheduleBuilder repeatSecondlyForTotalCount(int count) |
Créer une instance configurée pour une exécution du nombre de fois passé en paramètre à une seconde d'intervalle |
static SimpleScheduleBuilder repeatSecondlyForTotalCount(int count, int seconds) |
Créer une instance configurée pour une exécution du nombre de fois passé en paramètre au nombre de secondes passé en paramètre d'intervalle |
static SimpleScheduleBuilder simpleSchedule() |
Créer une instance |
SimpleScheduleBuilder withIntervalInHours(int intervalInHours) |
Préciser un intervalle de répétitions en heures |
SimpleScheduleBuilder withIntervalInMilliseconds(long intervalInMillis) |
Préciser un intervalle de répétitions en millisecondes |
SimpleScheduleBuilder withIntervalInMinutes(int intervalInMinutes) |
Préciser un intervalle de répétitions en minutes |
SimpleScheduleBuilder withIntervalInSeconds(int intervalInSeconds) |
Préciser un intervalle de répétitions en secondes |
SimpleScheduleBuilder withMisfireHandlingInstructionFireNow() |
Définir l'instruction de type Trigger.MISFIRE_INSTRUCTION_FIRE_NOW si un déclenchement est raté |
SimpleScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() |
Définir l'instruction de type Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY |
SimpleScheduleBuilder withMisfireHandlingInstructionNextWithExistingCount() |
Définir l'instruction de type Trigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT si un déclenchement est raté |
SimpleScheduleBuilder withMisfireHandlingInstructionNextWithRemainingCount() |
Définir l'instruction de type Trigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT si un déclenchement est raté |
SimpleScheduleBuilder withMisfireHandlingInstructionNowWithExistingCount() |
Définir l'instruction de type Trigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT si un déclenchement est raté |
SimpleScheduleBuilder withMisfireHandlingInstructionNowWithRemainingCount() |
Définir l'instruction de type Trigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT si un déclenchement est raté |
SimpleScheduleBuilder withRepeatCount(int repeatCount) |
Préciser le nombre de fois que le trigger sera déclenché ; le nombre total d'exécutions est égal à la valeur en paramètre + 1 |
Pour obtenir une instance de type SimpleTrigger, il faut utiliser un TriggerBuilder pour les propriétés principales du trigger et un SimpleScheduleBuilder pour les propriétés spécifiques au SimpleTrigger.
L'exemple ci-dessous planifie une exécution à une heure d'intervalle débutant dans 30 minutes.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import org.quartz.DateBuilder;
import org.quartz.DateBuilder.IntervalUnit;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetail jobDetail = JobBuilder.newJob(MonJob.class)
.withIdentity("monJob").build();
final Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(new TriggerKey("monTrigger", "monGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInHours(1).repeatForever())
.startAt(DateBuilder.futureDate(30, IntervalUnit.MINUTE))
.build();
scheduler.start();
scheduler.scheduleJob(jobDetail, trigger);
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
L'exemple ci-dessous planifie une exécution immédiate.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetail jobDetail =
JobBuilder.newJob(MonJob.class).withIdentity("monJob").build();
final SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder
.newTrigger()
.withIdentity("monTrigger", "monGroup")
.build();
scheduler.start();
scheduler.scheduleJob(jobDetail, trigger);
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
Dans l'exemple ci-dessous, la propriété repeatCount est initialisée à 10 ce qui générera 11 déclenchements.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetail jobDetail = JobBuilder.newJob(MonJob.class)
.withIdentity("monJob").build();
final SimpleTrigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("monTrigger", "monGroup")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10).withRepeatCount(10))
.build();
scheduler.start();
scheduler.scheduleJob(jobDetail, trigger);
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
La classe SimpleTrigger définit plusieurs instructions pour préciser le comportement du scheduler au cas où le déclenchement du trigger est raté.
La classe CronTrigger permet de planifier des déclenchements en utilisant la syntaxe des expressions de l'outil cron des systèmes Unix. Le CronTrigger est particulièrement utile pour définir des répétitions basées sur des éléments du calendrier.
La configuration se fait en utilisant une expression de type cron. L'expression est une chaîne de caractères contenant des champs séparés par un caractère espace. Celle-ci est analysée et évaluée par une instance de type org.quartz.CronExpression.
La syntaxe d'une expression cron utilise 6 champs obligatoires et un champ facultatif. Chaque champ correspond à un élément permettant de définir la planification. Les champs doivent respecter un certain ordre :
Secondes Minutes Heures Jour_du_mois mois Jour_de_la_semaine Année (optionnel)
Chaque champ possède des valeurs acceptables et utilise éventuellement un caractère ou une combinaison de caractères spéciaux.
Champs |
Obligatoire |
Valeurs |
Caractères spéciaux |
Seconde |
Oui |
0-59 |
, - * / |
Minute |
Oui |
0-59 |
, - * / |
Heure |
Oui |
0-23 |
, - * / |
Jour du mois |
Oui |
1-31 |
, - * ? L W |
Mois |
Oui |
1-12 ou JAN-DEC ou jan-dec |
, - * / |
Jour de la semaine |
Oui |
1-7 ou SUN-SAT ou sun-sat |
, - * ? L # |
Année |
Non |
1970-2099 |
, - * / |
Le jour du mois peut avoir une valeur entre 1 et 31 mais cette valeur doit exister pour le mois considéré : c'est particulièrement vrai pour les mois de février (28 ou 29 jours selon que l'année soit bissextile ou non) et avril, juin, septembre et novembre qui ne possède pas de 31.
Le mois peut être précisé en utilisant son numéro (1-12) ou les trois premières lettres de son nom en anglais (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV et DEC)
Le jour de la semaine peut avoir une valeur entre 1 et 7 où 1 est dimanche ou être désigné par les trois premières lettres de son nom en anglais (SUN, MON, TUE, WED, THU, FRI et SAT).
Les valeurs alphabétiques ne sont pas sensibles à la casse.
Les caractères spéciaux permettent de définir des situations particulières :
Caractère |
Description |
* |
Désigner toutes les valeurs possibles pour le champ. Par exemple : dans le champ heure, cela signifie toutes les heures |
? |
Ne désigner aucune valeur particulière. Permet de préciser dans les champs jour du mois ou jour de la semaine que le champ doit être ignoré au profit de l'autre |
- |
Désigner une plage de valeurs dont les bornes sont incluses. Par exemple, 1-5 correspond aux valeurs 1, 2, 3, 4 et 5 |
, |
Désigner plusieurs valeurs. Par exemple, MON, WED, FRI dans le champ jour de la semaine correspond à lundi, mercredi et vendredi |
/ |
Désigner une valeur d'incrémentation à partir d'une valeur initiale limitée selon la plage de valeurs acceptables par le champ concerné. Par exemple, 0/15 dans le champ seconde correspond aux valeurs 0, 15, 30 et 45 et 10/10 correspond aux valeurs 10, 20, 30, 40 et 50. Attention : /35 ne signifie pas tous les 35 minutes mais toutes les 35 minutes à partir de la minute 0, ce qui est identique à 0,35 et déclenchera donc deux événements dans une même heure |
L |
Désigner la dernière valeur acceptable par le champ concerné. Il ne peut être utilisé que sur les champs jour de la semaine et jour du mois. Dans le champ jour du mois, il désigne le dernier jour du mois en tenant compte des années bissextiles pour le mois de février. Par exemple, L dans le champ jour du mois correspond au 31 janvier, 28 février pour les années non bissextiles, le 29 février pour les années bissextiles, le 31 mars, le 30 avril, ... Dans le champ jour de la semaine, cela correspond à la valeur 7 soit samedi. Il est possible de préciser un jour avant le L pour désigner le dernier jour correspondant du mois. Par exemple, 6L correspond au dernier vendredi du mois. Il est aussi possible de préciser un offset après le caractère L. Par exemple, L-3 correspond au troisième avant le dernier jour du mois. Il ne faut surtout pas utiliser plusieurs valeurs avec le caractère L |
W |
Désigner un jour de la semaine hors week end le plus proche de celui indiqué avant le caractère W. Par exemple, 15W. Si le 15 du mois est un samedi alors l'exécution aura lieu le vendredi 14. Si le 15 du mois est un dimanche alors l'exécution aura lieu le lundi 16. Si le 15 n'est ni un samedi ni un dimanche alors l'exécution a lieu le jour précisé. Il ne faut surtout pas utiliser plusieurs valeurs avec le caractère W. |
LW |
Désigner le dernier jour du mois hors week end dans le champ jour du mois |
# |
Désigner le n-ieme jour de la semaine du mois. Par exemple, LUN#1 correspond au premier lundi du mois, 6#3 correspond au troisième vendredi du mois |
Les listes de valeurs et les plages peuvent être combinées dans un même champ.
Exemple : MON-WED,SAT qui correspond à lundi, mardi, mercredi et samedi.
Les champs jour du mois et jour de la semaine sont généralement mutuellement exclusifs : si l'un à une ou plusieurs valeurs définies, l'autre peut avoir le caractère ? pour préciser de ne pas tenir compte du champ.
Exemple
Expression cron |
Description du déclenchement |
0 0 12 * * ? |
Tous les jours à midi |
0/30 * * * * ? |
Toutes les 30 secondes |
0 0/5 * * * ? |
Toutes les 5 minutes |
10 0/5 * * * ? |
10 secondes après toutes les 5 minutes |
0 30 14 ? * * |
Tous les jours à 14h30 |
0 30 14 * * ? 2014 |
Tous les jours de 2014 à 14h30 |
0 30 14 * * ? 2014-2016 |
Tous les jours de 2014, 2015 et 2016 à 14h30 |
0 * 15 * * ? |
Toutes les minutes entre 15h et 15h59 |
0 0/5 15 * * ? |
Toutes les 5 minutes entre 15h et 15h59 |
0 0/5 10,15 * * ? |
Toutes les 5 minutes entre 10h et 10h59 et entre 15h et 15h59 |
0 0/30 8-9 * * ? |
Toutes les demi-heures entre 8h00 et 10h00 (exclu) : 8:00, 8:30, 9:00 et 9:30 |
0 0-5 15 * * ? |
Toutes les minutes entre 10h et 10h05 |
0 15,45 15 * * ? |
Tous les jours à 15h15 et 15h45 |
0 0 12 ? * WED |
Tous les mercredis à midi |
0 0 12 ? 1 WED |
Tous les mercredis du mois de janvier à midi |
0 0 12 ? * MON-FRI |
Du lundi au vendredi à midi |
0 30 11-13 ? * WED,FRI |
Tous les mercredis, jeudis et vendredi à 11H30, 12H30 et 13H30 |
0 0 12 15 * ? |
Le 15 de chaque mois à 12h00 |
0 0 12 L * ? |
Le dernier jour de chaque mois à midi |
0 0 12 L-1 * ? |
L'avant dernier jour de chaque mois à midi |
0 0 12 ? * 6L |
Le dernier vendredi de chaque mois à midi |
0 0 12 ? * 6#3 |
Le troisième vendredi de chaque mois à midi |
0 0 12 1/5 * ? |
Tous les 5 jours de chaque mois à partir du premier jour du mois à midi |
59 59 23 25 12 ? |
La visite du père Noël chaque année |
Parfois, il n'est pas possible de créer certaines conditions dans un seul Trigger notamment si celle-ci est une composition de plusieurs conditions. Dans ce cas, il faut définir plusieurs triggers qui devront tous exécuter le même job.
Attention lors de la définition de triggers qui pourraient être déclenchés durant le passage à l'heure d'été ou d'hiver (daylight savings) car cela pourrait faire rater un déclenchement ou le faire exécuter en double selon le cas.
Avant la version 2.0, CronTrigger était une classe qui possédait de nombreux constructeurs.
Exemple : |
CronTrigger trigger = new CronTrigger("monTrigger",
Scheduler.DEFAULT_GROUP, "0/30 * * * * ?"); |
Il est aussi possible de créer une instance et d'utiliser les différents setters pour configurer l'instance.
Exemple : |
CronTrigger trigger = new CronTrigger();
trigger.setName("monTrigger");
trigger.setCronExpression("0/30 * * * * ?"); |
A partir de la version 2.0, CronTrigger est une interface dont la classe CronTriggerImpl est une implémentation.
Pour créer une instance de type CronTrigger, il faut utiliser un TriggerBuilder pour les propriétés générales du trigger et utiliser un CronSchedulerBuilder pour les propriétés spécifiques au CronTrigger.
La classe CronSchedulerBuilder met en oeuvre le motif de conception monteur et propose donc plusieurs méthodes pour configurer et obtenir l'instance.
Méthode |
Rôle |
static CronScheduleBuilder atHourAndMinuteOnGivenDaysOfWeek(int hour, int minute, Integer... daysOfWeek) |
Obtenir une instance de type CronScheduleBuilder correspondant à une expression pour l'heure, la minute et les jours de la semaine fournis en paramètre |
MutableTrigger build() |
Obtenir l'instance du Trigger. Cette méthode ne devrait être appelée que par un TriggerBuilder |
static CronScheduleBuilder cronSchedule(CronExpression cronExpression) |
Obtenir une instance de type CronScheduleBuilder correspondant à la CronExpression fournie en paramètre |
static CronScheduleBuilder cronSchedule(String cronExpression) |
Obtenir une instance de type CronScheduleBuilder correspondant à l'expression fournie en paramètre qui doit être valide |
static CronScheduleBuilder cronScheduleNonvalidatedExpression(String cronExpression) |
Obtenir une instance de type CronScheduleBuilder correspondant à l'expression fournie en paramètre. Lève une exception de type ParseException si l'expression n'est pas valide |
static CronScheduleBuilder dailyAtHourAndMinute(int hour, int minute) |
Obtenir une instance de type CronScheduleBuilder correspondant à une expression pour l'heure et la minute fournies en paramètre pour tous les jours de la semaine |
CronScheduleBuilder inTimeZone(TimeZone timezone) |
Préciser le fuseau horaire à utiliser |
static CronScheduleBuilder monthlyOnDayAndHourAndMinute(int dayOfMonth, int hour, int minute) |
Obtenir une instance de type CronScheduleBuilder correspondant à une expression pour l'heure, la minute et le jour du mois fournis en paramètre |
static CronScheduleBuilder weeklyOnDayAndHourAndMinute(int dayOfWeek, int hour, int minute) |
Obtenir une instance de type CronScheduleBuilder correspondant à une expression pour l'heure, la minute et le jour de la semaine fournis en paramètre |
CronScheduleBuilder withMisfireHandlingInstructionDoNothing() |
Préciser d'utiliser l'instruction de type: CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING en cas de déclenchement raté |
CronScheduleBuilder withMisfireHandlingInstructionFireAndProceed() |
Préciser d'utiliser l'instruction de type : CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW en cas de déclenchement raté |
CronScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() |
Préciser d'utiliser l'instruction de type: Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY en cas de déclenchement raté |
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import java.util.TimeZone;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetail jobDetail = JobBuilder
.newJob(MonJob.class)
.withIdentity("monJob", "groupe_1")
.usingJobData("monParametre", "12345")
.build();
final Trigger cronTrigger = TriggerBuilder
.newTrigger()
.withIdentity("monTrigger", "groupe_1")
.withSchedule(
CronScheduleBuilder.cronSchedule("0/5 * * * * ?")
.inTimeZone(TimeZone.getTimeZone("Europe/Paris")))
.build();
scheduler.start();
scheduler.scheduleJob(jobDetail, cronTrigger);
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
La classe CronTrigger définit plusieurs constantes pour désigner une misfire instruction :
Depuis la version 2.0 de Quartz, un CalendarIntervalTrigger permet de planifier des déclenchements répétés selon un intervalle de temps dans le calendrier. Ce type de trigger ne peut pas être défini avec SimpleTrigger parce que par exemple tous les mois ne possèdent pas le même nombre de secondes ni avec un CronTrigger par ce que par exemple 5 mois n'est pas divisible de manière entière par 12.
L'interface CalendarIntervalTrigger définit plusieurs méthodes :
Méthode |
Rôle |
int getRepeatInterval() |
Obtenir la valeur de l'interface de répétitions |
DateBuilder.IntervalUnit getRepeatIntervalUnit() |
Obtenir l'unité de l'intervalle |
int getTimesTriggered() |
Obtenir le nombre de fois que le trigger a déjà été déclenché |
TriggerBuilder<CalendarIntervalTrigger> getTriggerBuilder() |
Obtenir un TriggerBuilder configuré pour produire un nouveau trigger identique |
L'unité de l'intervalle est définie par l'énumération DateBuilder.IntervalUnit qui définit les valeurs suivantes : DAY, HOUR, MILLISECOND, MINUTE, MONTH, SECOND, WEEK et YEAR
La classe org.quartz.CalendarIntervalScheduleBuilder met en oeuvre le motif de conception monteur et propose donc plusieurs méthodes pour configurer et obtenir l'instance.
Méthode |
Rôle |
MutableTrigger build() |
Obtenir l'instance du Trigger. Cette méthode ne devrait être appelée que par un TriggerBuilder |
static CalendarIntervalScheduleBuilder calendarIntervalSchedule() |
Obtenir une instance de type CalendarIntervalScheduleBuilder |
CalendarIntervalScheduleBuilder withInterval(int interval, DateBuilder.IntervalUnit unit) |
Définir l'unité de temps et l'intervalle entre chaque déclenchement |
CalendarIntervalScheduleBuilder withIntervalInDays(int intervalInDays) |
Définir l'unité de temps et l'intervalle entre chaque déclenchement avec l'unité de temps en jour (IntervalUnit.DAY) |
CalendarIntervalScheduleBuilder withIntervalInHours(int intervalInHours) |
Définir l'unité de temps et l'intervalle entre chaque déclenchement avec l'unité de temps en heure (IntervalUnit.HOUR) |
CalendarIntervalScheduleBuilder withIntervalInMinutes(int intervalInMinutes) |
Définir l'unité de temps et l'intervalle entre chaque déclenchement avec l'unité de temps en minute (IntervalUnit.MINUTE) |
CalendarIntervalScheduleBuilder withIntervalInMonths(int intervalInMonths) |
Définir l'unité de temps et l'intervalle entre chaque déclenchement avec l'unité de temps en mois (IntervalUnit.MONTH) |
CalendarIntervalScheduleBuilder withIntervalInSeconds(int intervalInSeconds) |
Définir l'unité de temps et l'intervalle entre chaque déclenchement avec l'unité de temps en seconde (IntervalUnit.SECOND) |
CalendarIntervalScheduleBuilder withIntervalInWeeks(int intervalInWeeks) |
Définir l'unité de temps et l'intervalle entre chaque déclenchement avec l'unité de temps en semaine (IntervalUnit.WEEK) |
CalendarIntervalScheduleBuilder withIntervalInYears(int intervalInYears) |
Définir l'unité de temps et l'intervalle entre chaque déclenchement avec l'unité de temps en année (IntervalUnit.YEAR) |
CalendarIntervalScheduleBuilder withMisfireHandlingInstructionDoNothing() |
Utiliser l'instruction CalendarIntervalTrigger.MISFIRE_INSTRUCTION_DO_NOTHING en cas d'échec du déclenchement du trigger |
CalendarIntervalScheduleBuilder withMisfireHandlingInstructionFireAndProceed() |
Utiliser l'instruction CalendarIntervalTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW en cas d'échec du déclenchement du trigger |
CalendarIntervalScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() |
Utiliser l'instruction CalendarIntervalTrigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY en cas d'échec du déclenchement du trigger |
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import org.quartz.CalendarIntervalScheduleBuilder;
import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetail jobDetail = JobBuilder.newJob(MonJob.class)
.withIdentity("monJob").build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("monTrigger", "monGroupe")
.startAt(DateBuilder.tomorrowAt(12, 0, 0))
.withSchedule(CalendarIntervalScheduleBuilder
.calendarIntervalSchedule().withIntervalInDays(2))
.build();
scheduler.start();
scheduler.scheduleJob(jobDetail, trigger);
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
Si l'intervalle de temps utilisé est le mois, il faut faire attention quand la propriété startTime est proche de la fin du mois. Par exemple, si le trigger est configuré avec un intervalle de 1, une unité de temps égale à MONTH et la propriété startTime initialisée avec 31 janvier alors les déclenchements auront lieu le 28 février, le 28 mars et ainsi de suite même si le mois comporte 30 ou 31 jours. Dans ce cas, il est préférable d'utiliser un CronTrigger configuré pour se déclencher le dernier jour du mois.
Depuis la version 2.1 de Quartz, un DailyTimeIntervalTrigger permet de planifier des déclenchements répétés selon un intervalle de temps journalier.
Il permet de planifier un intervalle de répétition exprimé en secondes, minutes ou heures qui peut survenir durant une plage horaire tous les jours ou pour certains jours de la semaine.
L'interface DailyTimeIntervalTrigger définit plusieurs méthodes :
Méthode |
Rôle |
Set<Integer> getDaysOfWeek() |
Définir les jours de la semaine où le déclenchement peut s'appliquer |
TimeOfDay getEndTimeOfDay() |
Définir l'heure de fin de la plage horaire |
int getRepeatInterval() |
Définir la valeur de l'intervalle selon l'unité précisée par la propriété repeatIntervalUnit |
DateBuilder.IntervalUnit getRepeatIntervalUnit() |
Définir l'unité de temps de l'intervalle de répétitions |
TimeOfDay getStartTimeOfDay() |
Définir l'heure de début de la plage horaire |
int getTimesTriggered() |
Obtenir le nombre de fois que le trigger a été déclenché |
TriggerBuilder<DailyTimeIntervalTrigger> getTriggerBuilder() |
Obtenir une instance de type TriggerBuilder qui soit configuré pour créer une instance identique à celle courante |
Par défaut, un DailyTimeIntervalScheduleBuilder utilise des valeurs par défaut pour ses propriétés :
Tous les jours, la propriété startTime est initialisée avec la valeur de startTimeOfDay. A chaque déclenchement la valeur de repeatinterval est ajoutée jusqu'à atteindre la valeur de endTimeOfDay.
La classe org.quartz.DailyTimeIntervalScheduleBuilder met en oeuvre le motif de conception monteur et propose donc plusieurs méthodes pour configurer et obtenir l'instance.
Méthode |
Rôle |
MutableTrigger build() |
Obtenir l'instance du Trigger. Cette méthode ne devrait être appelée que par un TriggerBuilder |
static DailyTimeIntervalScheduleBuilder dailyTimeIntervalSchedule() |
Obtenir une instance de DailyTimeIntervalScheduleBuilder |
DailyTimeIntervalScheduleBuilder endingDailyAfterCount(int count) |
Déterminer l'heure de fin de la plage horaire selon le nombre de répétitions fournis en paramètre, la durée de l'intervalle et l'heure de début de la plage horaire. Ces deux dernières informations doivent être définies avant d'invoquer cette méthode |
DailyTimeIntervalScheduleBuilder endingDailyAt(TimeOfDay timeOfDay) |
Préciser l'heure de fin de la plage horaire de planification |
DailyTimeIntervalScheduleBuilder onDaysOfTheWeek(Integer... onDaysOfWeek) |
Définir les jours de la semaine où les déclenchements peuvent survenir |
DailyTimeIntervalScheduleBuilder onDaysOfTheWeek(Set<Integer> onDaysOfWeek) |
Définir les jours de la semaine où les déclenchements peuvent survenir |
DailyTimeIntervalScheduleBuilder onEveryDay() |
Définir les déclenchements tous les jours de la semaine |
DailyTimeIntervalScheduleBuilder onMondayThroughFriday() |
Définir les déclenchements uniquement du lundi au vendredi inclus |
DailyTimeIntervalScheduleBuilder onSaturdayAndSunday() |
Définir les déclenchements uniquement les samedis et les dimanches |
DailyTimeIntervalScheduleBuilder startingDailyAt(TimeOfDay timeOfDay) |
Préciser l'heure de début de la plage horaire de planification |
DailyTimeIntervalScheduleBuilder withInterval(int timeInterval, DateBuilder.IntervalUnit unit) |
Préciser l'intervalle de temps entre chaque répétition (durée et unité de temps) |
DailyTimeIntervalScheduleBuilder withIntervalInHours(int intervalInHours) |
Préciser un intervalle de temps en heures entre chaque répétition |
DailyTimeIntervalScheduleBuilder withIntervalInMinutes(int intervalInMinutes) |
Préciser un intervalle de temps en minutes entre chaque répétition |
DailyTimeIntervalScheduleBuilder withIntervalInSeconds(int intervalInSeconds) |
Préciser un intervalle de temps en secondes entre chaque répétition |
DailyTimeIntervalScheduleBuilder withMisfireHandlingInstructionDoNothing() |
Utiliser l'instruction DailyTimeIntervalTrigger.MISFIRE_INSTRUCTION_FIRE_DO_NOTHING en cas d'échec du déclenchement du trigger |
DailyTimeIntervalScheduleBuilder withMisfireHandlingInstructionFireAndProceed() |
Utiliser l'instruction DailyTimeIntervalTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW en cas d'échec du déclenchement du trigger |
DailyTimeIntervalScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() |
Utiliser l'instruction DailyTimeIntervalTrigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY en cas d'échec du déclenchement du trigger |
DailyTimeIntervalScheduleBuilder withRepeatCount(int repeatCount) |
Définir le nombre de répétitions de l'intervalle |
L'exemple ci-dessous planifie l'exécution du job du lundi au vendredi à 9h00, 12h00, 15h00 et 18h00.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import org.quartz.DailyTimeIntervalScheduleBuilder;
import org.quartz.DailyTimeIntervalTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.TimeOfDay;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetail jobDetail = JobBuilder
.newJob(MonJob.class)
.withIdentity("monJob")
.storeDurably()
.build();
final DailyTimeIntervalTrigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("monTrigger")
.withSchedule(
DailyTimeIntervalScheduleBuilder
.dailyTimeIntervalSchedule()
.withIntervalInHours(3)
.onDaysOfTheWeek(
DailyTimeIntervalScheduleBuilder.MONDAY_THROUGH_FRIDAY)
.startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 00))
.endingDailyAt(TimeOfDay.hourAndMinuteOfDay(19, 00)))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
La classe DailyTimeIntervalTrigger définit plusieurs constantes pour désigner une misfire instruction :
L'interface org.quartz.Calendar permet de définir des périodes de temps durant lesquelles un trigger ne pourra pas être déclenché. L'utilisation d'un Calendar ne permet que d'exclure des périodes dans le temps.
Par exemple, il est possible de définir un trigger qui s'exécute tous les jours à midi et utiliser un Calendar pour exclure les jours fériés.
Un Calendar Quartz peut être associé à un trigger et est stocké dans le scheduler. Il permet de définir des périodes de temps durant lesquelles un trigger ne pourra pas être déclenché.
L'interface Calendar définit plusieurs méthodes :
Méthode |
Rôle |
Calendar getBaseCalendar() |
Obtenir le calendrier de base |
String getDescription() |
Obtenir la description |
long getNextIncludedTime(long timeStamp) |
Obtenir le prochain moment (en millisecondes) qui est inclus dans le calendrier par rapport au moment fourni en paramètre |
boolean isTimeIncluded(long timeStamp) |
Déterminer si le moment fourni en paramètre (en millisecondes) est inclus dans le calendrier ou pas |
void setBaseCalendar(Calendar baseCalendar) |
Définir le calendrier de base ou retirer le calendrier de base actuel en passant null en paramètre |
void setDescription(String description) |
Définir la description du calendrier. C'est une donnée informative qui n'est pas utilisée par Quartz |
Ceci permet d'exclure par exemple des jours particuliers : jours fériés, fériés bancaires, jours de fermeture dominicale, ...
La granularité utilisée par un Calendar est la milliseconde, ce qui est beaucoup plus fin que la plupart des besoins qui sont généralement de l'ordre de l'heure ou de la journée. Pour faciliter cette gestion, Quartz propose, dans le package org.quartz.impl.calendar, plusieurs implémentations de l'interface Calendar qui héritent de la classe BaseCalendar.
Classe |
Rôle |
AnnualCalendar |
Exclure un ou plusieurs jours dans l'année |
CronCalendar |
Exclure les périodes définies par une expression de type cron |
DailyCalendar |
Exclure une période continue dans une même journée |
HolidayCalendar |
Exclure les jours de congés |
MonthlyCalendar |
Exclure un ou plusieurs jours dans le mois |
WeeklyCalendar |
Exclure un ou plusieurs jours de la semaine. Par exemple : les samedis et les dimanches |
Pour utiliser un Calendar, il faut :
L'enregistrement d'un Calendar dans un Scheduler se fait en utilisant sa méthode addCalendar() qui attend en paramètres : le nom du Calendar, son instance, un booléen qui précise si l'instance doit remplacer celle précédemment enregistrée et un autre booléen qui précise si les triggers utilisant déjà le Calendar doivent être mis à jour.
Le nom du Calendar sera utilisé pour associer le Calendar à un trigger. Il est possible d'associer le même Calendar à plusieurs triggers.
Attention : il est nécessaire de bien vérifier le nom du Calendar fournit en paramètre
Exemple : |
scheduler.addCalendar("jours_feries", cal, true, true); |
Avant la version 2.0 de Quartz, il faut invoquer la méthode setCalendar() de la classe Trigger pour lui associer un Calendar.
Exemple : |
trigger.setCalendarName("jours_feries"); |
A partir de la version 2.0, il faut utiliser la méthode modifiedByCalendar() de la classe TriggerBuilder.
Exemple : |
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("monTrigger", "monGroupe")
.modifiedByCalendar("jours_feries")
.withSchedule(simpleSchedule()
.withIntervalInSeconds(30)
.repeatForever())
.build(); |
La classe org.quartz.impl.calendar.WeeklyCalendar permet de définir des jours de la semaine durant lesquels le déclenchement d'une planification peut avoir lieu. Par défaut, elle exclue le samedi et le dimanche.
La méthode setDayExcluded() permet de préciser pour un jour s'il doit être exclu ou non. Elle attend en paramètre un entier qui précise le jour (le plus simple est d'utiliser une des constantes de la classe java.util.Calendar) et un booléen (true pour exclusion et false pour inclusion).
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import org.quartz.CalendarIntervalScheduleBuilder;
import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.calendar.WeeklyCalendar;
import org.quartz.spi.OperableTrigger;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final WeeklyCalendar cal = new WeeklyCalendar();
cal.setDayExcluded(java.util.Calendar.MONDAY, true);
cal.setDayExcluded(java.util.Calendar.SUNDAY, true);
cal.setDayExcluded(java.util.Calendar.SATURDAY, false);
scheduler.addCalendar("jours_d_ouverture", cal, true, true);
final JobDetail jobDetail = JobBuilder.newJob(MonJob.class)
.withIdentity("monJob").storeDurably().build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("monTrigger", "monGroupe")
.modifiedByCalendar("jours_d_ouverture")
.startAt(DateBuilder.todayAt(14, 0, 0))
.withSchedule(CalendarIntervalScheduleBuilder
.calendarIntervalSchedule().withIntervalInDays(2))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
La classe org.quartz.impl.calendar.AnnualCalendar permet de définir des jours dans une année pour lesquels la planification d'un trigger ne se fera pas. Ce type de Calendar ne tient pas compte de l'année : les jours exclus le sont pour toutes les années. Il est donc pratique pour définir les jours fériés fixes d'une année à l'autre.
La méthode setDayExcluded() permet de définir un ou plusieurs jours à exclure. Elle possède deux surcharges :
La méthode removeExcludedDay() permet d'enlever le jour fourni en paramètre de la liste des jours exclus.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.quartz.CalendarIntervalScheduleBuilder;
import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.calendar.AnnualCalendar;
import org.quartz.spi.OperableTrigger;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final ArrayList<Calendar> joursFeriesFixes =
new ArrayList<Calendar>(7);
final AnnualCalendar cal = new AnnualCalendar();
Calendar jourFerie = Calendar.getInstance();
jourFerie.set(Calendar.MONTH, Calendar.JANUARY);
jourFerie.set(Calendar.DATE, 1);
joursFeriesFixes.add(jourFerie);
jourFerie = Calendar.getInstance();
jourFerie.set(Calendar.MONTH, Calendar.MAY);
jourFerie.set(Calendar.DATE, 1);
joursFeriesFixes.add(jourFerie);
jourFerie = Calendar.getInstance();
jourFerie.set(Calendar.MONTH, Calendar.MAY);
jourFerie.set(Calendar.DATE, 8);
joursFeriesFixes.add(jourFerie);
jourFerie = Calendar.getInstance();
jourFerie.set(Calendar.MONTH, Calendar.JULY);
jourFerie.set(Calendar.DATE, 14);
joursFeriesFixes.add(jourFerie);
jourFerie = Calendar.getInstance();
jourFerie.set(Calendar.MONTH, Calendar.AUGUST);
jourFerie.set(Calendar.DATE, 15);
joursFeriesFixes.add(jourFerie);
jourFerie = Calendar.getInstance();
jourFerie.set(Calendar.MONTH, Calendar.NOVEMBER);
jourFerie.set(Calendar.DATE, 1);
joursFeriesFixes.add(jourFerie);
jourFerie = Calendar.getInstance();
jourFerie.set(Calendar.MONTH, Calendar.NOVEMBER);
jourFerie.set(Calendar.DATE, 11);
joursFeriesFixes.add(jourFerie);
jourFerie = Calendar.getInstance();
jourFerie.set(Calendar.MONTH, Calendar.DECEMBER);
jourFerie.set(Calendar.DATE, 25);
joursFeriesFixes.add(jourFerie);
cal.setDaysExcluded(joursFeriesFixes);
scheduler.addCalendar("Jours feries fixes", cal, true, true);
final JobDetail jobDetail = JobBuilder.newJob(MonJob.class)
.withIdentity("monJob").storeDurably().build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("monTrigger", "monGroupe")
.modifiedByCalendar("Jours feries fixes")
.startAt(DateBuilder.todayAt(14, 0, 0))
.withSchedule(CalendarIntervalScheduleBuilder
.calendarIntervalSchedule().withIntervalInDays(2))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
La classe org.quartz.impl.calendar.HolidayCalendar permet de définir des jours à exclure en tenant compte de l'année.
La méthode addExcludedDate() permet d'exclure un jour : elle attend en paramètre une instance de type Date.
La méthode removeExcludedDate() permet de retirer un jour de la liste de ceux à exclure.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.quartz.CalendarIntervalScheduleBuilder;
import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.calendar.HolidayCalendar;
import org.quartz.spi.OperableTrigger;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final HolidayCalendar cal = new HolidayCalendar();
final Calendar gCal = Calendar.getInstance();
gCal.set(2014, Calendar.JANUARY, 1);
cal.addExcludedDate(gCal.getTime());
gCal.set(2014, Calendar.APRIL, 21);
cal.addExcludedDate(gCal.getTime());
gCal.set(2014, Calendar.MAY, 1);
cal.addExcludedDate(gCal.getTime());
gCal.set(2014, Calendar.MAY, 8);
cal.addExcludedDate(gCal.getTime());
gCal.set(2014, Calendar.MAY, 29);
cal.addExcludedDate(gCal.getTime());
gCal.set(2014, Calendar.JUNE, 9);
cal.addExcludedDate(gCal.getTime());
gCal.set(2014, Calendar.JULY, 14);
cal.addExcludedDate(gCal.getTime());
gCal.set(2014, Calendar.AUGUST, 15);
cal.addExcludedDate(gCal.getTime());
gCal.set(2014, Calendar.NOVEMBER, 1);
cal.addExcludedDate(gCal.getTime());
gCal.set(2014, Calendar.NOVEMBER, 11);
cal.addExcludedDate(gCal.getTime());
gCal.set(2014, Calendar.DECEMBER, 25);
cal.addExcludedDate(gCal.getTime());
scheduler.addCalendar("Jour feries 2014", cal, true, true);
final JobDetail jobDetail = JobBuilder.newJob(MonJob.class)
.withIdentity("monJob").storeDurably().build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("monTrigger", "monGroupe")
.modifiedByCalendar("Jour feries 2014")
.startAt(DateBuilder.todayAt(14, 0, 0))
.withSchedule(CalendarIntervalScheduleBuilder
.calendarIntervalSchedule().withIntervalInDays(2))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
Il est possible de définir sa propre implémentation de type org.quartz.Calendar qui doit être sérialisable.
La classe TriggerUtils propose des utilitaires qui peuvent être pratiques.
Avant la version 2.0, elle propose :
A partir de la version 2.0, elle ne propose plus que trois méthodes qui permettent de faire des calculs relatifs au nombre de déclenchements d'un trigger.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import org.quartz.CalendarIntervalScheduleBuilder;
import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.spi.OperableTrigger;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetail jobDetail = JobBuilder.newJob(MonJob.class)
.withIdentity("monJob").storeDurably().build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("monTrigger", "monGroupe")
.startAt(DateBuilder.tomorrowAt(12, 0, 0))
.withSchedule(CalendarIntervalScheduleBuilder
.calendarIntervalSchedule().withIntervalInDays(2))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
final List<Date> dates = TriggerUtils
.computeFireTimes((OperableTrigger) trigger, null, 20);
for (final Date date : dates) {
System.out.println(date);
}
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
L'interface Scheduler propose de nombreuses méthodes qui permettent de gérer les triggers et les jobs.
Méthodes |
Rôle |
void addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers) |
Enregistrer le Calendar avec le nom fourni en paramètres |
void addJob(JobDetail jobDetail, boolean replace) |
Enregistrer le job dans le scheduler sans l'associer avec un trigger |
boolean checkExists(JobKey jobKey) |
Vérifier si un job dont l'identifiant est fourni en paramètre est déjà enregistré dans le scheduler |
boolean checkExists(TriggerKey triggerKey) |
Vérifier si un trigger dont l'identifiant est fourni en paramètre est déjà enregistré dans le scheduler |
void clear() |
Supprimer tous les éléments enregistrés dans le scheduler (Triggers, Jobs et Calendars) |
boolean deleteCalendar(String calName) |
Supprimer le Calendar dont le nom est fourni en paramètre |
boolean deleteJob(JobKey jobKey) |
Supprimer le job dont l'identifiant est fourni en paramètre. Les triggers associés sont aussi retirés |
boolean deleteJobs(List<JobKey> jobKeys) |
Supprimer les jobs dont les identifiants sont fournis en paramètre. Les triggers associés sont aussi retirés |
Calendar getCalendar(String calName) |
Obtenir l'instance de Calendar enregistré avec le nom fourni en paramètre |
List<String> getCalendarNames() |
Obtenir la liste des noms de Calendar enregistrés |
SchedulerContext getContext() |
Obtenir l'instance de type SchedulerContext |
List<JobExecutionContext> getCurrentlyExecutingJobs() |
Obtenir la liste des JobExecutionContext des jobs en cours d'exécution dans le scheduler |
JobDetail getJobDetail(JobKey jobKey) |
Obtenir le JobDetail du job dont l'identifiant est fourni en paramètre |
List<String> getJobGroupNames() |
Obtenir la liste de tous les groupes associés à au moins un job |
Set<JobKey> getJobKeys(GroupMatcher<JobKey> matcher) |
Obtenir les identifiants des jobs qui sont associés à des groupes répondant au GroupMatcher fourni en paramètre |
ListenerManager getListenerManager() |
Obtenir l'instance de type ListenerManager permettant de gérer les listeners |
SchedulerMetaData getMetaData() |
Obtenir l'instance de type SchedulerMetaData |
Set<String> getPausedTriggerGroups() |
Obtenir la liste des noms de groupes de triggers qui sont en pause |
String getSchedulerInstanceId() |
Renvoyer l'identifiant de l'instance |
String getSchedulerName() |
Renvoyer le nom du scheduler |
Trigger getTrigger(TriggerKey triggerKey) |
Obtenir le trigger dont l'identifiant est fourni en paramètre |
List<String> getTriggerGroupNames() |
Obtenir la liste de tous les groupes associés à au moins un trigger |
Set<TriggerKey> getTriggerKeys(GroupMatcher<TriggerKey> matcher) |
Obtenir les identifiants des triggers qui sont associés à des groupes répondant au GroupMatcher fourni en paramètre |
List<? extends Trigger> getTriggersOfJob(JobKey jobKey) |
Obtenir une liste des triggers qui sont associés au job dont l'identifiant est passé en paramètre |
Trigger.TriggerState getTriggerState(TriggerKey triggerKey) |
Obtenir l'état d'un trigger dont l'identifiant est fourni en paramètre |
boolean interrupt(JobKey jobKey) |
Demander l'interruption de l'exécution dans l'instance du scheduler du ou des jobs dont l'identifiant est fourni en paramètre. Les jobs concernés doivent implémenter l'interface InterruptableJob interface |
boolean isInStandbyMode() |
Déterminer si le scheduler est en mode standby |
boolean isShutdown() |
Déterminer si le scheduler a été arrêté |
boolean isStarted() |
Déterminer si le scheduler est démarré |
void pauseAll() |
Mettre en pause tous les triggers |
void pauseJob(JobKey jobKey) |
Mettre en pause le job dont l'identifiant est fourni en paramètre |
void pauseJobs(GroupMatcher<JobKey> matcher) |
Mettre en pause les jobs dont l'identifiant répond au GroupMatcher fourni en paramètre |
void pauseTrigger(TriggerKey triggerKey) |
Mettre en pause le trigger dont l'identifiant est fourni en paramètre |
void pauseTriggers(GroupMatcher<TriggerKey> matcher) |
Mettre en pause les triggers dont l'identifiant répond au GroupMatcher fourni en paramètre |
Date rescheduleJob(TriggerKey triggerKey, Trigger newTrigger) |
Supprimer le trigger dont l'identifiant est fourni en paramètre et enregistrer celui fourni en paramètre à sa place. Ils n'ont pas l'obligation d'avoir le même nom mais ils doivent être associé au même identifiant de job |
void resumeAll() |
Réactiver tous les triggers |
void resumeJob(JobKey jobKey) |
Réactiver le job dont l'identifiant est fourni en paramètre |
void resumeJobs(GroupMatcher<JobKey> matcher) |
Réactiver les jobs dont l'identifiant répond au GroupMatcher fourni en paramètre |
void resumeTrigger(TriggerKey triggerKey) |
Réactiver le trigger dont l'identifiant est fourni en paramètre |
void resumeTriggers(GroupMatcher<TriggerKey> matcher) |
Mettre en pause les triggers dont l'identifiant répond au GroupMatcher fourni en paramètre |
Date scheduleJob(JobDetail jobDetail, Trigger trigger) |
Enregistrer la planification du job avec le trigger fournis en paramètres |
Date scheduleJob(Trigger trigger) |
Enregistrer la planification du job associé au trigger fourni en paramètre |
void scheduleJobs(Map<JobDetail,List<Trigger>> triggersAndJobs, boolean replace) |
Planifier les jobs associés aux triggers fournis en paramètres |
void setJobFactory(JobFactory factory) |
Définir l'instance de la fabrique pour créer des instances de type Job |
void shutdown() |
Arrêter le scheduler |
void shutdown(boolean waitForJobsToComplete) |
Arrêter le scheduler avec une attente de la fin de l'exécution des jobs en cours |
void standby() |
Mettre le scheduler en mode standby : le déclenchement des triggers est suspendu |
void start() |
Démarrer le scheduler |
void startDelayed(int seconds) |
Démarrer le scheduler après avoir attendu le nombre de secondes fourni en paramètre |
void triggerJob(JobKey jobKey) |
Exécuter immédiatement le Job dont l'identifiant est fourni en paramètre |
void triggerJob(JobKey jobKey, JobDataMap data) |
Exécuter immédiatement le Job dont l'identifiant est fourni en paramètre avec les métadonnées fournies |
boolean unscheduleJob(TriggerKey triggerKey) |
Retirer le trigger dont l'identifiant est fourni en paramètre |
boolean unscheduleJobs(List<TriggerKey> triggerKeys) |
Retirer la liste de triggers fournie en paramètre |
La planification d'un job
Il est nécessaire d'enregistrer la planification d'un job dans le scheduler pour permettre son déclenchement. Il faut utiliser une des surcharges de la méthode scheduleJob() ou la méthode scheduleJobs() du scheduler.
Exemple : |
scheduler.scheduleJob(jobDetail, trigger); |
L'enregistrement d'un job pour une utilisation ultérieure
Il faut invoquer la méthode addJob() du scheduler en lui passant en paramètre l'instance de type JobDetail et le booléen true.
Exemple : |
final JobDetail jobDetail = JobBuilder
.newJob(MonJob.class).withIdentity("monJob").storeDurably().build();
scheduler.addJob(jobDetail, true); |
La modification d'un trigger ou d'un job enregistré
Il faut invoquer la méthode rescheduleJob() du scheduler en lui passant en paramètre l'identifiant du trigger à remplacer et l'instance du Trigger. Le nouveau trigger sera associé au même job même si un autre job lui est déjà associé.
Exemple : |
final JobDetail jobDetail = JobBuilder
.newJob(MonJob.class)
.withIdentity("monJob")
.storeDurably()
.build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(new TriggerKey("monTrigger", "monGroup"))
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInHours(1)
.repeatForever())
.startAt(DateBuilder.evenHourDate(new Date()))
.build();
scheduler.scheduleJob(jobDetail, trigger);
final Trigger triggerNouveau = TriggerBuilder
.newTrigger()
.withIdentity(new TriggerKey("monTriggerNouveau", "monGroup"))
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInHours(2)
.repeatForever())
.startAt(DateBuilder.evenMinuteDate(new Date()))
.build();
scheduler.rescheduleJob(trigger.getKey(), triggerNouveau); |
La suppression d'un job ou de sa planification
Pour retirer la planification d'un job, il faut invoquer la méthode unscheduleJob() en lui passant en paramètre l'identifiant du trigger concerné. Pour retirer plusieurs jobs, il faut invoquer la méthode unscheduleJobs().
Exemple : |
scheduler.unscheduleJob(trigger.getKey()); |
Pour retirer un job de la ou les planifications qui lui sont associées, il faut invoquer la méthode deleteJob() en lui passant en paramètre l'identifiant du job concerné.
Exemple : |
scheduler.deleteJob(jobDetail.getKey()); |
La mise en pause d'un job ou d'un trigger
La méthode pauseJob() permet de suspendre la planification de l'exécution d'un job.
Exemple : |
scheduler.pauseJob(jobDetail.getKey()); |
La méthode resumeJob() permet de rependre la planification de l'exécution d'un job.
Exemple : |
scheduler.resumeJob(jobDetail.getKey()); |
L'obtention de la liste des jobs et des triggers
La méthode getJobGroupNames() permet d'obtenir la liste des groupes. Avant Quartz 2.0, la méthode getJobNames() qui attend en paramètre le nom d'un groupe permet d'obtenir les jobs qui appartiennent à ce groupe.
Exemple : |
for (String groupName : scheduler.getJobGroupNames()) {
for (String jobName : scheduler.getJobNames(groupName)) {
System.out.println("jobName : " + jobName + ", group : " + groupName);
}
} |
A partir de Quartz 2.0, la méthode getJobKeys() qui attend en paramètre une instance de type GroupMatcher renvoie les jobs dont le nom du groupe répond au matcher.
Exemple : |
for(String group: scheduler.getJobGroupNames()) {
for(JobKey jobKey : scheduler.getJobKeys(GroupMatcher.groupEquals(group))) {
System.out.println("job : " + jobKey);
}
} |
Pour obtenir la liste des prochains déclenchements de chaque job, il faut :
Avant Quartz 2.0 :
Exemple : |
for (String nomGroupe : scheduler.getJobGroupNames()) {
for (String nomJob : scheduler.getJobNames(nomGroupe)) {
Trigger[] triggers = scheduler.getTriggersOfJob(nomJob, nomGroupe);
Date nextFireTime = null;
if (triggers.length > 0) {
nextFireTime = triggers[0].getNextFireTime();
}
System.out.println("Job : " + nomJob + " groupe : " + nomGroupe
+ ", prochain declenchement " + nextFireTime);
}
} |
A partir de Quartz 2.0 :
Exemple : |
for (final String nomGroupe : scheduler.getJobGroupNames()) {
for (final JobKey jobKey : scheduler.getJobKeys(GroupMatcher
.jobGroupEquals(nomGroupe))) {
Date nextFireTime = null;
final List<Trigger> triggers = (List<Trigger>) scheduler
.getTriggersOfJob(jobKey);
if (!triggers.isEmpty()) {
nextFireTime = triggers.get(0).getNextFireTime();
}
System.out.println("Job : " + jobKey.getName() + " groupe : " + jobKey.getGroup()
+ ", prochain declenchement " + nextFireTime);
}
} |
A partir de Quartz 2.0, il faut utiliser la méthode getTriggerKeys() qui attend en paramètre une instance de type GroupMatcher et renvoie les triggers dont le nom du groupe répond au matcher.
Exemple : |
for (final String group : scheduler.getTriggerGroupNames()) {
for (final TriggerKey triggerKey : scheduler
.getTriggerKeys(GroupMatcher.triggerGroupEquals(group))) {
System.out.println("Trigger : " + triggerKey.getName() + ", groupe "
+ triggerKey.getGroup());
}
} |
L'obtention des jobs associés à un trigger
La méthode getTriggerOfJob() permet d'obtenir la liste des triggers qui sont associés au job dont l'identifiant est fourni en paramètre.
Exemple : |
System.out.println("Trigger for job " + jobDetail.getKey());
final List<? extends Trigger> jobTriggers = scheduler
.getTriggersOfJob(jobDetail.getKey());
for (final Trigger jobTrigger : jobTriggers) {
System.out.println("Trigger : " + jobTrigger.getKey());
} |
L'arrêt d'un job en cours d'exécution
Pour permettre l'arrêt d'un job, sa classe doit implémenter l'interface InterruptableJob. Elle hérite de l'interface Job et ne définit qu'une seule méthode : void interrupt().
Cette méthode sera invoquée par le scheduler pour tenter d'arrêter l'exécution du job. En aucun cas elle ne permet d'arrêter directement l'exécution mais elle permet de demander son arrêt. Généralement son implémentation consiste à changer la valeur d'un boolean qui doit être périodiquement testé dans les traitements du job pour les arrêter. La logique d'interruption doit donc être codée dans les traitements du job eux-mêmes.
Pour interrompre un job en cours d'exécution, il faut :
Quartz propose la possibilité de réagir à des événements en utilisant des Listeners.
Quartz propose trois types de Listeners :
L'implémentation d'un listener peut se faire de deux manières :
Un listener doit avoir un nom retourné par la méthode getName()
Pour utiliser un listener, il faut :
Avant la version 2.0 de Quartz, l'enregistrement d'un listener dans le scheduler se fait en invoquant une de ses méthodes addXXXListener(). A partir de la version 2.0, l'enregistrement d'un listener dans le scheduler se fait en utilisant le ListenerManager qui lui est associé.
L'interface ListenerManager définit des méthodes pour gérer les listeners associés à un scheduler :
Méthode |
Rôle |
void addJobListener(JobListener jobListener, List<Matcher<JobKey>> matchers) |
Ajouter un JobListener au Scheduler et l'enregistrer pour être invoqué sur les événements des Job dont la clé correspond à au moins un des Matcher fournis |
boolean addJobListenerMatcher(String listenerName, Matcher<JobKey> matcher) |
Ajouter un Matcher au listener dont le nom est fourni en paramètre |
void addSchedulerListener(SchedulerListener schedulerListener) |
Ajouter un SchedulerListener |
void addTriggerListener(TriggerListener triggerListener, List<Matcher<TriggerKey>> matchers) |
Ajouter un TriggerListener et l'enregistrer dans le scheduler pour être invoqué sur les événements des triggers dont la clé correspond à au moins un des Matcher fournis |
boolean addTriggerListenerMatcher(String listenerName, Matcher<TriggerKey> matcher) |
Ajouter un Matcher au listener dont le nom est fourni en paramètre |
JobListener getJobListener(String name) |
Obtenir un JobListener à partir de son nom |
List<Matcher<JobKey>> getJobListenerMatchers(String listenerName) |
Obtenir les Matcher associés au listener dont le nom est fourni en paramètre |
List<JobListener> getJobListeners() |
Obtenir tous les JobListener enregistrés |
List<SchedulerListener> getSchedulerListeners() |
Obtenir tous les SchedulerListener |
TriggerListener getTriggerListener(String name) |
Obtenir un TriggerListener à partir de son nom |
List<Matcher<TriggerKey>> getTriggerListenerMatchers(String listenerName) |
Obtenir les Matcher associés au listener dont le nom est fourni en paramètre |
List<TriggerListener> getTriggerListeners() |
Obtenir tous les TriggerListener enregistrés |
boolean removeJobListener(String name) |
Retirer le JobListener dont le nom est fourni en paramètre |
boolean removeJobListenerMatcher(String listenerName, Matcher<JobKey> matcher) |
Retirer le Matcher associé au listener dont le nom est fourni en paramètre |
boolean removeSchedulerListener(SchedulerListener schedulerListener) |
Retirer le schedulerListener fourni en paramètre du scheduler |
boolean removeTriggerListener(String name) |
Retirer le TriggerListener dont le nom est fourni en paramètre |
boolean removeTriggerListenerMatcher(String listenerName, Matcher<TriggerKey> matcher) |
Retirer le Matcher associé au listener dont le nom est fourni en paramètre |
boolean setJobListenerMatchers(String listenerName, List<Matcher<JobKey>> matchers) |
Définir les Matchers associés au listener dont le nom est fourni en paramètre |
boolean setTriggerListenerMatchers(String listenerName, List<Matcher<TriggerKey>> matchers) |
Définir les Matchers associés au listener dont le nom est fourni en paramètre |
Lors de l'enregistrement d'un listener, il est possible de filtrer les éléments (Job ou Trigger) qui seront concernés par les événements en utilisant un objet de type org.quartz.Matcher.
Quartz fournit en standard plusieurs implémentations de type Matcher pour définir le filtre : AndMatcher, EverythingMatcher, GroupMatcher, KeyMatcher, NameMatcher, NotMatcher, OrMatcher et StringMatcher.
Les listeners ne sont pas enregistrés dans le JobStore : il est donc nécessaire d'enregistrer les listeners à chaque démarrage du scheduler. Il est cependant possible de configurer des listeners globaux qui seront instanciés et enregistrés par la fabrique au démarrage du scheduler.
Un JobListener permet de réagir à des événements liés à l'exécution d'un job.
L'interface JobListener définit plusieurs méthodes :
Méthode |
Rôle |
String getName() |
Obtenir le nom du listener |
void jobExecutionVetoed(JobExecutionContext context) |
Invoquée par le scheduler lorsqu'un JobDetail est sur le point d'être exécuté (suite au déclenchement d'un trigger lié au JobDetail) mais qu'un TriggerListener a mis un véto sur son exécution |
void jobToBeExecuted(JobExecutionContext context) |
Invoquée par le scheduler lorsqu'un JobDetail est sur le point d'être exécuté (suite au déclenchement d'un trigger lié au JobDetail) |
void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) |
Invoquée par le scheduler lorsqu'un JobDetail a été exécuté |
Exemple : |
package fr.jmdoudoux.dej.quartz;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.listeners.JobListenerSupport;
public class MonJobListener extends JobListenerSupport {
@Override
public String getName() {
return "MonJobListener";
}
@Override
public void jobToBeExecuted(final JobExecutionContext context) {
final String jobName = context.getJobDetail().getKey().toString();
System.out.println("Le job : " + jobName + " va demarrer");
}
@Override
public void jobWasExecuted(final JobExecutionContext context,
final JobExecutionException jobException) {
final String jobName = context.getJobDetail().getKey().toString();
System.out.println("Le job : " + jobName + " est termine");
if (jobException != null) {
System.out.println("Exception levee par le job " + jobName + " : "
+ jobException.getMessage());
}
}
} |
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetail jobDetail = JobBuilder
.newJob(MonJob.class)
.withIdentity("monJob")
.storeDurably()
.build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(new TriggerKey("monTrigger", "monGroup"))
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInHours(1)
.repeatForever())
.startNow()
.build();
scheduler.getListenerManager().addJobListener(new MonJobListener(),
KeyMatcher.keyEquals(jobDetail.getKey()));
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
Un TriggerListener permet de réagir à des événements lies à l'exécution d'un trigger.
L'interface TriggerListener définit plusieurs méthodes :
Méthode |
Rôle |
String getName() |
Obtenir le nom du listener |
void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) |
Invoquée par le scheduler lorsque les traitements du déclenchement d'un trigger sont terminés (le job associé a été exécuté) |
void triggerFired(Trigger trigger, JobExecutionContext context) |
Invoquée par le scheduler lorsqu'un trigger est déclenché et que son Job associé est prêt à être exécuté |
void triggerMisfired(Trigger trigger) |
Invoquée par le scheduler lorsque le déclenchement d'un trigger est raté |
boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) |
Invoquée par le scheduler lorsqu'un trigger est déclenché et que son Job associé est prêt à être exécuté |
La méthode vetoJobExecution() permet de mettre un véto sur l'exécution du job lié au trigger simplement en renvoyant true. Elle est invoquée par le scheduler après la méthode triggerFired().
Exemple : |
package fr.jmdoudoux.dej.quartz;
import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.listeners.TriggerListenerSupport;
public class MonTriggerListener extends TriggerListenerSupport {
private final String name;
public MonTriggerListener(final String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
@Override
public void triggerFired(final Trigger trigger,
final JobExecutionContext context) {
final String triggerName = context.getTrigger().getKey().toString();
System.out.println("Le trigger : " + triggerName + " est declenche");
}
} |
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetail jobDetail = JobBuilder
.newJob(MonJob.class)
.withIdentity("monJob")
.storeDurably()
.build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(new TriggerKey("monTrigger", "monGroup"))
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInHours(1)
.repeatForever())
.startNow()
.build();
scheduler.getListenerManager().addTriggerListener(
new MonTriggerListener("monTriggerListener"),
KeyMatcher.keyEquals(trigger.getKey()));
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
Un SchedulerListener permet de réagir à des événements liés au scheduler lui-même notamment ceux concernant son cycle de vie : l'ajout/suppression de jobs/triggers, la mise en pause ou la reprise de jobs/triggers et la levée d'erreurs.
L'interface SchedulerListener définit plusieurs méthodes :
Méthode |
Rôle |
void jobAdded(JobDetail jobDetail) |
Invoquée par le scheduler lorsqu'un JobDetail est ajouté |
void jobDeleted(JobKey jobKey) |
Invoquée par le scheduler lorsqu'un job est supprimé |
void jobPaused(JobKey jobKey) |
Invoquée par le scheduler lorsqu'un job est mis en pause |
void jobResumed(JobKey jobKey) |
Invoquée par le scheduler lorsqu'un job est redémarré |
void jobScheduled(Trigger trigger) |
Invoquée par le scheduler lorsqu'un job est planifié |
void jobsPaused(String jobGroup) |
Invoquée par le scheduler lorsqu'un groupe de jobs est mis en pause |
void jobsResumed(String jobGroup) |
Invoquée par le scheduler lorsqu'un group de jobs est redémarré |
void jobUnscheduled(TriggerKey triggerKey) |
Invoquée par le scheduler lorsque la planification d'un job est supprimée |
void schedulerError(String msg, SchedulerException cause) |
Invoquée par le scheduler lorsqu'une erreur grave survient dans ses propres traitements (échec de mises à jour du JobStore, impossible d'instancier un job lors du déclenchement d'un trigger, ...) |
void schedulerInStandbyMode() |
Invoquée par le scheduler lorsque son état passe à standby |
void schedulerShutdown() |
Invoquée par le scheduler lorsqu'il s'arrête |
void schedulerShuttingdown() |
Invoquée par le scheduler lorsqu'il débute ses traitements pour son arrêt |
void schedulerStarted() |
Invoquée par le scheduler lorsqu'il démarre |
void schedulerStarting() |
Invoquée par le scheduler lorsqu'il débute ses traitements pour son démarrage |
void schedulingDataCleared() |
Invoquée par le scheduler lorsque tous les jobs, triggers et calendars ont été supprimés |
void triggerFinalized(Trigger trigger) |
Invoquée par le scheduler lorsque tous les déclenchements du trigger ont été réalisés |
void triggerPaused(TriggerKey triggerKey) |
Invoquée par le scheduler lorsqu'un trigger est mis en pause |
void triggerResumed(TriggerKey triggerKey) |
Invoquée par le scheduler lorsqu'un trigger est redémarré |
void triggersPaused(String triggerGroup) |
Invoquée par le scheduler lorsqu'un groupe de triggers est mis en pause |
void triggersResumed(String triggerGroup) |
Invoquée par le scheduler lorsqu'un groupe de triggers est redémarré |
Exemple : |
package fr.jmdoudoux.dej.quartz;
import org.quartz.Trigger;
import org.quartz.listeners.SchedulerListenerSupport;
public class MonSchedulerListener extends SchedulerListenerSupport {
@Override
public void schedulerStarted() {
System.out.println("Demarrage scheduler");
}
@Override
public void schedulerShutdown() {
System.out.println("Arret scheduler");
}
@Override
public void jobScheduled(final Trigger trigger) {
System.out.println("Planification du job " + trigger.getJobKey()
+ " avec le trigger " + trigger.getKey());
}
} |
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.io.IOException;
import java.util.Date;
import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
public class TestQuartz {
public static void main(final String[] args) {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetail jobDetail = JobBuilder
.newJob(MonJob.class)
.withIdentity("monJob")
.storeDurably()
.build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(new TriggerKey("monTrigger", "monGroup"))
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInHours(1)
.repeatForever())
.startAt(DateBuilder.evenMinuteDate(new Date()))
.build();
scheduler.getListenerManager().addSchedulerListener(
new MonSchedulerListener());
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
} |
Un JobStore a la responsabilité de stocker les éléments utilisés par le scheduler (Jobs, Triggers, Calendars, ...).
Le JobStore utilisé par le Scheduler est défini et configuré dans les propriétés fournies à la fabrique de type SchedulerFactory.
Quartz fournit plusieurs JobStore en standard :
C'est le plus simple et le plus performant des JobStore mais il n'est pas persistant. Toutes les informations sont donc perdues lors de l'arrêt normal ou non de la JVM dans laquelle le scheduler s'exécute. C'est le JobStore utilisé par défaut.
Pour configurer Quartz à utiliser le RAMJobStore, il faut le préciser dans le fichier de configuration :
Exemple : |
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore |
Un JDBCJobStore stocke les informations dans une base de données relationnelles. Il est nécessaire de créer les différentes tables requises en utilisant les scripts SQL contenus dans le sous-répertoire docs/dbTables de la distribution Quartz.
Le préfixe du nom des tables est configurable : cela peut être pratique pour stocker la configuration de plusieurs schedulers dans la même base de données.
Deux classes d'implémentation sont proposées par Quartz selon le type de transactions à utiliser :
Le paramétrage du JDBCJobStore se fait dans le fichier de configuration de Quartz notamment la définition de la Datasource qui peut être créée par Quartz ou obtenue d'un serveur d'applications par JNDI.
Quartz propose un ensemble de fonctionnalités configurables dans un fichier de type properties :
Exemple : |
org.quartz.scheduler.instanceName = MonScheduler
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore |
La configuration de Quartz se définit dans un fichier properties qui sera utilisé par la classe StdSchedulerFactory pour créer une instance de type Scheduler selon la configuration.
Par défaut, elle recherche un fichier nommé quart.properties dans le répertoire courant. Si le fichier n'est pas trouvé, alors c'est le fichier quartz.properties contenu dans le package org.quartz qui est chargé.
Pour préciser un autre fichier de configuration à utiliser, il est possible de :
La configuration de certaines instances se fait en les identifiant grâce à un nom unique contenu dans leur clé.
Une propriété peut faire référence à la valeur d'une autre propriété en préfixant son nom avec $@. Par exemple, "$@org.quartz.scheduler.instanceName" sera remplacé par le nom de l'instance du Scheduler.
Plusieurs propriétés concernent la configuration globale du scheduler, notamment :
Nom de la propriété |
Type |
Description |
org.quartz.scheduler.instanceName |
String |
Définir le nom logique du scheduler. Dans un cluster, tous les schedulers doivent avoir le même nom logique. Valeur par défaut : «QuartzScheduler» |
org.quartz.scheduler.instanceId |
String |
Identifiant de l'instance du scheduler. Doit être unique pour chaque scheduler d'un même cluster. La valeur doit être AUTO pour laisser Quartz donner un identifiant ou SYS_PROP pour que Quartz récupère la valeur de la propriété org.quartz.scheduler.instanceId de la JVM Valeur par défaut : «NON_CLUSTERED» |
org.quartz.scheduler.instanceIdGenerator.class |
String |
N'est utilisé que si la propriété org.quartz.scheduler.instanceId vaut AUTO. Plusieurs classes sont proposées en standard par Quartz :
Valeur par défaut : org.quartz.simpl.SimpleInstanceIdGenerator |
org.quartz.scheduler.threadName |
String |
Définir le nom des threads Valeur par défaut : instanceName + '_QuartzSchedulerThread' |
org.quartz.scheduler.makeSchedulerThreadDaemon |
boolean |
Préciser si le thread principal du scheduler doit être de type démon (true) ou non (false) Valeur par défaut : false |
org.quartz.scheduler.dbFailureRetryInterval |
long |
Définir le nombre de millisecondes que le scheduler va attendre pour se reconnecter au système sous-jacent du JobStore (par exemple une base de données) après une perte de connexion. Valeur par défaut : 15000 |
org.quartz.scheduler.jobFactory.class |
String |
Nom pleinement qualifié de la classe de type JobFactory utilisée par Quartz pour créer des instances de type Job. Quartz fournit plusieurs classes d'implémentation possibles :
Valeur par défaut : org.quartz.simpl.PropertySettingJobFactory |
org.quartz.context.key.SOME_KEY |
String |
Définir une paire clé/valeur qui sera ajoutée dans le contexte du scheduler. Il faut remplacer SOME_KEY par le nom de la clé Exemple : |
org.quartz.scheduler.userTransactionURL |
String |
Préciser l'URL JNDI du gestionnaire de transactions distribuées du serveur d'applications. Valeur par défaut : 'java:comp/UserTransaction' |
Org.quartz.scheduler.wrapJobExecutionInUserTransaction |
boolean |
Préciser si Quartz doit démarrer une nouvelle UserTransaction avant d'exécuter un job. La transaction est validée lorsque le job est terminé. Valeur par défaut : false |
org.quartz.scheduler.skipUpdateCheck |
boolean |
Préciser si Quartz doit vérifier sur Internet s'il existe une version plus récente. La propriété org.terracotta.quartz.skipUpdateCheck de la JVM permet de préciser si la vérification doit être activée. Il est préférable de désactiver cette fonctionnalité en production. Valeur par défaut : false |
La fabrique StdChedulerFactory peut lire, créer et enregistrer des listeners dans le scheduler au moment de sa création. Ces listeners seront globaux donc concerneront tous les événements de l'instance de leur type (job et trigger).
Chaque listener doit être défini et identifié avec un nom unique contenu dans les clés le concernant.
Pour définir un listener, il faut :
Dans la forme des clés, il faut remplacer :
Exemple de configuration d'un JobListener : |
org.quartz.jobListener.MonJobListener.class = fr.jmdoudoux.dej.quartz.MonJobListener
org.quartz.jobListener.MonJobListener.min = 100
org.quartz.jobListener.MonJobListener.max = 1000 |
Quartz utilise un pool de threads pour permettre d'exécuter des jobs en parallèle.
Lorsqu'un trigger déclenche l'exécution d'un job, le scheduler obtient un thread libre de son pool et lui fait exécuter les traitements du job.
La taille du pool de threads doit être configurée pour permettre l'exécution en simultanée de tous les jobs requis.
Cette configuration se fait grâce à plusieurs propriétés :
Nom |
Description |
org.quartz.threadPool.class |
Nom pleinement qualifié de l'implémentation du pool de threads à utiliser. Valeur par défaut : null |
org.quartz.threadPool.threadCount |
Valeur entière positive qui représente le nombre de threads dans le pool Valeur par défaut : -1 qui signifie pas de pool de threads |
org.quartz.threadPool.threadPriority |
Entier compris entre Thread.MIN_PRIORITY(1) et Thread.MAX_PRIORITY(10) qui précise la priorité des threads (optionnel) Valeur par défaut : Thread.NORM_PRIORITY (5) |
Plusieurs propriétés sont spécifiques à la classe SimpleThreadPool qui est l'implémentation du pool de threads fournie par défaut par Quartz.
Nom |
Description |
org.quartz.threadPool.makeThreadsDaemons |
Les threads du pool sont des démons (optionnel) |
org.quartz.threadPool.threadsInheritGroupOfInitializingThread |
(optionnel) |
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread |
(optionnel) |
org.quartz.threadPool.threadNamePrefix |
Préfixe utilisé dans le nom des threads du pool (optionnel) |
Les entités utilisées par le scheduler pour réaliser la planification sont stockées dans un JobStore. Quartz propose plusieurs implémentations d'un JobStore, chacune possédant ses propres propriétés pour sa configuration.
Un RAMJobStore stocke les informations de planification du scheduler en mémoire. Pour utiliser un JobStore de type RAMJobStore, il faut préciser le nom pleinement qualifié de la classe comme valeur pour la propriété org.quartz.jobstore.class.
Exemple : |
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore |
Un RAMJobStore peut être configuré grâce à une propriété :
Nom de la propriété |
Type |
Description |
org.quartz.jobStore.misfireThreshold |
int |
Définir un nombre de millisecondes de tolérance avant que le déclenchement d'un trigger ne soit considéré comme raté Valeur par défaut : 60000 |
Un JDBCStore permet de stocker les informations des entités de planification (jobs, triggers, calendars, ...) dans une base de données relationnelle.
Remarque : les scripts de création des tables Quartz se trouvent dans le répertoire docs/dbTables de l'archive de la distribution.
Deux implémentations sont utilisables selon le comportement transactionnel souhaité :
Un JobStoreTX est à mettre en oeuvre si aucune transaction distribuée n'est utilisée.
Pour utiliser un JobStore de type JobStoreTX, il faut préciser le nom pleinement qualifié de la classe comme valeur pour la propriété org.quartz.jobstore.class
Exemple : |
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX |
Un JobStoreTX peut être configuré grâce à plusieurs propriétés, notamment :
Nom de la propriété |
Type |
Description |
org.quartz.jobStore.driverDelegateClass |
String |
Définir le nom pleinement qualifié de la classe qui va assurer la communication avec une base de données précise. (Obligatoire) Quartz propose une implémentation générique et plusieurs implémentations pour le support des principales bases de données lorsque celles-ci requièrent des opérations spécifiques : org.quartz.impl.jdbcjobstore.StdJDBCDelegate : support générique pour un pilote JDBC Valeur par défaut : null |
org.quartz.jobStore.dataSource |
String |
Préciser le nom de la datasource : ce nom doit être défini dans la configuration (Obligatoire) Valeur par défaut : null |
org.quartz.jobStore.tablePrefix |
String |
Définir le préfixe des tables dans la base de données Valeur par défaut : "QRTZ_" |
org.quartz.jobStore.useProperties |
boolean |
Indiquer au JDBCJobStore que toutes les valeurs des JobDataMaps sont des String ce qui évite d'avoir à les sérialiser et stocker ces valeurs dans un champ de type blob Valeur par défaut : false |
org.quartz.jobStore.misfireThreshold |
int |
Définir un nombre de millisecondes de tolérance avant que le déclenchement d'un trigger ne soit considéré comme raté Valeur par défaut : 60000 |
org.quartz.jobStore.isClustered |
boolean |
Préciser si la base de données est utilisée par plusieurs instances de Quartz. Valeur par défaut : false |
org.quartz.jobStore.dontSetAutoCommitFalse |
boolean |
Préciser de ne pas invoquer la méthode setAutoCommit(false) sur les connexions à la DataDource. Valeur par défaut : false |
org.quartz.jobStore.selectWithLockSQL |
String |
Définir la requête SQL qui est utilisée pour poser un verrou dans la table xxxLOCKS {0} est remplacé à l'exécution par le préfixe des tables {1} est remplacé à l'exécution par le nom du scheduler Valeur par défaut : "SELECT * FROM {0}LOCKS WHERE SCHED_NAME = {1} AND LOCK_NAME = ? FOR UPDATE" |
org.quartz.jobStore.txIsolationLevelSerializable |
boolean |
Demander d'utiliser le niveau d'isolation Connection.TRANSACTION_SERIALIZABLE Valeur par défaut : false |
org.quartz.jobStore.driverDelegateInitString |
String |
Chaîne de caractères permettant de préciser des paires clé=valeur qui seront utilisées par le DriverDelegate. Les paires son séparées par un caractère | Exemple : "propriete1=valeur1|propriete2=valeur2" |
Exemple : |
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource = QUARTZ_DS
org.quartz.jobStore.tablePrefix = QUARTZ_ |
Un JobStoreCMT est à mettre en oeuvre si les opérations du JobStore doivent être incluses dans une transaction distribuée. Le gestionnaire de transaction doit être démarré avant le scheduler.
Un JobStoreCMT a besoin de deux datasources :
Pour utiliser un JobStore de type JobStoreCMT, il faut préciser le nom pleinement qualifié de la classe comme valeur pour la propriété org.quartz.jobstore.class
Exemple : |
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT |
Un JobStoreCMT peut être configuré grâce à plusieurs propriétés :
Nom de la propriété |
Type |
Description |
org.quartz.jobStore.driverDelegateClass |
String |
Identique à la propriété correspondante du JobStoreTX (Obligatoire) Valeur par défaut : null |
org.quartz.jobStore.dataSource |
String |
Préciser le nom de la datasource dont les connexions doivent être compatibles JTA : ce nom doit être défini dans la configuration (Obligatoire) Valeur par défaut : null |
org.quartz.jobStore.nonManagedTXDataSource |
String |
Préciser le nom de la datasource dont les connexions ne doivent pas être compatibles JTA : ce nom doit être défini dans la configuration (Obligatoire) Valeur par défaut : |
org.quartz.jobStore.tablePrefix |
String |
Identique à la propriété correspondante du JobStoreTX Valeur par défaut : "QRTZ_" |
org.quartz.jobStore.useProperties |
boolean |
Identique à la propriété correspondante du JobStoreTX Valeur par défaut : false |
org.quartz.jobStore.misfireThreshold |
int |
Identique à la propriété correspondante du JobStoreTX Valeur par défaut : 60000 |
org.quartz.jobStore.isClustered |
boolean |
Identique à la propriété correspondante du JobStoreTX Valeur par défaut : false |
org.quartz.jobStore.dontSetAutoCommitFalse |
boolean |
Identique à la propriété correspondante du JobStoreTX Valeur par défaut : false |
org.quartz.jobStore.selectWithLockSQL |
String |
Identique à la propriété correspondante du JobStoreTX Valeur par défaut : "SELECT * FROM {0}LOCKS WHERE SCHED_NAME = {1} AND LOCK_NAME = ? FOR UPDATE" |
org.quartz.jobStore.txIsolationLevelSerializable |
boolean |
Identique à la propriété correspondante du JobStoreTX Valeur par défaut : false |
org.quartz.jobStore.txIsolationLevelReadCommitted |
boolean |
Identique à la propriété correspondante du JobStoreTX Valeur par défaut : false |
org.quartz.jobStore.driverDelegateInitString |
String |
Identique à la propriété correspondante du JobStoreTX Valeur par défaut : null |
La configuration d'une datasource utilisée par le JDBCJobStore peut se faire de trois manières :
Il est préférable que la taille du pool de connexions lié à la Datasource soit plus grande que la taille du pool de threads. Lors de l'utilisation du JobStoreCMT, la taille du pool de la Datasource non JTA doit être au moins de 4.
Une Datasource créée par Quartz peut être configurée grâce à plusieurs propriétés :
Nom de la propriété |
Type |
Description |
org.quartz.dataSource.NAME.driver |
String |
Nom pleinement qualifié de la classe Java qui correspond au driver JDBC à utiliser (Obligatoire) Valeur par défaut : null |
org.quartz.dataSource.NAME.URL |
String |
L'url de connexion à la base de données Valeur par défaut : null |
org.quartz.dataSource.NAME.user |
String |
Le nom de l'utilisateur utilisé pour se connecter à la base de données Valeur par défaut : "" |
org.quartz.dataSource.NAME.password |
String |
Le mot de passe de l'utilisateur utilisé pour se connecter à la base de données Valeur par défaut : "" |
org.quartz.dataSource.NAME.maxConnections |
int |
Le nombre maximum de connexions que pourra contenir le pool de connexions Valeur par défaut : 10 |
org.quartz.dataSource.NAME.validationQuery |
String |
Requête SQL utilisée pour tester les connexions invalides Valeur par défaut : null |
org.quartz.dataSource.NAME.validateOnCheckout |
boolean |
Demander l'exécution d'une requête SQL chaque fois d'une connexion est obtenue du pool pour vérifier qu'elle est toujours valide Valeur par défaut : false |
org.quartz.dataSource.NAME.discardIdleConnectionsSeconds |
int |
Timeout en secondes pour retirer une connexion inutilisée du pool de connexions Valeur par défaut : 0 |
Exemple : |
org.quartz.dataSource.QUARTZ_DS.driver = oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.QUARTZ_DS.URL = jdbc:oracle:thin:@localhost:1521:demodb
org.quartz.dataSource.QUARTZ_DS.user = mon_user
org.quartz.dataSource.QUARTZ_DS.password = user_mdp
org.quartz.dataSource.QUARTZ_DS.maxConnections = 10 |
Chaque Datasource définie doit avoir un nom unique qui correspond à NAME dans le tableau ci-dessous. L'utilisation de Datasource configurée dans le serveur d'applications se fait grâce à plusieurs propriétés :
Nom de la propriété |
Type |
Description |
org.quartz.dataSource.NAME.jndiURL |
String |
L'URL JNDI de la datasource configurée dans le serveur d'applications. (Obligatoire) Valeur par défaut : null |
org.quartz.dataSource.NAME.java.naming.factory.initial |
String |
Nom pleinement qualifié de la classe de type InitialContextFactor Valeur par défaut : null |
org.quartz.dataSource.NAME.java.naming.provider.url |
String |
URL pour se connecter au contexte JNDI Valeur par défaut : null |
org.quartz.dataSource.NAME.java.naming.security.principal |
String |
Le nom de l'utilisateur pour se connecter au contexte JNDI Valeur par défaut : null |
org.quartz.dataSource.NAME.java.naming.security.credentials |
String |
Le mot de passe de l'utilisateur pour se connecter au contexte JNDI Valeur par défaut : null |
Il est possible d'utiliser Quartz en mode client/serveur : dans ce mode, le scheduler s'exécute dans une JVM distincte. Le client peut interagir avec le scheduler en utilisant de manière transparente RMI.
Cela peut être utile pour mutualiser le scheduler sur plusieurs applications et/ou pour réduire l'activité sur le client puisque l'exécution du scheduler et des tâches qu'il va déclencher se fait sur la JVM distante.
La configuration de l'utilisation en mode client/serveur utilise plusieurs propriétés de la configuration.
Nom de la propriété |
Type |
Description |
org.quartz.scheduler.rmi.export |
String |
Demander au scheduler d'exporter ses fonctionnalités grâce à RMI Valeur par défaut : false |
org.quartz.scheduler.rmi.registryHost |
String |
Préciser le nom du host sur lequel s'exécute le registre RMI : c'est généralement la machine locale Valeur par défaut : localhost |
org.quartz.scheduler.rmi.registryPort |
String |
Préciser le port sur lequel le registre RMI écoute Valeur par défaut : 1099 |
org.quartz.scheduler.rmi.createRegistry |
String |
Définir si oui et comment le registre RMI est créé. Plusieurs valeurs sont utilisables :
Valeur par défaut : never |
org.quartz.scheduler.rmi.serverPort |
String |
Préciser le numéro du port sur lequel le Scheduler attend des connexions Valeur par défaut : random |
org.quartz.scheduler.rmi.proxy |
String |
Préciser que l'on souhaite se connecter à un scheduler distant. Dans ce cas, l'instance du scheduler sera un proxy permettant d'invoquer des méthodes du scheduler distant Valeur par défaut : false |
Remarque : il n'est pas cohérent de mettre les valeurs true en même temps aux propriétés org.quartz.scheduler.rmi.export et org.quartz.scheduler.rmi.proxy.
Pour lancer le scheduler, il est nécessaire de mettre en place un RMISecurityManager.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzServeur {
public static void main(final String[] args) {
if (System.getSecurityManager() != null) {
System.setSecurityManager(new java.rmi.RMISecurityManager());
}
try {
final SchedulerFactory schedulerFactory = new StdSchedulerFactory();
final Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.start();
} catch (final SchedulerException se) {
se.printStackTrace();
}
}
} |
Il faut configurer le scheduler pour permettre une invocation de ces fonctionnalités en utilisant RMI.
Exemple : le fichier Quartz_serveur.properties |
org.quartz.scheduler.instanceName = scheduler_serveur
org.quartz.scheduler.instanceId = scheduler1
org.quartz.scheduler.rmi.export = true
org.quartz.scheduler.rmi.registryHost = localhost
org.quartz.scheduler.rmi.registryPort = 1099
org.quartz.scheduler.rmi.createRegistry = true
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 4
org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownhook.cleanShutdown = true |
Il faut préciser le fichier de configuration en tant que paramètre de la JVM :
-Dorg.quartz.properties=src/main/resources/quartz_serveur.properties
Résultat : |
INFO StdSchedulerFactory - Using default implementation for ThreadExecutor
INFO SchedulerSignalerImpl - Initialized Scheduler Signaller of type:
class org.quartz.core.SchedulerSignalerImpl
INFO QuartzScheduler - Quartz Scheduler v.2.2.1 created.
INFO ShutdownHookPlugin - Registering Quartz shutdown hook.
INFO RAMJobStore - RAMJobStore initialized.
INFO QuartzScheduler - Scheduler bound to RMI registry under name
'scheduler_serveur_$_scheduler1'
INFO QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.1)
'scheduler_serveur' with instanceId 'scheduler1'
Scheduler class: 'org.quartz.core.QuartzScheduler' - access via RMI.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 4 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support
persistence. and is not clustered.
INFO StdSchedulerFactory - Quartz scheduler 'scheduler_serveur' initialized
from specified file: 'src/main/resources/quartz_serveur.properties'
INFO StdSchedulerFactory - Quartz scheduler version: 2.2.1
INFO QuartzScheduler - Scheduler scheduler_serveur_$_scheduler1 started. |
Dans le mode client/serveur, l'instance du scheduler côté client est uniquement un proxy qui permet d'invoquer les fonctionnalités sur l'instance distante du scheduler.
Exemple : |
package fr.jmdoudoux.dej.quartz;
import java.util.Date;
import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzClient {
public static void main(final String[] args) {
try {
final SchedulerFactory factory = new StdSchedulerFactory();
final Scheduler scheduler = factory.getScheduler();
final JobDetail jobDetail = JobBuilder
.newJob(MonJob.class)
.withIdentity("monJob")
.storeDurably()
.build();
JobBuilder
.newJob(MonJob.class)
.withIdentity("monJobNouveau")
.storeDurably()
.build();
final Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(new TriggerKey("monTrigger", "monGroup"))
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInMinutes(1)
.repeatForever())
.startAt(DateBuilder.evenMinuteDate(new Date()))
.build();
scheduler.scheduleJob(jobDetail, trigger);
System.in.read();
} catch (final Exception e) {
e.printStackTrace();
}
}
} |
La configuration du scheduler précise uniquement les informations utiles pour invoquer le scheduler distant grâce à RMI.
Exemple : Quartz_client.properties |
org.quartz.scheduler.instanceName = scheduler_serveur
org.quartz.scheduler.instanceId = scheduler1
org.quartz.scheduler.rmi.proxy = true
org.quartz.scheduler.rmi.registryHost = localhost
org.quartz.scheduler.rmi.registryPort = 1099 |
Il faut préciser le fichier de configuration en tant que paramètre de la JVM
-Dorg.quartz.properties=src/main/resources/quartz_client.properties
Chaque plugin doit être défini avec un nom unique qui doit suivre org.quarts.plugin dans la définition de la configuration.
Pour utiliser un plugin, il faut préciser le nom pleinement qualifié de sa classe comme valeur de la propriété org.quartz.plugin.XXX.class où XXX est le nom unique du plugin dans l'instance du scheduler.
Il est possible de fournir une valeur à un paramètre du plugin en utilisant la propriété org.quartz.plugin.XXX.nom_propriete où XXX est le nom unique du plugin et nom_propriete est le nom de la propriété qui sera valorisée par introspection.
Quartz propose en standard plusieurs plugins contenus dans le package org.quartz.plugins et ses sous-packages :
La classe abstraite SchedulerPluginWithUserTransactionSupport offre des fonctionnalités de base pour développer des plugins dont les méthodes start() et shutdown() ont en paramètre un UserTransaction. C'est utile lorsqu'un JobStoreCMT est utilisé et que le plugin interagit avec des jobs ou des triggers. Si le JobStore utilisé est de type JobStoreCMT, il faut obligatoirement que la propriété wrapInUserTransaction soit positionnée à la valeur true. Le paramètre de type UserTransaction ne doit pas être null si la propriété wrapInUserTransaction est à true. La méthode initialize() est invoquée par le Scheduler lors de l'initialisation du plugin.
Le plugin LoggingJobHistoryPlugin permet de journaliser l'exécution des jobs dans un log en utilisant la bibliothèque Apache Commons Logging.
De manière similaire, le plugin LoggingTriggerHistoryPlugin permet de journaliser l'exécution des triggers dans un log.
Il est possible de configurer le contenu journalisé selon le type de messages en utilisant la syntaxe de la classe java.util.MessageFormat. En fonction du type de message, plusieurs placeholders ({n} où n varie de 0 à 8) sont utilisables pour configurer le contenu du message. Chaque message associe un numéro à une donnée : chacune de ces valeurs est détaillée dans la Javadoc.
Résultat : |
org.quartz.plugin.jobsLogging.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin
org.quartz.plugin.jobsLogging.jobSuccessMessage=Le job [{1}.{0}]
a terminé son execution avec le code retour : {8}
org.quartz.plugin.jobsLogging.jobFailedMessage=Le job [{1}.{0}]
a terminé son execution avec l'exception: {8}
org.quartz.plugin.jobsLogging.jobWasVetoedMessage=L'execution du job [{1}.{0}]
a été bloquée. Il a été déclenché
par le trigger [{4}.{3}] à {2, date, dd-MM-yyyy HH:mm:ss.SSS}
org.quartz.plugin.triggersLogging.class=org.quartz.plugins.history.LoggingTriggerHistoryPlugin
org.quartz.plugin.triggersLogging.triggerFiredMessage=Le trigger [{1}.{0}]
déclenche le job [{6}.{5}] prévu à :
{2, date, dd-MM-yyyy HH:mm:ss.SSS}, prochaine exécution à :
{3, date, dd-MM-yyyy HH:mm:ss.SSS}
org.quartz.plugin.triggersLogging.triggerCompleteMessage=Le trigger [{1}.{0}]
a terminé le déclenchement du job [{6}.{5}] avec le résultat : {9}.
org.quartz.plugin.triggersLogging.triggerMisfiredMessage=Le trigger [{1}.{0}]
a rate le déclenchement du job [{6}.{5}] qui aurait du survenir à
{3, date, dd-MM-yyyy HH:mm:ss.SSS} |
Le plugin ShutdownHookPlugin permet de capturer l'événement de terminaison de la JVM pour permettre un arrêt propre du scheduler.
Exemple : |
org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownhook.cleanShutdown = true |
Le plugin XMLSchedulingDataProcessorPlugin permet de lire un fichier XML contenant la définition de jobs et triggers qui seront lus et enregistrés dans le scheduler lors de son démarrage. Il permet aussi de gérer les jobs et les triggers.
Exemple : |
org.quartz.plugin.initialisation.class=org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
org.quartz.plugin.initialisation.fileNames=ma_configuration.xml
org.quartz.plugin.initialisation.failOnFileNotFound=true |
Pour la version 1.8.5 de Quartz, le schéma du fichier de configuration XML peut être obtenu à l'url :
https://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd
A partir de la version 2.0 de Quartz, le schéma du fichier de configuration XML peut être obtenu à l'url :
https://www.quartz-scheduler.org/xml/job_scheduling_data_2_0.xsd
Plusieurs instances de Scheduler peuvent être rassemblées dans un cluster qui permet :
Le partage des informations entre les instances du cluster se fait en utilisant un JDBCJobStore.
Chaque instance du cluster doit avoir dans sa configuration la valeur true dans sa propriété org.quartz.jobStore.isClustered. Chacun d'eux doit avoir une valeur unique pour un même cluster dans sa propriété org.quartz.scheduler.instanceId.
Toutes les horloges des machines exécutant des instances du cluster doivent être synchronisées.
|