Szymon Janus

JavaMail

Wprowadzenie

JavaMail to zbiór klas służących do czytania, tworzenia i wysyłania listów elektronicznych. Klasy można podzielić na dwie grupy:

Jak funkcjonuje poczta elektroniczna?

MIME

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.

SMTP

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.

POP i IMAP

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.

Rozszerzenia i ...

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.

Podstawowe klasy

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 store = session.getStore("pop3");
lub
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).

Wiadomości wieloczęściowe

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

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

Pobieranie

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.

Instalacja

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.

Podsumowanie

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.

Uwaga

Tekst ten jest bardzo mocno inspirowany tutorialem do JavaMail znalezionym na stronach firmy SUN autorstwa Johna Zukowskiego.