MokaByte 79 - 9mbre 2003 
Java Mail 2003
Gestire la posta elettronica in Java - I parte
di
Massimiliano
Bigatti
JavaMail implementano il supporto generico alla posta elettronica, tramite un insieme di API ben disegnate, espressive ed espandibili. Tramite l'architettura a provider, è possibile supporta-re diversi standard; SUN infatti offre i provider per SMTP, POP3 ed IMAP

Introduzione
All'interno delle funzionalità offerte dalla piattaforma Java, si trova anche il supporto alla po-sta elettronica, realizzato tramite le API JavaMail. Questa libreria offre le funzionalità necessa-rie ad implementare applicazioni di email completamente funzionanti, come Outlook o Netsca-pe Mail. Non essendo realizzata con componenti visuali - la creazione di una interfaccia utente è interamente a carico dello sviluppatore - è utilizzabile sia in applicazioni desktop che sul lato server, ad esempio per implementare una WebMail.

 


Figura 1 - Tipologie di client di posta elettronica
(clicca per ingrandire l'immagine)

Come noto, esistono diversi protocolli che consentono ad una applicazione di realizzare la po-sta elettronica. I principali sono tre:

  • SMTP (Simple Mail Transfer Protocol). Consente l'invio di un messaggio di posta;
  • POP (Post Office Protocol). Protocollo comunemente utilizzato per la ricezione di messaggi di posta da un server;
  • IMAP (Internet Message Access Protocol). Protocollo di posta orientato al server e di tipo drop-and-store, più evoluto rispetto a POP e dotato di funzionalità di gestione del server che facilita la gestione della posta off-line.

Attualmente le versioni più diffuse sono la 3 per POP e la 4 per IMAP.
JavaMail, come vedremo, supporta tutti questi protocolli, ma non ne è limitato, in quanto im-plementa un'architettura a provider, che consente di svincolare le applicazioni dal reale proto-collo utilizzato, creando una astrazione di un completo sistema di posta elettronica. In modo similare a quanto avviene in JDBC, dove la comunicazione effettiva al database avviene trami-te driver specifici, allo stesso modo JavaMail si astrae dai protocolli specifici, che sono co-munque forniti assieme al package principale.
Si noti che le implementazione dei provider di riferimento appartengono al package sun.* e non a javax.*. Non sono quindi API standard facenti parte delle specifiche di JavaMail.

 

Le piattaforme
Il package JavaMail è ottenibile in due diversi modi, per prima cosa è possibile scaricare l'implementazione di riferimento e la documentazione dal sito ufficiale ospitato da SUN, che si trova all'indirizzo http://java.sun.com/products/javamail/. Il package è utilizzabile all'interno di un ambiente J2SE a partire dalla versione 1.1.7, e richiede anche il JavaBeans Activation Framework (http://java.sun.com/products/javabeans/glasgow/jaf.html) un package forse poco famoso, che ha lo scopo di abilitare la gestione, all'interno di applicazioni Java, di blocchi di dati di tipo arbitrario, incapsulandoli ed accedendovi, e di scoprire le operazioni effettuabili su di essi. Esempi sono le immagini JPEG o GIF, oppure documenti Word od Excel; l'Activation Framework è utilizzato all'interno di JavaMail per supportare gli allegati binari ai messaggi di posta ed anche per scoprire a runtime il tipo di dato memorizzato all'interno di un blocco bina-rio.
Se invece si sta utilizzando la piattaforma Java2 Enterprise Edition (J2EE), a partire dalla ver-sione 1.3, non è necessario scaricare né l'una, né l'altra, in quanto i servizi legati alla posta e-lettronica sono parte fondamentale e formante della piattaforma enterprise.
Ovviamente le modalità d'accesso cambieranno leggermente nelle due piattaforme, in modo similare alle differenze che esistono in JDBC per accedere ad una connessione al database in una applicazione J2SE ed in una applicazione J2EE, ma il corpo delle funzionalità rimane il medesimo.

 

Invio di un messaggio
Per illustrare con semplicità un utilizzo pratico di JavaMail, si osservi il listato 1, che imple-menta un semplice programma di invio di un messaggio di posta, forse il più semplice realiz-zabile con queste API. La prima cosa che si nota è la presenza dell'importazione dei package javax.mail e javax.mail.internet, due componenti principali di JavaMail.
All'interno del primo package si trovano le classi principali, come Session e Message, utilizzati nel programma per l'invio del messaggio; la prima classe - di tipo final - implementa una ses-sione di posta ed ha lo scopo di raccogliere proprietà e configurazioni e di fornire le sessioni alle classi client. Le sessioni possono essere di due tipi: condivise o meno; nel primo caso una unica sessione viene utilizzata da parte di più sezioni del programma, tipicamente in una appli-cazione desktop. In ambiente server è invece preferibile utilizzare sessioni non condivise, otte-nute in modo similare a come in JDBC si ottiene una connessione da un DataSource.
Per ottenere una sessione condivisa è necessario utilizzare il metodo Sessione.getDefaultInstance() a cui è indispensabile passare un oggetto Properties con i parame-tri di configurazione necessari ad operare con il protocollo di posta. L'unico parametro indi-spensabile in questo caso è mail.smtp.host, che indica il nome o l'indirizzo del server SMTP di invio.
Il programma poi crea un oggetto Message di tipo MimeMessage e ne imposta le proprietà. In particolare:

  • il mittente;
  • il destinatario;
  • l'oggetto;
  • la data di invio;
  • il testo del messaggio.

Questi parametri sono impostati tramite i metodi riassunti in tabella 1 che nella classe Message sono astratti, mentre in MimeMessage sono effettivamente implementati.


Tabella 1 - Alcuni metodi della classe Message

A questo punto è possibile inviare il messaggio utilizzando il metodo Transport.send(). Questo metodo si occuperà dunque di individuare tutti i destinatari (tramite Message.getAllRecipients()) ed ad inviargli il messaggio, utilizzando il trasporto appropriato per ciascun destinatario (alcuni di questi potrebbero non essere destinatari Internet).
Gli indirizzi sono infatti stati rappresentati con oggetti InternetAddress (classe presente nel package javax.mail.internet), classe che rappresenta indirizzi nella forma nome@server.dominio e che estende javax.mail.Address.
Il metodo send() può sollevare due eccezioni nel caso l'invio non vada a buon fine: SendFaildException e MessagingException entrambi presenti nel package javax.mail.

Si noti che send() può eseguire un invio parziale. Se ad esempio un messaggio è indirizzato a più destina-tari, chiamiamoli Mario, Giovanni, Andrea, Luca, Elisa e Maurizio nell'ordine e l'invio fallisce per Gio-vanni, Luca e Maurizio, l'invio a Mario, Andrea ed Elisa (chi mancava dall'elenco precedente) viene co-munque fatto. Al termine delle operazioni viene sollevata una eccezione che riassume quanto successo: fornisce l'elenco dei destinatari per cui l'invio è andato a buon fine e l'elenco dei destinatari per cui l'invio non è riuscito.

Listato 1 - Invio.java
package com.mokabyte.mokabook2.javamail;

import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;

public class Invio {

public static void main( String[] args ) {

  try {
    Properties props = System.getProperties();
    props.put( "mail.smtp.host", args[2] );

    Session session = Session.getDefaultInstance( props );
    Message message = new MimeMessage( session );

    InternetAddress from = new InternetAddress( args[0] );
    InternetAddress to[] = InternetAddress.parse( args[1] );

    message.setFrom( from );
    message.setRecipients( Message.RecipientType.TO, to );
    message.setSubject( args[3] );
    message.setSentDate( new Date() );
    message.setText( args[4] );

    Transport.send(message);
    } catch(MessagingException e) {
      e.printStackTrace();
    }
  }

}

Per eseguire l'esempio è possibile utilizzare il file di Ant build.xml fornito con il codice sor-gente, ma è necessario modificare i parametri che riguardano il destinatario ed il server SMTP da utilizzare. Per inviare il messaggio di prova digitare poi:

ant invio

La porzione di script di Ant che si occupa di ereguire il programma è la seguente:

<target name="testInvio">
<java classname="com.mokabyte.mokabook2.javamail.Invio" fork="yes">
<classpath refid="project-classpath"/>
<arg value="sender@null.it" />
<arg value="max@bigatti.it" />
<arg value="mail.tin.it" />
<arg value="[Mokabyte] - Messaggio di prova" />
<arg value="Questo e' un messaggio di prova per verificare l'invio di un messaggio attraverso le API di SUN Javamail. Questo package implementa una astrazione di un sistema di posta elettronica fornendo anche provider per i protocolli SMTP, IMAP e POP3" />
</java>
</target>

Nota: l'elenco delle librerie richieste per eseguire gli esempi sono: activation.jar, imap.jar, mailapi.jar, pop3.jar, smtp.jar.

Se tutto funziona correttamente, si dovrebbe essere in grado di ricevere il messaggio ed ottene-re qualcosa simile al quanto presente in figura 2.


Figura 2
- Il messaggio ricevuto dal programma "Invio"
(clicca per ingrandire l'immagine)

 

Ricevere messaggi
Implementando un completo insieme di funzionalità per la gestione della posta elettronica, co-me accennato JavaMail dispone anche del supporto alla ricezione di messaggi di posta elettro-nica, tramite l'accesso a server di posta come POP e IMAP.
Il supporto alla ricezione dei messaggi è inserito al più ampio contesto dei folder, contenitori di messaggi che ricalcano concettualmente le caselle di posta dei programmi di email, come la posta in arrivo, la posta in uscita e la posta inviata.
La classe Folder, presente nel package javax.mail, rappresenta un folder astratto che può conte-nere messaggi (oggetti Message), altri folder o entrambi in modo gerarchico sotto forma di al-bero. Le sottoclassi di Folder, come IMAPFolder e POP3Folder, completano l'infrastruttura im-plementando gli specifici protocolli, limitando eventualmente la flessibilità nella costruzione di gerarchie di Folder, nel caso il protocollo specifico non supporti questa possibilità. I Folder la-vorano in stretto contatto con gli oggetti Store, che si occupano invece di fornire un modello per la memorizzazione dei messaggi, per il protocollo di accesso e per l'ottenimento dei mes-saggi. Anche in questo caso Store è una classe astratta usata come superclasse da IMAPStore e POP3Store, le due implementazioni fornite da JavaMail.

Il nome completo di un folder può avere significati diversi in funzione dell'implementazione concreta del folder, anche se il nome "INBOX" è riservato per indicare il folder principale del server, ma si noti che le implementazioni di Store devono obbligatoriamente fornire un IN-BOX. Se il folder primario per un certo protocollo esiste, si chiamerà INBOX, in caso contrario non ci sarà nessun folder con questo nome e la chiamata Folder.getFolder("INBOX") ritornerà null; per verificare l'esistenza di un folder è però consigliato utilizzare il metodo Folder.exists().

I passi per leggere da una casella di posta prevedono dunque, dopo l'ottenimento di un oggetto Session, il recupero dell'oggetto Store a partire dalla sessione, tramite il metodo Session.getStore(). Questo ritornerà un oggetto Store in funzione del protocollo configurato nel-la proprietà mail.store.protocol; in alternativa è possibile chiamare il metodo Session.getStore(string) e fornire direttamente il protocollo desiderato, come ad esempio "pop3". Se il protocollo richiesto non viene trovato, viene sollevata una eccezione NoSuchProviderException.

Una volta ottenuto l'oggetto Folder che rappresenta la casella richiesta, è necessario aprirla tramite il metodo open(), che si aspetta un parametro modo, che può valere Folder.READ_ONLY e Folder.READ_WRITE; una volta aperto il folder è possibile leggerne i messaggi con il metodo getMessages() (per una panoramica dei metodi di Folder si veda la ta-bella 2).

 


Tabella 2 - Metodi della classe Folder

Nel listato 2 è presente un esempio di lettura di messaggi che utilizza un server POP3. Come si vede l'implementazione ricalca la procedura sopra esposta; nel visualizzare i messaggi è stato implementato un controllo sul mittente. Di norma il metodo getPersonal() su un oggetto InternetAddress fornisce il nome del mittente, ma non sempre. Alcune volte il metodo ritorna null ed è dunque necessario rivolgersi al metodo toString() per ottenere una rappresentazione significativa dell'indirizzo.

Listato 2 - Ricezione.java
package com.mokabyte.mokabook2.javamail;

import java.util.*;

import javax.mail.*;
import javax.mail.internet.*;

public class Ricezione {

public static void main(String args[]) {

  try {
    Properties props = System.getProperties();
    Session session = Session.getDefaultInstance(props, null);

    Store store = session.getStore("pop3");
    
store.connect(args[0], args[1], args[2]);

    Folder folder = store.getDefaultFolder();
    if (folder != null) {
      folder = folder.getFolder("INBOX");
        if (folder != null) {
          folder.open(Folder.READ_ONLY);
          Message[] elencoMessaggi = folder.getMessages();
          for (int indice = 0; indice < elencoMessaggi.length; indice++) {
            Message messaggio = elencoMessaggi[ indice ];
            InternetAddress fromAddress = (InternetAddress)messaggio.getFrom()[0];
            String from = fromAddress.getPersonal();
            if( from == null ) {
              from = fromAddress.toString();
            }
          System.out.println("DA:" + from + " OGGETTO: " + messaggio.getSubject() +
                             " DATA: " + messaggio.getSentDate()
          );
        }
      folder.close(false);
      } else {
        System.out.println( "Folder non trovato" );
      }
     } else {
       System.out.println( "Folder di default non trovato" );
     }
     store.close();
   } catch (Exception ex) {
     ex.printStackTrace();
   }
  }
}

Per eseguire l'esempio utilizzare il comando:

Ant ricezione

La porzione di script di Ant eseguita è la seguente (attenzione anche in questo caso ad indicare i parametri di configurazione corretti):

<target name="testRicezione">
<java classname="com.mokabyte.mokabook2.javamail.Ricezione" fork="yes">
<classpath refid="project-classpath"/>
<arg value="pop3.server.it" />
<arg value="max/bigatti.it" />
<arg value="password" />
</java>
</target>

Un tipico output è simile al seguente (riformattato):

DA:Apple Developer Connection
OGGETTO: ADC News #372
DATA: Sun Oct 26 03:37:20 CET 2003

DA:Apple iCards
OGGETTO: Massimiliano Bigatti has sent you an Apple iCard
DATA: Thu Oct 30 10:17:28 CET 2003

DA:mrossi@tiscalinet.it
OGGETTO: orologio a mano...
DATA: Thu Oct 30 18:03:12 CET 2003

DA:sender@null.it
OGGETTO: [Mokabyte] - Messaggio di prova
DATA: Thu Oct 30 18:49:52 CET 2003
DA:sender@null.it
OGGETTO: [Mokabyte] - Messaggio di prova
DATA: Thu Oct 30 18:50:48 CET 2003

Conclusioni
In questa puntata sono state affrontate le classi che consentono l'invio e la ricezione dei mes-saggi di posta elettronica tramite SMTP e POP3; nella prossima verranno descritti gli eventi ed i messaggi multipart.


Link e risorse
[1] JavaMail QuickStart - http://www.javaworld.com/javaworld/jw-10-2001/jw-1026-javamail.html
[2] Managing ezines with JavaMail and XSLT, Part 2 - http://www-106.ibm.com/developerworks/xml/library/x-xmlist2/?open&l=842%2Ct=gr%2Cp=JavaMail2
[3] Fundamentals of the JavaMail API short course - http://developer.java.sun.com/developer/onlineTraining/JavaMail/contents.html
[4] JavaMail Homepage - http://java.sun.com/products/javamail/
[5] JavaMail API documentation - http://java.sun.com/products/javamail/javadocs/index.html
[6] JavaBeans Activation Framework - http://java.sun.com/products/javabeans/glasgow/jaf.html

 
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