MokaByte 81 - Gennaio 2004
Java Mail 2003
Gestire la posta elettronica in Java
III parte: eventi e messaggi multipart
di
Massimiliano
Bigatti
La API Javamail forniscono un framework estensibile per trattare con la posta elettronica, di qualunque tipo sia. Queste comprendono anche l'implementazione delle specificità legate alla posta Internet; alcune funzionali-tà a cui siamo abituati quando utilizziamo questo tipo di posta, ad esempio con il nostro programma di posta, possono essere facilmente ottenute attraverso chiamate standard.

Nelle precedenti puntate abbiamo affrontato l'invio e la ricezione di messaggi di posta elettro-nica Internet, sia di tipo normale che multipart (con allegati); nella fattispecie, sono stati pre-sentati esempi per l'invio di messaggi con il protocollo SMTP, per la ricezione di messaggi tramite POP3, e per il controllo delle caselle di posta IMAP.

 

Rispondere è cortesia
Una funzionalità implementata in tutti i client di posta Internet è quella di risposta ad un deter-minato messaggio che è stato ricevuto; la particolarità in questo caso è che il programma di po-sta inserisce in automatico la stringa "Re:", come prefisso dell'oggetto del messaggio. Questo prefisso è importante, perché alcuni programmi di posta lo utilizzano per collegare messaggi correlati tra di loro, ad esempio per presentarli sotto forma di Thread (figura 1).


Figura 1
- Messaggi con oggetto comune possono essere raccolti in thread
(clicca sull'immagine per ingrandire)

Alcuni client di posta localizzati utilizzano invece "Rif:", ma questo impedisce il corretto funzionamento di questi meccanismi automatici.
Per rispondere ad un messaggio utilizzare il metodo reply() della classe MimeMessage (questo metodo è dunque presente solo nella sottoclasse di Message). Ad esempio:

MimeMessage risposta = (MimeMessage)message.reply(false);
risposta.setFrom(new InternetAddress("mbigatti@mokabyte.com"));
risposta.setText("Grazie per la documentazione");
Transport.send(reply);

il parametro booleano del metodo reply() indica, se impostato a true, di rispondere a tutti, con-trariamente di rispondere al solo mittente. Il messaggio viene marcato come "risposto" (si veda più avanti).
Per impostare i destinatari della risposta, è possibile utilizzare il metodo setReplyTo(), che si aspetta un array di Address e che sovrascrive i destinatari del messaggio. Ad esempio per ri-spodere a due destinatari, si può scrivere:

Address[] destinatari = new Address[ 2 ];
destinatari[ 0 ] = new InternetAddress("mbigatti@mokabyte.com");
destinatari[ 1 ] = new InternetAddress("gpuliti@mokabyte.com");

messaggio.setReplyTo( destinatari );

Se viene passato null, viene rimosso qualsiasi destinatario sia presente. Quando si risponde ad un messaggio, il destinatario della risposta viene ottenuto con il metodo getReplyTo(), che cor-risponde al campo "Reply-To" dell'intestazione del messaggio di posta. Il valore ritornato da questo metodo può essere l'array impostato con setReplyTo(), oppure il risultato della chiamata a getFrom(), nel caso il campo "Reply-To" sia assente o non abbia valore.

Si noti che un messaggio costruito in questo modo, non ha contenuto; non dispone infatti del testo "quotato" copiato dal messaggio ricevuto, come invece solitamente viene fatto dai client di posta. Per ovviare a questo si può eseguire una copia del testo ricevuto con una semplice chiamata, ma si consideri che questa operazione è valida solo per messaggi non multipart:

String risposta = "Ho ricevuto il tuo messaggio:\n";
risposta.setText( risposta + message.getContent() );

Si noti, ad ogni modo, che quotare tutto il messaggio ricevuto è considerato un comportamento maleducato dalla netiquette. Per copiare un messaggio multipart si veda il paragrafo successivo.

 

Inoltrare i messaggi
La funzione gemella della risposta è l'inoltro, l'operazione per cui un messaggio di posta viene inviato ad un altro destinatario, copiando l'intero contenuto del messaggio, compresi eventuali allegati. Non esiste in Javamail un singolo metodo che realizza l'inoltro, ma è possibile esegui-re una copia del messaggio utilizzando la classe DataHandler dell'Activation Framework. Come si ricorderà infatti dalle puntate precedenti, un singolo messaggio di posta può essere costituito, se di tipo multipart, da un insieme di più parti, ciascuna dedicata a contenere il testo e tutti gli allegati (figura 2).


Figura 2
- Un messaggio multipart è costituito da molte parti

Ciascuna parte di un messaggio è rappresentata da un oggetto MimeBodyPart, che verranno poi associate allo stesso oggetto MimeMultiPart. Per creare un messaggio inoltrato è possibile creare una prima parte testuale, ed una seconda parte binaria:

BodyPart messageBodyPart1 = new MimeBodyPart();
messageBodyPart1.setText( "Messaggio inoltrato" );

BodyPart messageBodyPart2 = new MimeBodyPart();
messageBodyPart2.setDataHandler(message.getDataHandler());

A questo punto è possibile aggregare le due parti e creare un unico oggetto Multipart:

Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart1);
multipart.addBodyPart(messageBodyPart2);

Come il messaggio di risposta possiede la dicitura "Re:" nell'oggetto, quello inoltrato solita-mente viene arricchito con la stringa "Fwd:", dunque l'oggetto del messaggio dovrà riportare questa informazione. Una volta costruito l'oggetto multipart da spedire, è possibile creare il messaggio ed inoltrarlo:

Message inoltro = new MimeMessage(session);
inoltro.setSubject("Fwd: " + message.getSubject());
//… imposta mittente e destinatari
inoltro.setContent(multipart);
Transport.send(inoltro);

Per duplicare semplicemente un messaggio MIME, è possibile utilizzare un costruttore della classe MimeMessage:

MimeMessage nuovo = new MimeMessage(vecchio);

la copia è crea un oggetto completamente indipendente dall'originale, ma la documentazione precisa che allo stato delle cose, il messaggio originale viene duplicato in modo un po' ineffi-ciente, eseguendo la copia in modo non stringente.

 

Messaggi HTML
Un'altra funzionalità spesso supportata dai programmi di posta elettronica è l'invio dei mes-saggi in formato HTML, che consentono di formattare il messaggio in modo graficamente più accattivante, eventualmente includendo immagini di supporto, inviate come allegati, che arric-chiscono il messaggio inviato.
Per indicare che un messaggio è in formato HTML è sufficiente impostare il contenuto del messaggio passando il tipo MIME, tramite il metodo MimeMessage.setContent():

String html = "<html><body>Questo &egrave; un messaggio <b>html</b></body></html>";
messaggio.setContent( html, "text/html" );

Si noti che questa versione del metodo setContent() non è altro che una versione di convenien-za che utilizza un oggetto DataHandler per incapsulare l'oggetto passato come primo parame-tro (che nella firma del metodo è di tipo Object). Ovviamente è possibile specificare come tipo MIME solo un formato supportato dall'Activation Framework, come nel caso di "text/html". Internamente viene poi chiamato il metodo:
public void setContent(Multipart mp)

In questo modo, però, è possibile inviare solo documenti HTML che utilizzano immagini e-sterne (che contengono cioè istruzioni come <img src=" http://www.mokabyte.it/images/titolo.gif" />), richiedendo una connessione ad Internet attiva per visualizzare correttamente tutto il messaggio.
Se le dimensioni delle immagini non sono eccessive, è possibile inviarle come allegati, facendo riferimento, all'interno del documento HTML utilizzato, alle singole immagini tramite il valore di Content-ID specificato per ciascuna di queste (vedi figura 3).


Figura 3
- Ogni allegato dispone di un id di riferimento univoco

All'interno del blocco HTML è possibile referenziare gli elementi voluti utilizzando il prefisso "cid:". Ad esempio, è possibile scrivere: <img src="cid:figura1_id">. Nel listato 1 è presente il codice completo di un programma che invia un messaggio HTML con immagini allegate. Semplicemente, viene creato un messaggio multipart con due sezioni: la prima, di tipo HTML, contiene il documento che implementa il testo del messaggio. Per impostare il tipo viene utiliz-zata la chiamata:

messageBodyPart1.setContent( html, "text/html" );

La seconda parte del messaggio, è una immagine GIF, caricata in memoria tramite Activation Framework, su cui viene impostato l'attributo Content-ID:

messageBodyPart2.setHeader("Content-ID","<foto_logo>");

Il messaggio così inviato, ha l'aspetto presentato in figura 4.


Figura 4
- Il client di posta gestisce il messaggio HTML

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

import java.util.*;

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

public class InvioHTML {

  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 HTML" );
      message.setSentDate( new Date() );

      String html = "<h1>Messaggio da Mokabyte</h1>" +
                    "<img src=\"cid:foto_logo\"><br/>" +
                    "Mokabyte &egrave; felice di inviarti questo divertente " +
                    "messaggio HTML attraverso le API Javamail";

      //crea la parte testuale
      BodyPart messageBodyPart1 = new MimeBodyPart();
      messageBodyPart1.setContent( html, "text/html" );

      //allega l'immagine
      DataSource source = new FileDataSource( "foto_logo.gif" );
      BodyPart messageBodyPart2 = new MimeBodyPart();
      messageBodyPart2.setDataHandler( new DataHandler(source) );
      //messageBodyPart2.setFileName( "foto_logo.gif" );
      messageBodyPart2.setHeader("Content-ID","<foto_logo>");
      messageBodyPart2.setDisposition( Part.ATTACHMENT );

      //aggiunge le parti all'oggetto multipart
      Multipart multipart = new MimeMultipart();
      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();
    }
  }
}

Un allegato ad un messaggio può essere presentato in due modi diversi all'interno del pro-gramma di posta, in funzione del parametro Content-Disposition impostato per la relativa par-te. Come si vede dal listato 1, l'immagine allegata è impostata come Part.ATTACHMENT, tramite la chiamata:

messageBodyPart2.setDisposition( Part.ATTACHMENT );

questa, indica al client di posta che il messaggio non deve essere presentato all'utente, ma che questo lo deve aprire esplicitamente per vederlo.
La costante Part.INLINE, invece, indica al client di posta di visualizzare l'allegato direttamen-te nel corpo del messaggio.

 

Invio a più destinatari
Quando si crea un messaggio in uscita, sono disponibili alcune opzioni per impostarne i desti-natari. Tramite il metodo Message.setRecipient(), è possibile indicare il tipo e le caselel di po-sta a cui inviare. Il primo parametro di questo metodo è di tipo Message.RecipientType, classe che definisce alcune costanti simboliche:

  • BCC - Blind Carbon Copy (copia carbone nascosta);
  • CC - Carbon Copy;
  • TO;

il secondo parametro è un oggetto Address. Se si desidera passare più indirizzi, è possibile uti-lizzare il metodo setRecipients() che si aspetta, come secondo parametro, un array di Address.
La classe astratta Address possiede due sottoclassi concrete: InternetAddress e NewsAddress. La prima indirizza a caselle di posta internet, mentre la seconda può essere utilizzata per invia-re ai newsgroup.
Per inviare a più destinatari si può dunque scrivere:

messaggio.setRecipients(RecipientType.TO,
                        new InternetAddress("mbigatti@mokabyte.com"));
messaggio.setRecipients(RecipientType.CC,
                        new InternetAddress("gpuliti@mokabyte.com"));


Dalla versione 1.3, Javamail supporta i gruppi, e cioè elenchi di nominativi separati da virgole, come ad esempio: mbigatti@mokabyte.com, Giovanni gpuliti@mokabyte.com, "Andrea Gini" agini@mokabyte.com.
Questo supporto è implementato nella classe InternetAddress; creando un oggetto in questo modo:

Address destinatario = new InternetAddress(
"mbigatti@mokabyte.com, Giovanni gpuliti@mokabyte.com, \"Andrea Gini\" agini@mokabyte.com"
);

la classe richiama il metodo parse(), che si occupa di interpretare la stringa fornita ed estrarre i singoli destinatari.
Per capire se un oggetto InternetAddress rappresenta un gruppo di nominativi, è possibile in-vocare il metodo isGroup(). Per ottenere l'elenco dei singoli nominativi, è invece disponibile il metodo getGroup() che si aspetta un parametro booleano che indica se eseguire una decodifica stringente secondo lo standard RFC 822 o meno. Se l'oggetto non rappresenta un gruppo, getGroup() ritorna null.

 

Autenticazione
Le API Javamail supportano anche la possibilità di autenticare la connessione al server di posta con un meccanismo a richiesta: invece che fornire l'utente e la password in fase di connessio-ne, si fornisce un oggetto che eseguirà il recupero di questi dati solo quando necessario. Questa funzionalità è solitamente utilizzata nei programmi di posta, e si palesa nella presentazione di una finestra di richiesta utente e password.
Il package javax.mail implementa la classe astratta Authenticator, differente da quella presente in java.net, che definisce una serie di metodi per ottenere la porta, il protocollo, il sito richie-dente ed altro ancora. Lo sviluppatore deve quindi implementare questi metodi, in modo da fornire alle API Javamail le informazioni necessarie all'autenticazione. Tra questi, quello più importante è getPasswordAuthentication(), che ritorna un oggetto PasswordAuthentication; quest'ultimo, a sua volta, dispone dei metodi getPassword() e getUserName() che dovranno ritornare i dati di autenticazione dell'utente che si intende connettere al sistema. L'oggetto Authenticator così creato andrà registrato alla sessione Session in uso; si noti che se non viene implementato un metodo tra quelli previsti nella classe, questa dispone di una implementazione che per default fallisce.
Si osservi l'esempio seguente. Per prima cosa viene impostato l'indirizzo del server POP3 nelle proprietà di connessione, in quanto si utilizzerà il metodo connect() della Session che non pre-vede parametri. Nel momento dell'ottenimento della sessione, si passerà una sottoclasse con-creta - qui con classe anonima - di Autenthicator, che in questo caso non fa altro che presenta-re due finestre per richiedere all'utente il nome e la password (figura 5).

Properties props = System.getProperties();
props.put("mail.pop3.host", args[0]);

Authenticator auth = new Authenticator() {
  protected PasswordAuthentication getPasswordAuthentication() {
  String user = JOptionPane.showInputDialog(null,
                                            "Digita il nome utente",
                                            "Autenticazione 1",
                                            JOptionPane.QUESTION_MESSAGE
                                            );
    String password = null;

    if( user != null ) {
        password = JOptionPane.showInputDialog(null,
                                                                                       "Digita la password",
                                                                                       "Autenticazione 2",
                                                                                       JOptionPane.QUESTION_MESSAGE);
  }

    return new PasswordAuthentication( user, password );
   }
};
Session session = Session.getDefaultInstance(props, auth);

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


Figura 5
- Le due finestre presentate da queste semplice meccanismo di autenticazione

 
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