Introduzione

In questa seconda parte, tratteremo il cuore del mailing-list, e quindi per come è stato progettato, parleremo della classe ListJobs. Questa classe è instanziata dal costruttore del MailingListServer, e vengono passati come parametri un riferimento alla classe Timer e uno alla classe SynchronizationMonitor. La prima ci servirà per la gestione della scheduledJobs, la seconda è una semplice classe che servirà per sincronizzare i threads quando si inserisce in currentJobs e poi quando si è in attesa di nuovi Jobs da elaborare. Ecco la SynchronizationMonitor:

class SynchronizationMonitor {

    private boolean available = false;

    public synchronized void get() {
        if (available == false) {
            try { wait(); }
            catch (InterruptedException e) { System.out.println("InterruptedException"); }
        }

        available = false;
    }

    public synchronized void put() {
        if (available == true) {
            return;
        }

        available = true;
        notify();
    }
}

ListJobs include due strutture principali: una contiene i Jobs da inviare immediatamente (currentJobs), un'altra contiene quelli da inviare dopo una certa data prefissata (scheduledJobs). Occorre quindi dichiararle:

public SortedSet currentJobs = Collections.synchronizedSortedSet(new TreeSet(new JobsComparator()));
public Vector scheduledJobs = new Vector();

La prima consente di inserire Jobs sincronizzandoli e confrontadoli in base alle priorità (in JobsComparator). La seconda è un semplice Vector. La JobsComparator è un classe che estenderà un Comparator e la metodologia di ordinamento sarà la priorità del Job.

Nel costruttore di questa classe, andremo a deserializzare il file che contiene lo stato del mailing-list server. La scelta di questa tecnica è dovuta al fatto che nel caso di crash del sistema, viene salvato (deserializzato) l'ultimo invio della mailing-list su file system. In questo modo, se facciamo ripartire il mailing-list server, si attiveranno i vari timer e il sistema ripartirà dall'ultimo invio effettuato.

I metodi per serializzare/deserializzare le strutture sono i seguenti:

// Metodo per la serializzazione
public synchronized void serialize() {
    try {
        FileOutputStream out = new FileOutputStream(JOBS_LISTS);
        ObjectOutput s = new ObjectOutputStream(out);

        s.writeObject(currentJobs);
        s.writeObject(scheduledJobs);
        s.flush();
        s.close();
        out.close();
    } catch (Exception e) {
        logger.fatal("Exception: nella serializzazione delle liste", e);
        System.exit(1);
    }
}

// Metodo per la deserializzazione
public synchronized void deserialize() {
    try {
        FileInputStream in = new FileInputStream(JOBS_LISTS);
        ObjectInputStream s = new ObjectInputStream(in);

        currentJobs = (SortedSet)s.readObject();
        scheduledJobs = (Vector)s.readObject();

        s.close();
        in.close();
    } catch (Exception e) {
        logger.fatal("Exception: nella deserializzazione delle liste", e);
        System.exit(1);
    }
}

La costante JOBS_LISTS indica il file da serializzare, ad esempio 'conf/jobLists.ser'. Il logger appartiene al package org.apache.log4j e viene utilizzato per settare i log del sistema. Da come si può facilmente notare, andremo a memorizzare su file system le strutture descritte precedentemente.

Dopo aver recuperato gli oggetti che contengono i Jobs, si verifica, sempre nella ListJobs, se ci sono dati; nel caso particolare dello scheduledJobs, andremo a recuperare tutti i Jobs dal file JOBS_LISTS e, per ognuno di essi, si confronta la data in cui inviare con la data di sistema; se la prima è maggiore della seconda, lanceremo il comando:

timer.schedule(new JobScheduleDaemon(job, this, sm), job.getBeginDate());

Questo comando è lo stesso che troveremo ovviamente nel MailingListServer ed in particolare nel metodo send che contiene appunto la data di invio. Qui chiameremo il metodo schedule della classe Timer con i parametri TimerTask e la data di invio. JobScheduleDaemon estende perciò un TimerTask; il costruttore mantiene i riferimenti del job, della classe ListJobs e della classe SynchronizationMonitor. Il cuore di JobScheduleDaemon è il metodo run() che viene eseguito dal TimerTask quando la data passata al Timer è attiva. In questo metodo, sposteremo il Job da currentJobs a scheduledJobs, notificheremo al thread di inserimento tramite flag che un Job deve essere elaborato e quindi eseguiamo il comando sm.put().

Se la data di invio è < della data di sistema chiameremo il metodo

moveFromScheduleToCurrent(job);

e cioè:

// Sposta il Job passato come parametro dal vettore scheduledJobs
// alla collection currentJobs.
public void moveFromScheduleToCurrent(Job job) {
    scheduledJobs.remove(job);
    currentJobs.add(job);
    serialize();
}

L'ultima classe da esaminare in questo articolo è JobExtractor. Quest'ultima è un thread in ascolto su currentJobs. Il thread appena riceve una put della SynchronizationMonitor va a controllare la dimensione della lista solo se il MailSender è alive. Se non è alive, va ad estrarre il primo Job, che è quello con priorità più alta, lo cancella e da currentJobs, serializza le strutture di ListJobs e invia il Job alla MailSender.

Conclusione

La descrizione della classe ListJobs è effettivamente complessa, ma sarà più facile comprenderla se abbiamo in mente la struttura creata nell'articolo del mese precedente. Occorre focalizzare bene il funzionamento della ListJobs, che è in effetti il cuore del sistema. In particolare sulla classe SynchronizationMonitor che permette di sincronizzare i vari thread che "girano" nell'applicazione. Ricapitolando, i threads sono i seguenti:

  • JobExtractor: che controlla la struttura currentJobs; appena c'è una put da parte o di uno dei due metodi send della MailingListServer oppure da JobScheduleDaemon che sposta un Job da scheduledJobs a currentJobs, vuol dire che c'è da elaborare qualcosa.
  • JobScheduleDaemon: che si attiva quando una data è maggiore di quella di sistema.

In seguito ci sarà (dal prossimo e ultimo articolo) anche il thread MailSender.