JavaMail to zbiór klas służących do czytania, tworzenia i wysyłania listów elektronicznych. Klasy można podzielić na dwie grupy:
Listy przesyłane są jako zwykły tekst. Po to by móc przesyłać dane binarne i/lub wiadomości wieloczęściowe (np. z załącznikami) stworzono standard MIME (Multipurpose Internet Mail Extensions). Określa on sposób kodowania danych nietekstowych i opisywania poszczególnych części wiadomości. JavaMail pozwala budować wieloczęściowe wiadomości zgodne z MIME.
Do przesyłania listów służy protokół SMTP (Simple Mail Transfer Protocol). Jest on bardzo prosty, ale JavaMail służy właśnie do tego, by programista nie zajmował się szczegółami protokołów.
Do pobierania wiadomości z serwera służą przede wszystkim dwa protokoły: POP (Post Office Protocol) i IMAP (Internet Message Access Protocol).
POP jest protokołem popularniejszym, ale mającym dość skromne możliwości. JavaMail nie dostarcza mechanizmów obchodzących ograniczenia protokołów, więc programista musi sobie radzić sam. Przede wszystkich chodzi tu o określanie, które wiadomości zostały już wcześniej odczytane, a które są nowe. W tym przypadku programista musi zapewnić np. zapamiętywanie tego, co zostało już kiedyś przeczytane.
IMAP jest protokołem o znacznie szerszych możliwościach. Umożliwia m.in. tworzenie folderów na serwerze i znakowanie wiadomości. Powoduje jednak duże obciążenie łącz i zapychanie dysków. W związku z tym nie jest tak popularny jak POP. JavaMail pozwal wykorzystać wszystkie możliwości IMAP'a.
Poszczególne protokoły są obsługiwane przez tzw. dostawców (ang.providers), czyli odpowiednie, specyficzne klasy. Implementacje wszystkich podstawowych protokołów dostarczane są przez SUN'a. Istnieją także klasy realizujące inne protokoły, np. NNTP (Network News Transport Protocol). Z punktu widzenia programisty używającego JavaMail są one niemal identyczne. Różnią się jedynie zakresem możliwości, a API pozostaje niezmienne.
Session
Obiekty tej klasy są wykorzystywane w praktycznie wszystkich operacjach, które wykonujemy przy pomocy JavaMail. Pobierać można je na dwa sposoby:
Session session = Session.getDefaultInstance(props, null); |
Session session = Session.getInstance(props, null); |
W obu przypadkach pierwszy z argumentów jest klasy java.util.Properties
, z którego
pobiera wszystkie potrzebne informacje, takie jak nazwy serwerów, identyfikatory użytkownika czy hasła.
Informacje te będą współdzielone przez całą aplikację. Oczywiście istnieje możliwość podawania tych
parametrów w dalszej części kodu.
Drugi z parametrów jest klasy Authenticator
, której opis znajduje się w dalszej
części.
Message
Jest to klasa abstrakcyjna, najczęściej wykorzystywaną jej podklasą jest javax.mail.internet.MimeMessage
.
Jak sama nazwa wskazuje, obiekty tej klasy reprezentują wiadomości zakodowane zgodnie ze stadardem
MIME.
Konstruktor przyjmuje jako parametr sesję:
MimeMessage message = new MimeMessage(session); |
Na tak stworzonej wiadomości możemy wykonywać dość oczywiste operacje, takie jak: setContent(...)
,
setText(...)
, setSubject(...)
. W przypadku tworzenia wiadomości
wieloczęściowych postępowanie jest nieco bardziej złożone, aczkolwiek równie intuicyjne.
Address
Podobnie jak poprzednio jest to klasa abstrakcyjna. Interesującą nas jej podklasą jest
javax.mail.internet.InternetAddress
. Znaczenie obiektów tej klasy jest chyba oczywiste.
Konstrukcja obiektów jest dość prosta:
Address address= new InternetAddress("president@whitehouse.gov","George Bush"); |
Tak przgotowany adres możemy dodać do wiadomości jako pole "From:", czyli adres nadawcy:
message.setFrom(address); |
Jako parametr możne też podać tablicę ;)
W normalnym liście elektronicznym możemy podać adresatów trzech różnych typów: "To:", "Cc:" i "Bcc:".
Metody obiektów klasy Message
pozwalają podać adresy wszystkich
trzech typów:
message.addRecipient(Message.RecipientType.TO,toAddress); message.addRecipient(Message.RecipientType.CC,ccAddress); message.addRecipient(Message.RecipientType.BCC,bccAddress); |
Authenticator
Obiekty podklas tej klasy mogą być podawane jako parametry sesji. Mają one implementować metodę
getPasswordAuthentication()
zwracającą obiekty klasy PasswordAuthentication
.
Cały ten mechanizm ma pozwalać programiście na określanie interfejsu zapytań o hasło. Zasada działania
jest analogiczna jak funkcje konwersacji w PAM'ie (laboratorium z Systemów Operacyjnych!).
Transport
Klasą odpowiedzialną za właściwe wysyłanie wiadomości jest klasa abstrakcyjna Transport
.
Można jej używać na dwa sposoby:
Transport.send(message); |
Transport transport = session.getTransport("smtp"); transport.connect(host, username, password); transport.sendMessage(message, message.getAllRecipients()); transport.close(); |
Drugi ze sposobów daje nam większą kontrolę nad procesem wysyłania wiadomości. Mechanizm ten pozwala
np. na wysłanie kilku listów w ciągu jednego połączenia (Transport.send(...)
używa oddzielnych
połączeń).
Store
i Folder
O ile wysyłanie wiadomości jest stosunkowo proste, to ich pobieranie jest już dość skomplikowane. Wynika to przede wszystkim z możliwości protokołów. O ile patrząc "z perspektywy" POP'a API wydaje się zbyt rozbudowane, to dla IMAP'a jest idealne. By zapewnić swoistą przeźroczystość, niezależność, sprowadzono wszystko do "wspólnego mianownika".
Pobieranie wiadomości za pomocą API JavaMail jest podzielone na kilka etapów.
Store
- skrzynkę pocztową:
Store store = session.getStore("pop3"); |
Store store = session.getStore("imap"); |
store.connect(host,username,password); |
Folder folder = store.getFolder("INBOX"); |
Jeżeli używamy protokołu POP, "INBOX" jest jedyną poprawną nazwą folderu. Ten mechanizm pozwala na pełne wykorzystanie możliwości IMAP'a.
folder.open(Folder.READ_WRITE); |
Message message[] = folder.getMessages(); |
Powyższa operacja nie musi oznaczać fizycznego ściągnięcia całych wiadomości z serwera. Implementacje
SUN'a ściągają je dopiero, gdy te są naprawdę potrzebne, np. chcemy zobaczyć zawartość (getContent()
),
czy wypisać na strumień (writeTo(...)
).
message.setFlag(Flags.Flag.DELETED, true); |
Zdefiniowane jest w sumie 7 różnych flag (ANSWERED, DELETED, DRAFT, FLAGGED, RECENT, SEEN, USER). To, z któych
z nich możemy korzystać, zależy od protokołu. Aby się dowiedzieć możemy się posłużyć funkcją
getPermanentFlags()
folderu. Dla POP'a możemy korzystać jedynie z DELETED.
folder.close(aBoolean); store.close(); |
Zmienna typu boolean
podawana jako parametr zamknięcia folderu określa czy folderze mają
zostać dokonane zmiany (np. usunięte wiadomości).
Powyższy opis klas powinnien wystarczyć do napisania prostego klienta pocztowego. Do pełnej funkcjonalności brakuje już tylko obsługi wiadomości wieloczęściowych, czyli z załącznikami. Do ich obsługi wykorzystywany jest mechanizm MIME.
Do przetwarzania wiadomości z załącznikami służą obiekty klasy MimeMultipart
przechowujące
w sobie obiekty implementujące interfejs Part
. W tym miejscu wykorzystywane są także klasy
należące do JavaBeans Activation Framework pozwalające na obsługę danych w różnych formatach.
Wysyłanie wiadomości z załącznikami jest stosunkowo proste:
// Definiujemy wiadomość Message message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.addRecipient(Message.RecipientType.TO,new InternetAddress(to)); message.setSubject("Hello World!"); // Tworzymy jedną część BodyPart messageBodyPart = new MimeBodyPart(); // Wypełniamy tę część (domyślnie typ "text/plain") messageBodyPart.setText("hello, i'm jan b."); Multipart multipart = new MimeMultipart(); multipart.addBodyPart(messageBodyPart); // A teraz druga część załącznik messageBodyPart = new MimeBodyPart(); DataSource source = new FileDataSource(filename); messageBodyPart.setDataHandler(new DataHandler(source)); messageBodyPart.setFileName(filename); multipart.addBodyPart(messageBodyPart); // Wkładamy to do wiadomości message.setContent(multipart); // I wysyłamy Transport.send(message); |
Mając obiekt klasy Message
nie wiemy jaka jest jego struktura. Oglądamy go więc po kawałku:
Multipart mp = (Multipart)message.getContent(); // Iterujemy po zawartości listu for (int i=0, n=multipart.getCount(); i<n; i++) { Part part = multipart.getBodyPart(i)); String disposition = part.getDisposition(); if ((disposition != null) && ((disposition.equals(Part.ATTACHMENT) || (disposition.equals(Part.INLINE))) { saveFile(part.getFileName(), part.getInputStream()); } } |
Po zrzutowaniu części na MimeBodyPart
możemy za pomocą funkcji isMimeType(nazwa)
pytać o typ jej zawartości. Pozwala to na różne traktowanie danych różnych typów.
JavaMail jest częścią J2EE więc nie wymaga dodatkowej instalacji (jeżeli dysponujemy J2EE oczywiście).
Jeżeli natomiast nie dysponujemy J2EE musimy ściągnąć i zainstalować implementację JavaMail, providera POP3 (wymagane dla wersji 1.1.3, nie wymagane dla 1.2) i JavaBeans Activation Framework.
JavaMail jest API pozwalającym na korzystanie z poczty elektronicznej w aplikacjach napisanych w Javie. Za jego uniwerslaność płacimy cenę w postaci skomplikowania niektórych operacji. Wydaje się jednak, że samodzielne implementowanie protokołów pocztowych byłoby znacznie bardziej pracochłonne.
Tekst ten jest bardzo mocno inspirowany tutorialem do JavaMail znalezionym na stronach firmy SUN autorstwa Johna Zukowskiego.