MokaByte 80 - Dicembre 2003
Java Mail 2003
Gestire la posta elettronica in Java
II parte: eventi e messaggi multipart
di
Massimiliano
Bigatti
Nel corso della precedente puntata sono stati descritti i meccanismi che consentono a JavaMail di inviare e ricevere messaggi di posta; in questa seconda parte verranno descritti gli eventi ed i messaggi multipart, che consentono di gestire allegati binari come immagini o documenti Word

Le API JavaMail implementano anche alcuni eventi; le operazioni sui Folder possono essere infatti controllate da altri oggetti tramite una serie di eventi. Ad esempio, se una parte dell'applicazione rimuove un messaggio, i listener registrati sul folder stesso (e sullo Store da cui il folder deriva), ne vengono notificati. In questo modo è possibile eseguire porzioni di codice arbitrario a fronte di determinati eventi, quali ad esempio la cancellazione dei messaggi. Nella tabella 3 sono riassunti i metodi per l'aggiunta dei listener nelle classi Folder, nella tabella 4 quelli della classe Store.

 


Tabella 3 - Listener di Folder


Tabella 4 - Listener di Store


Quando avviene una modifica al contenuto del folder, viene generato un evento FolderEvent appropriato. Ad esempio, quando viene invocato il metodo delete() su un Folder, viene generato un evento con type = DELETED. La classe FolderEvent dispone infatti dei metodi:

  • getFolder(). Ritorna il folder coinvolto nell'operazione;
  • getNewFolder(). Se l'operazione eseguita è stata una rinominazione, questo metodo ritorna un Folder che rappresenta il nuovo nome;
  • getType(). Ritorna il tipo di operazione, che può essere FolderEvent.CREATED, FolderEvent.DELETED, FolderEvent.RENAMED;

Nel listato 3 è presente un esempio di utilizzo degli eventi; in questo caso viene utilizzato l'evento MessageCountEvent, per individuare l'arrivo di un nuovo messaggio e realizzare così un programma monitor che segnali all'utente che ha nuova posta.
Come si nota osservando il codice, viene fatto uso del metodo addMessageCountListener() presente nella classe Folder, a cui viene passato un nuovo oggetto anonimo derivato da MessageCountAdapter. Questo implementa il metodo messagesAdded(), metodo che indica l'aggiunta di messaggi al Folder; quando questo metodo viene richiamato, viene stampata a console la lunghezza dell'array di messaggi contenuti nell'oggetto evento, di classe MessageCountEvent. Questo array identifica appunto i messaggi coinvolti dall'operazione, che può essere di aggiunta o rimozione, come indicato dal metodo getType(), che può ritornare ADDED o REMOVED.

Listato 3 - Monitor.java
package com.mokabyte.mokabook2.javamail;

import java.util.*;

import javax.mail.*;
import javax.mail.event.*;

public class Monitor {

static final int millisecAttesa = 4500;

public static void main(String args[]) {

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

    Store store = session.getStore("imap");
    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_WRITE);

          folder.addMessageCountListener(new MessageCountAdapter() {
            public void messagesAdded(MessageCountEvent ev) {
              Message[] msgs = ev.getMessages();
              System.out.println("Sono arrivati " + msgs.length +
                                 "nuovi messaggi");
            }
          });

           System.out.println( "Pronto a ricevere messaggi" );
          for(;;) {
            Thread.sleep( millisecAttesa );

            folder.getMessageCount();
            System.out.println( "ping..." );
          }
        } else {
          System.out.println( "Folder non trovato" );
        }
      } else {
        System.out.println( "Folder di default non trovato" );
      }
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}

Per eseguire il codice digitare:

ant monitor

la porzione di script di Ant che esegue il comando è la seguente:

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

Messaggi multipart
In precedenza è stato utilizzato il metodo setText() per impostare il contenuto di un messaggio, ma in realtà questo è un metodo di "convenienza", che effettua alcune operazioni per impostare il tipo di contenuto (MIME) a text/plain. JavaMail supporta infatti messaggi di tipo multipart, composti cioè da contenuti multipli di tipi eterogenei. Un messaggio di posta potrebbe essere composto infatti da un contenuto in testo semplice e da un contenuto HTML; spesso questi messaggi di tipo ricco possono essere visualizzati in un modo o nell'altro in funzione delle capacità dei singoli programmi di client email. Un altro uso dei messaggi multipart è quello legato agli allegati; quando si allega un file di Word o Excel, un file compresso od un'immagine ad un messaggio di posta, il client crea un messaggio multipart creando una parte per ciascun allegato associando alla parte il tipo MIME dell'archivio (p.e. image/gif) ed il contenuto binario del file.
E' in questo momento che entra in gioco il JavaBeans Activation Framework, che offre appunto il supporto richiesto a queste operazioni con dati binari, permettendo addirittura l'invio di oggetti Java serializzati!

 

Invio di allegati
Per inviare un messaggio multipart in JavaMail le operazioni di connessione al provider ed inizializzazione del messaggio sono le medesime di quelle viste nel listato 1; le novità intervengono infatti solo nella composizione del corpo da inviare. E' necessario infatti utilizzare un oggetto Multipart, che consente di memorizzare diverse parti di corpo (oggetti BodyPart) e che fornisce metodi per impostarle e ritornarle; anche in questo caso Multipart è una classe astratta che demanda alle sottoclassi l'effettiva realizzazione di parte dei suoi servizi; per la posta Internet è disponibile la classe MimeMultipart che si basa appunto sulle convenzioni MIME.
La classe MimeMultipart definisce diversi sottotipi, conformi alle specifiche MIME, che possono essere "mixed", "alternative", "related" e così via; il tipo principale è invece "multipart". Questo approccio consente una più agevole corrispondenza nell'Activation Framework, disaccoppiando il processo di fornitura dei gestori delle diverse parti dalle API JavaMail.
Nel listato 4 è presente il codice completo di un programma di prova che esegue un invio di un messaggio con allegato, sviluppato a partire dalla classe Invio presentata nel listato 1. Come si nota osservando il codice, viene per prima cosa istanziato un oggetto MimeMultipart, che ospiterà il corpo del messaggio e subito dopo un oggetto MimeBodyPart (sottoclasse di BodyPart) che fornisce una implementazione MIME di una singola parte. Nell'esempio vengono utilizzati due parti, una per il testo del messaggio ed un'altra per l'allegato Word. Se nel primo caso è sufficiente chiamare il metodo setText() per impostare il contenuto della parte, che è solo testuale, nel secondo caso è necessario utilizzare le classi di Activation Framework per ottenere un blocco dati binario che contenga il documento Word.
In particolare, viene creato un nuovo DataSource a partire dal file Documento.doc che viene letto tramite una istanza di DataHandler:

DataSource source = new FileDataSource( "Documento.doc" );
messageBodyPart = new MimeBodyPart();
messageBodyPart.setDataHandler( new DataHandler(source) );
messageBodyPart.setFileName( "Documento.doc" );

I due oggetti MimeBodyPart vengono poi aggiunti all'oggetto MimeMultipart tramite il metodo addBodyPart().
Per eseguire l'esempio digitare:

ant invioMultipart

la porzione di script Ant invocata è la seguente:

<target name="testInvioMultipart">
<java classname="com.mokabyte.mokabook2.javamail.InvioMultipart" fork="yes">
<classpath refid="project-classpath"/>
<arg value="mail.tin.it" />
</java>
</target>

Si dovrebbe ottenere un risultato simile a quello presente in figura 3.


Figura 3
- Il messaggio multipart è stato ricevuto correttamente

Listato 4 - InvioMultipart.java
package com.mokabyte.mokabook2.javamail;

import java.util.*;

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

public class InvioMultipart {

public static void main( String[] args ) {
try {
Properties props = System.getProperties();
props.put( "mail.smtp.host", args[0] );

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

InternetAddress from = new InternetAddress( "max@bigatti.it" );
InternetAddress to[] = InternetAddress.parse( "max@bigatti.it" );

message.setFrom( from );
message.setRecipients( Message.RecipientType.TO, to );
message.setSubject( "[Mokabyte] - Messaggio multipart di prova" );
message.setSentDate( new Date() );

Multipart multipart = new MimeMultipart();

//crea la parte testuale
BodyPart messageBodyPart1 = new MimeBodyPart();
messageBodyPart1.setText("Invio in allegato un documento Word");

//crea l'allegato Word
DataSource source = new FileDataSource( "Documento.doc" );
BodyPart messageBodyPart2 = new MimeBodyPart();
messageBodyPart2.setDataHandler( new DataHandler(source) );
messageBodyPart2.setFileName( "Documento.doc" );

//aggiunge le parti all'oggetto multipart
multipart.addBodyPart( messageBodyPart1 );
multipart.addBodyPart( messageBodyPart2 );

//imposta come contenuto del messaggio l'oggetto multipart
message.setContent(multipart);

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

Ricezione di allegati
La ricezione di messaggi con allegati passa, in modo similare all'invio, dalle classi Multipart e Part. Nell'esempio riportato nel listato 5, una volta ottenuto il messaggio, viene stampato tramite il metodo stampaMessaggio() che limita la sua elaborazione ai soli messaggi multipart. Tramite il metodo getCount() sull'oggetto Multipart ottenuto con la chiamata Message.getContent() si ottiene il numero di parti di cui è composto l'oggetto; queste vengono estratte una ad una tramite il metodo getBodyPart(). A questo punto viene chiamato il metodo stampaParte() che elabora ciascuna parte; per prima cosa viene ottenuto il tipo MIME, tramite il metodo getContentType(). Se questo è di tipo text/plain, il contenuto è semplice testo, che può essere stampato direttamente a console riga per riga tramite la classe BufferedReader; in caso di allegati binari, viene creato un file su disco tramite la classe FileOutputStream.

Listato 5 - RicezioneMultipart.java
package com.mokabyte.mokabook2.javamail;

import java.io.*;
import java.util.*;

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

public class RicezioneMultipart {

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( "----------------------------------------" );
System.out.println(
"DA:" + from +
" OGGETTO: " + messaggio.getSubject() +
" DATA: " + messaggio.getSentDate() +
"\n"
);

stampaMessaggio( messaggio );
}

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();
}
}

static void stampaMessaggio( Message msg ) throws MessagingException, IOException {
Part msgPart = msg;
Object contenuto = msgPart.getContent();
if( contenuto instanceof Multipart ) {
Multipart mp = (Multipart)contenuto;

for( int i=0; i<mp.getCount(); i++ ) {
stampaParte( mp.getBodyPart(i), i );
}
}
}

static void stampaParte( Part parte, int count ) throws MessagingException, IOException {
String contentType = parte.getContentType();

System.out.println( "Disposizione: " + parte.getDisposition() );
System.out.println( "Testo: " );

if( contentType.startsWith("text/plain") ) {

InputStream in = parte.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader( in )
);

do {
String linea = reader.readLine();
if( linea == null ) {
break;
}

System.out.println( linea );
} while( true );

reader.close();
in.close();

} else {

String filename = parte.getFileName();
if( filename == null ) {
filename = "allegato" + count + ".bin";
}
FileOutputStream writer = new FileOutputStream( filename );

byte[] buffer = new byte[ 4096 ];
InputStream in = parte.getInputStream();

while( true ) {
int readed = in.read( buffer );
if( readed == -1 ) {
break;
}

writer.write( buffer, 0, readed );
}

writer.close();
in.close();

System.out.println("Salvato il file " + filename );
}
}

}


Per provare questo esempio digitare:

ant ricezioneMultipart

la porzione di script di Ant relativo è la seguente:

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

Un esempio di output è il seguente (riformattato):
----------------------------------------
DA:Apple iCards
OGGETTO: Massimiliano Bigatti has sent you an Apple iCard DATA: Thu Oct 30 10:17:28 CET 2003

Disposizione: null
Testo:
Salvato il file allegato0.bin
Disposizione: inline
Testo:
Salvato il file iCard.jpg
Disposizione: null
Testo:
Salvato il file allegato2.bin
----------------------------------------
DA:max@bigatti.it
OGGETTO: [Mokabyte] - Messaggio multipart di prova DATA: Fri Oct 31 21:50:02 CET 2003

Disposizione: null
Testo:
Invio in allegato un documento Word
Disposizione: attachment
Testo:
Salvato il file Documento.doc

 

Conclusioni
Le API JavaMail supportano un completo insieme di funzionalità per la gestione della posta elettronica; oltre all'invio e ricezione di messaggi semplici e multipart, sono presenti funzionalità di gestione della quota dell'utente, per controllare l'occupazione di spazio delle caselle di posta, per la ricerca di messaggi all'interno delle caselle postali e per l'autenticazione dell'utente. Argomenti che verranno trattati nei prossimi numeri.

 

Bibliografia
[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