MokaByte 86 - Giugno 2004 
Un gestore di mailing list
II parte
di
Giovanni Bruni

In questa seconda parte, tratteremo il cuore del mailing-list, e quindi per come è stato progettato, parleremo della classe ListJobs

La classe ListJobs è 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.

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it