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 è
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
è 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
|