Il est fréquent de devoir exécuter des tâches planifiées, pour une ou plusieurs exécution(s), qu’elles soient récurrentes ou non. Dans notre cas, on veut planifier les envois de mails pour notre plateforme de formations internes. Ce qui est important, c’est que ces tâches planifiées soient gérées par un batch/agent extérieur et que cet agent ait une structure permettant une très bonne facilité de maintenance et de déploiement.

C’est là qu’entre en jeu la notion d’héritage de classe. L’idée est que nos agents vont tous hériter d’un socle commun donnant les outils nécessaires à leur bon fonctionnement.

Architecture :

  • Notre agent a besoin d’initialiser une tâche.
  • Notre agent a besoin de gérer son statut.
  • Notre agent a besoin de loguer des informations.
  • Notre agent a besoin de s’exécuter.

Classe de base et sous-classe
La classe de base (ou classe mère) est celle qui définit les membres qui seront hérités par les sous-classes. Une sous-classe (ou classe fille) hérite des membres de la classe mère, peut ajouter de nouveaux membres, implémenter de nouvelles interfaces et redéfinir le comportement des méthodes de la classe de base.

Lorsqu’une classe ne déclare aucune classe de base, par défaut elle hérite de la classe System.Object. La classe System.Object est la seule qui n’hérite d’aucune autre classe.

Création de la classe de base « Agent »

La classe Agent est définie comme abstraite, car ce n’est pas cette classe qu’on souhaite instancier, mais ses « enfants ».

public abstract class AgentBase {}

On définit tout d’abord via une classe d’énumération les constantes suivantes : Pending, Success, Error – qui correspondent à l’état du statut – tel que :

public enum Status
{
    Pending,
    Success,
    Error
}

Ici, nous souhaitons connaître l’état de nos agents. Pour cela nous allons utiliser la classe java.util.logging.Logger qui va nous afficher ces états via des fichiers de log.

Afin de paramétrer nos fichiers de sortie, nous passerons par la classe java.util.Properties. Les Properties représentent des paires key/values pouvant être enregistrées dans un flux ou chargées à partir d’un flux. Chaque paire correspondante dans la liste de propriétés est une string. Elle va nous permettre de stocker le nom du fichier, sa taille et le nombre maximal de fichiers que l’on peut avoir, ainsi que son statut de réussite, le tout enregistré dans un fichier XML.

Ensuite nous passerons par une méthode abstraite que nous nommerons doWork(String[] args). Elle sera overridée par la classe enfant afin d’initialiser une tâche précise. Puis, c’est depuis la méthode run(String[] args), que nous appellerons la méthode doWork afin qu’elle soit exécutée et qu’en même temps, on affiche dans le fichier de log si elle a été correctement exécutée (Succes, Error).

Voici comment on a procédé :

public abstract class AgentBase {

    private Status status = Status.Pending; // On initialise le status à 'en cours' (pending)
    protected Logger logger;

    public AgentBase() throws IOException {
    }

    // création du fichier de configuration 'config_agent.properties' dans lequel se trouve le nom du fichier, sa taille et le nombre maximal de fichier.
    public void createDefaultConfig() throws IOException {
        Properties props = new Properties();
        props.setProperty('FileName', 'Agent_logs_%g.xml');
        props.setProperty('ByteSize', '5000000');
        props.setProperty('NumberOfFile', '8');
        try(OutputStream stream = Files.newOutputStream(Paths.get('config_agent.properties'))) {
            props.storeToXML(stream, 'Agent configuration file');
        }
    }

    // lecture du fichier config_agent.properties avec le logger
    public void readConfig(final String properties_path) throws IOException {
        Properties props = new Properties();

        InputStream reader = Files.newInputStream(Paths.get(properties_path));
        props.loadFromXML(reader);

        String FileName = props.getProperty('FileName');
        int ByteSize = Integer.parseInt(props.getProperty('ByteSize'));
        int NumberOfFile = Integer.parseInt(props.getProperty('NumberOfFile'));

        logger = Logger.getLogger('AgentBase');
        logger.addHandler(new FileHandler(FileName,ByteSize, NumberOfFile, true));
    }

    // la méthode plannification à implémenter par la sous classe.
    protected abstract void doWork(String[] args);

    // lancement de la tâche plannifiée, avec l'état de réussite ou non
    public Status run(String[] args) {
        try {
            doWork(args);
            status = Status.Success;
        } catch(Exception e) {
            status = Status.Error;
            logger.log(Level.WARNING, 'This is a warning! ' + e);
        }
        logger.log(Level.INFO, 'Agent status : ' + status.toString());
        return status;
    }
}


Maintenant, on va compresser le projet AgentBase dans un JAR (Java ARchive) qu’on va intégrer dans un projet Agent enfant. Un JAR sert à compresser des applications et des bibliothèques de classes Java, et ce, afin de faciliter leur distribution. En effet, aujourd’hui nous avons un agent dédié à l’envoi de mails, mais demain, on aura peut-être besoin d’un agent pour gérer une toute autre tâche.

Exemple d’une classe sous Agent « MailPlanner »

Tout d’abord, on définit la classe MailPlanner en tant qu’enfant de la classe de base Agent.

public class MailPlanner extends AgentBase {
}


Puis, on override la méthode doWork, dans laquelle on va implémenter nos jobs qui correspondent aux tâches planifiées. Ci-dessous, on a défini un job jobOnTheVeryDay instancié par la classe org.quartz.JobBuilder. On a affecté à ce job la fonctionnalité définie dans le JobBuilder (ici, c’est la classe ReminderTrainingOnTheVeryDay) et on a défini un trigger triggerOnTheVeryDay qui s’exécute tous les jours de la semaine prévu à 9h00 grâce à la CRON expression « 0 0 9 ? * MON,TUE,WED,THU,FRI * »

public class MailPlanner extends AgentBase {
    public MailPlanner () throws IOException {
        super();
    }

    @Override
    protected void doWork(String[] strings) {
        //Job & Trigger planned on the very day
        JobDetail jobOnTheVeryDay = JobBuilder.newJob(ReminderTrainingOnTheVeryDay.class)
                .withIdentity('dummyJobName', 'group1').build();
        Trigger triggerOnTheVeryDay = TriggerBuilder
                .newTrigger()
                .withIdentity('dummyTriggerName', 'group1')
                .withSchedule(
        CronScheduleBuilder.cronSchedule('0 0 9 ? * MON,TUE,WED,THU,FRI *'))
}

A titre d’information, voici le code de la classe ReminderTrainingOnTheVeryDay :

public class ReminderTrainingOnTheVeryDay implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        try {
            ScheduledTasks scheduledtask =  new ScheduledTasks();
            try{
                scheduledtask.connectToAPI('http://localhost:10000/api/agent/scheduling-training-current-day', scheduledtask.getAuthorization(), 'PUT');
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Pour enclencher un job, il suffit d’utiliser la classe org.quartz.Scheduler, on instancie un objet de cette classe, on le lance et on lui associe un job et un trigger (dans la méthode doWork) tel que :

Scheduler scheduler = null;
try {
    scheduler = new StdSchedulerFactory().getScheduler();
    scheduler.start();
    scheduler.scheduleJob(jobOnTheVeryDay, triggerOnTheVeryDay);
} catch(SchedulerException e){
    e.printStackTrace();
}

Une fois que tous ces éléments sont réunis, on lance la tâche planifiée avec la méthode run() depuis la classe enfant (MailPlanner) dans la méthode main().

Catégories : Software technology

0 commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.