Folders
So far, we've worked mostly with the INBOX folder. This is the default folder in which most mail resides until the user filters or saves it into some other folder. On some systems, it may actually reside in a file called INBOX. On other systems, it may be called something different. Nonetheless, you can always access it from the JavaMail API using the name INBOX. Most mail programs allow you to organize your messages into different folders. These folders are hierarchical; that is, one folder may contain another folder. In particular, in the IMAP protocol, servers store the messages in different folders from which clients retrieve and manipulate the messages as necessary. POP servers, by contrast, generally send all the messages to the user when the user connects and rely on the client to store and manage them. The primary advantage of the IMAP approach over POP is that it allows users to easily access their entire email archives from multiple client machines. The JavaMail API represents IMAP-like folders as instances of the abstract Folder
class:
public abstract class Folder extends Object
This Class declares methods for requesting named folders from servers, deleting messages from folders, searching for particular messages in folders, listing the messages in a folder, and so forth. Most of these methods are declared abstract. When you ask a session, a store, or a folder to give you one of the folders it contains, it will give you an instance of a concrete subclass appropriate for the protocol in use: IMAP, POP, mbox, or whatever. The reference implementation of the JavaMail API knows how to do these operations only for IMAP servers. However, some third-party implementations provide these operations in local mailbox folders stored on the client's filesystem as well.
You cannot create folders directly. The only constructor is protected:
Instead, you get a There are actually three These methods share an annoying idiosyncrasy with the When you first get a folder, it's closed. Before you can read the messages it contains, you have to open the folder using the The Generally, trying to do something with a closed folder that requires the folder to be open or vice versa will throw a If the The The The The type of the folder should be one of the named constants If there are messages in this folder, they are deleted along with the folder. If the folder contains subfolders, the subfolders are deleted if the On occasion, you may find a need to put a message in a folder. There's only one method to do this, As the name implies, the messages in the array are placed at the end of this folder. The The copied messages are appended to the destination folder. They are not removed from the source folder. To move a message, you have to copy it from the source to the destination, delete it from the source folder, and finally expunge the source folder. To delete a message from a folder, set its After a message has been expunged, there may still be Some implementations (though not the default IMAP implementation) allow you to subscribe to particular folders. This would be most appropriate for an NNTP provider, where a typical server offers thousands of newsgroups, but the typical user will want to retrieve messages from a few dozen of these, at most. Each newsgroup would be represented as a If a provider doesn't support subscription, Folders are hierarchical. That is, a folder can contain other folders. There are four methods to list the folders that a folder contains:
The first method returns an array listing the folders that this folder contains. The second method returns an array listing all the subscribed folders that this folder contains. The third and fourth methods repeat these first two, except they allow you to specify a pattern. Only folders whose full names match the pattern will be in the returned array. The pattern is a string giving the name of the folders that match. However, the string can contain the % character, which is a wildcard that matches any sequence of characters not including the hierarchy separator, and *, which matches any sequence of characters including the hierarchy separator. The This method can be invoked on an open or closed folder. However, in the case of a closed folder, this method may (or may not) return -1 to indicate that the exact number of messages isn't easily available. The The Unlike Like The The The For example, suppose you wanted to download just the subjects, the To: addresses, and the content information of a block of messages. Fetch them like this:
If the server supports searching (as many IMAP servers do and most POP servers don't), it's easy to search a folder for the messages meeting certain criteria. The criteria are encoded in The It also provides several classes for combining searches:
And of course, you can write your own subclasses that implement your own search logic. To implement a search, write a subclass and override the subclass's This method returns A This search term looks for all messages from billg@bugmenot.com after 2003:
Example 19-13 is a simple variation of the Only messages from the specified users will be returned. However, if no email addresses are given, all messages will be returned.
It's sometimes useful to be able to change the flags for an entire group of messages at once. The Ultimately, these are just conveniences. There's nothing you can do with these methods that you can't do by setting the flags on each message individually with the Many email programs can be configured to periodically check for incoming email in the background. One way to structure an email program is as a series of responses to unpredictable events. This is much like coding for a graphical user interface, and indeed the JavaMail API uses the same basic patterns to handle mail events that the AWT and Swing use to handle GUI events. The JavaMail API defines six different kinds of mail events, all in the The six concrete kinds of mail events, the first four of which involve folders, are:
A A The message's envelope or flags have changed.
A message was added to or deleted from a A notification or alert from a A notification from a There are six listener interfaces corresponding to the six kinds of events:
Each of these interfaces declares one or more methods that must be provided by implementing classes. For example, the The Four of these events can be fired by folders. Consequently, there are 14 The Finally, for completeness's sake, I'll note that the Neither of these is especially important to the client programmer.
Opening Folders
protected Folder(Store store)
Folder
from a Session
, a Store
, or another Folder
like this:
Folder outbox = container.getFolder("sent-mail");
getFolder()
methods, one each in the Session
, Store
, and Folder
classes. They all have the same signature and behave similarly:
public abstract Folder getFolder(String name) throws MessagingException
File
class. Getting a Folder
object doesn't imply that the named Folder
actually exists on the server. To tell whether the folder is really present, you have to test for it with the exists( )
method:
public boolean exists( ) throws MessagingException
open( )
method:
public abstract void open(int mode) throws FolderNotFoundException, MessagingException
mode
argument should be one of the two named constants Folder.READ_ONLY
or Folder.READ_WRITE
. Some but not all implementations allow you to open multiple read-only connections to one real folder using multiple Folder
objects. However, all implementations allow at most one Folder
object to have write access to a folder at one time. Some operations discussed in this section, such as searching or retrieving messages from a folder, can only be performed on an open folder. Others, such as deleting or changing the name of a folder, can only be done to a closed folder. The isOpen()
method returns true
if the folder is open, false
if it's closed:
public abstract boolean isOpen( )
java.lang.IllegalStateException
. This is a runtime exception, so it doesn't need to be explicitly caught or declared. When you're done with a folder, close it using the close( )
method:
public abstract void close(boolean expunge) throws FolderNotFoundException, MessagingException
expunge
argument is true
, any deleted messages in the folder are deleted from the actual file on the server. Otherwise, they're simply marked as deleted, but the message can still be undeleted.Basic Folder Info
Folder
class has eight methods that return basic information about a folder:
public abstract String getName( )
public abstract String getFullName( )
public URLName getURLName( ) throws MessagingException public abstract Folder getParent( ) throws MessagingException public abstract int getType( ) throws MessagingException public int getMode( ) throws IllegalStateException public Store getStore( )
public abstract char getSeparator( ) throws FolderNotFoundException, MessagingException
getName( )
method returns the name of the folder, such as "Reader Mail", whereas the getFullName( )
method returns the complete hierarchical name from the root, such as "books/JNP3E/Reader Mail". The getURLName( )
method includes the server; for instance, "imap://elharo@mail.metalab.unc.edu/books/JNP3E/Reader Mail". In this example, the slash character is the separator between nested folders. The separator can vary from implementation to implementation, but the getSeparator()
method always tells you what it is. The getParent( )
method returns the name of the folder that contains this folder; e.g., "JNP3E" for the previous Reader Mail example. The getType( )
method returns an int
indicating whether the folder can contain messages and/or other folders. If it can contain messages but not folders, getType( )
returns the named constant Folder.HOLDS_MESSAGES
. If it can contain folders but not messages, getType( )
returns the named constant Folder.HOLDS_FOLDERS
. If it can contain both folders and messages, getType()
returns the bitwise union Folder.HOLDS_FOLDERS & Folder.HOLDS _MESSAGES
. The getMode( )
method tells you whether a folder allows writing. It returns one of the two named constants (Folder.READ_ONLY
or Folder.READ_WRITE
) or -1 if the mode is unknown. Finally, the getStore()
method returns the Store
object from which this folder was retrieved.Managing Folders
create( )
method creates a new folder in this folder's Store
:
public abstract boolean create(int type) throws MessagingException
Folder.HOLDS_MESSAGES
or Folder.HOLDS_FOLDERS
, depending on whether it will hold other folders or messages. It returns true
if the creation succeeded, false
if it didn't. The delete( )
method deletes this folder, but only if the folder is closed. Otherwise, it throws an IllegalStateException
:
public abstract boolean delete(boolean recurse) throws IllegalStateException, FolderNotFoundException, MessagingException
recurse
argument is true
. If the recurse
argument is not true
, the folder will only be deleted if it does not contain any subfolders. If it does contain subfolders, the delete fails. If the folder does contain subfolders and also contains messages, it's implementation-dependent whether the messages will be deleted even though the folder itself isn't. If the delete succeeds, the method returns true
; otherwise, it returns false
. The renameTo( )
method changes the name of this folder. A folder must be closed to be renamed. Otherwise, an IllegalStateException
is thrown. This method returns true
if the folder is successfully renamed, false
if it isn't:
public abstract boolean renameTo(Folder f) throws IllegalStateException, FolderNotFoundException, MessagingException
Managing Messages in Folders
appendMessages()
:
public abstract void appendMessages(Message[] messages) throws FolderNotFoundException, MessagingException
copyMessages()
method copies messages into this folder from a specified folder given as an argument:
public void copyMessages(Message[] messages, Folder destination) throws IllegalStateException, FolderNotFoundException, MessagingException
Flags.Flag.DELETED
flag to true
. To physically remove deleted messages from a folder, you have to call its expunge( )
method:
public abstract Message[] expunge( ) throws MessagingException, IllegalStateException, FolderNotFoundException
Message
objects that refer to it. In this case, almost any method call on such an object, except isExpunged( )
and getMessageNumber( )
, will throw an exception.Subscriptions
Folder
object. A subscription to the newsgroup's Folder
indicates that the user wants to retrieve messages from that newsgroup:
public boolean isSubscribed( )
public void setSubscribed(boolean subscribe) throws FolderNotFoundException, MethodNotSupportedException, MessagingException
setSubscribed( )
throws a MethodNotSupportedException
and isSubscribed( )
returns false
.Listing the Contents of a Folder
public Folder[] list( )
throws FolderNotFoundException, MessagingException public Folder[] listSubscribed( )
throws FolderNotFoundException, MessagingException public abstract Folder[] list(String pattern)
throws FolderNotFoundException, MessagingException public Folder[] listSubscribed(String pattern) throws FolderNotFoundException, MessagingException
Checking for Mail
getMessageCount()
method returns the number of messages in this folder:
public abstract int getMessageCount( )
throws FolderNotFoundException, MessagingException
hasNewMessages()
method returns true
if new messages have been added to the folder since it was last opened (not since the last time you checked!):
public abstract boolean hasNewMessages( ) throws FolderNotFoundException, MessagingException
getNewMessageCount()
method uses a slightly different approach for determining how many new messages there are. It checks the number of messages in the folder whose RECENT flag is set:
public int getNewMessageCount( )
throws FolderNotFoundException, MessagingException
hasNewMessages( )
, getNewMessageCount( )
can be invoked on either an open or a closed folder. However, in the case of a closed folder, getNewMessageCount( )
may return -1 to indicate that the real answer would be too expensive to obtain. The getUnreadMessageCount()
method is similar but returns the number of messages in the folder that do not have a SEEN flag set:
public int getUnreadMessageCount( )
throws FolderNotFoundException, MessagingException
getNewMessageCount( )
, getUnreadMessageCount( )
can be invoked on either an open or a closed folder. However, in the case of a closed folder, it may return -1 to indicate that the real answer would be too expensive to obtain.Getting Messages from Folders
Folder
class provides four methods for retrieving messages from open folders:
public abstract Message getMessage(int messageNumber) throws IndexOutOfBoundsException, FolderNotFoundException, IllegalStateException, MessagingException public Message[] getMessages( ) throws FolderNotFoundException, IllegalStateException, MessagingException public Message[] getMessages(int start, int end) throws IndexOutOfBoundsException, FolderNotFoundException, IllegalStateException, MessagingException public Message[] getMessages(int[] messageNumbers) throws IndexOutOfBoundsException, FolderNotFoundException, IllegalStateException, MessagingException
getMessage( )
method returns the nth message in the folder. The first message in the folder is number 1 (not 0). Message numbers may change when messages are expunged from the folder. An IndexOutOfBoundsException
is thrown if you ask for message n and there are n - 1 or fewer messages in the folder. The first getMessages()
method returns an array of Message
objects representing all the messages in this folder. The second getMessages( )
method returns an array of Message
objects from the folder, beginning with start
and finishing with end
, inclusive. The third getMessages()
method returns an array containing only those messages specifically identified by number in the messageNumbers
array. All four of these methods only create the Message
objects and fill in the minimal number of fields in those objects. The actual text and other content of the message will only be fetched from the server when the Message
's methods that use those things are invoked. This means, for example, that you can't get all the messages from the server, then hang up your PPP connection and work with them offline. There is, however, a fetch( )
method, which fills in certain parts of the Message
objects with actual data from the server:
public void fetch(Message[] messages, FetchProfile fp) throws IllegalStateException, MessagingException
messages
argument is an array containing the Message
objects to be prefetched. The FetchProfile
argument specifies which headers in the messages to prefetch. However, this is still just a suggestion. Implementations are free to ignore this request and fetch the message content only when it's actually needed. You can request prefetching of individual headers such as Subject: by name. You can also request prefetching of three predefined blocks of information: the envelope (essentially the subject and addressees of the message), the flags of the message, or the content info of the messages. The three groups you can ask for are given as constant FetchProfile.Item
objects. They are FetchProfile.Item.ENVELOPE
, FetchProfile.Item.FLAGS
, and FetchProfile.Item.CONTENT_INFO
. The FetchProfile
class has a simple noargs constructor as well as methods for constructing a new profile, adding particular items and headers to the profile, and testing whether a particular item is part of a particular profile:
public FetchProfile( )
public void add(FetchProfile.Item item)
public void add(String headerName)
public boolean contains(FetchProfile.Item item)
public boolean contains(String headerName)
public FetchProfile.Item[] getItems( )
public String[] getHeaderNames( )
Message[] messages = folder.getMessages( );
FetchProfile fp = new FetchProfile( );
fp.add(FetchProfile.Item.CONTENT_INFO);
fp.add("Subject");
fp.add("To");
Searching Folders
SearchTerm
objects:
public abstract class SearchTerm extends Object
SearchTerm
class is abstract, but the JavaMail API provides many subclasses for performing common searches:
public abstract class AddressTerm extends SearchTerm public abstract class FlagTerm extends SearchTerm public abstract class StringTerm extends SearchTerm public final class FromTerm extends AddressTerm public final class FromStringTerm extends AddressStringTerm public final class ReceipientTerm extends AddressTerm public final class AddressStringTerm extends StringTerm public final class BodyTerm extends StringTerm public final class HeaderTerm extends StringTerm public final class MessageIDTerm extends StringTerm public final class SubjectTerm extends StringTerm public abstract class DateTerm extends ComparisonTerm public final class ReceivedDateTerm extends DateTerm public final class SentDateTerm extends DateTerm
public final class AndTerm extends SearchTerm public abstract class ComparisonTerm extends SearchTerm public final class NotTerm extends SearchTerm public final class OrTerm extends SearchTerm
match( )
method to describe your search:
public abstract boolean match(Message message)
true
if the message
argument satisfies the search and false
if it doesn't. Set up a SearchTerm
matching your desired parameters and pass it to one of these two search( )
methods in the Folder
class:
public Message[] search(SearchTerm term) throws SearchException, FolderNotFoundException, IllegalStateException, MessagingException public Message[] search(SearchTerm term, Message[] messages) throws SearchException, FolderNotFoundException, IllegalStateException, MessagingException
SearchException
indicates that the search term is more complicated than the implementation can handle. For example, this search term seeks out all messages from billg@bugmenot.com:
Address billg = new InternetAddress("billg@bugmenot.com");
SearchTerm term = new FromTerm(billg);
Address billg = new InternetAddress("billg@bugmenot.com");
SearchTerm term1 = new FromTerm(billg);
Date millennium = Calendar.getInstance( ).set(2004, 0, 1).getTime( );
SearchTerm term2 = new SentDateTerm(ComparisonTerm.GE, millennium);
SearchTerm term = new AndTerm(term1, term2);
MailClient
program in Example 19-7. It allows the user to list email addresses on the command line after the initial URL, like this:
% java SearchClient imap://elharo@mail.metalab.unc.edu/INBOX
willis@bugmenot.com billg@bugmenot.com
Example 19-13. A mail client that searches by From: address
import javax.mail.*;
import javax.mail.search.*;
import javax.mail.internet.*;
import java.util.*;
import java.io.*;
public class SearchClient {
public static void main(String[] args) {
if (args.length == 0) {
System.err.println(
"Usage: java SearchClient protocol://username@host/foldername");
return; }
URLName server = new URLName(args[0]);
try {
Session session = Session.getDefaultInstance(new Properties( ), new MailAuthenticator(server.getUsername( )));
// Connect to the server and open the folder
Folder folder = session.getFolder(server);
if (folder == null) {
System.out.println("Folder " + server.getFile( ) + " not found.");
System.exit(1);
} folder.open(Folder.READ_ONLY);
SearchTerm term = null;
if (args.length > 1) {
SearchTerm[] terms = new SearchTerm[args.length-1];
for (int i = 1; i < args.length; i++) {
Address a = new InternetAddress(args[i]);
terms[i-1] = new FromTerm(new InternetAddress(args[i]));
}
if (terms.length > 1) term = new OrTerm(terms);
else term = terms[0];
}
// Get the messages from the server
Message[] messages;
if (term == null) {
messages = folder.getMessages( );
}
else {
messages = folder.search(term);
}
for (int i = 0; i < messages.length; i++) {
System.out.println("------------ Message " + (i+1) + " ------------");
// Print message headers
Enumeration headers = messages[i].getAllHeaders( );
while (headers.hasMoreElements( )) {
Header h = (Header) headers.nextElement( );
System.out.println(h.getName( ) + ": " + h.getValue( ));
} System.out.println( );
// Enumerate parts
Object body = messages[i].getContent( );
if (body instanceof Multipart) {
processMultipart((Multipart) body); }
else { // ordinary message
processPart(messages[i]);
}
System.out.println( );
} // Close the connection // but don't remove the messages from the server
folder.close(false);
} catch (Exception ex) {
ex.printStackTrace( );
} // Since we may have brought up a GUI to authenticate,
// we can't rely on returning from main( ) to exit
System.exit(0); }
public static void processMultipart(Multipart mp) throws MessagingException {
for (int i = 0; i < mp.getCount( ); i++) {
processPart(mp.getBodyPart(i));
}
}
public static void processPart(Part p) {
try {
// I'd prefer to test the Content-Disposition header here.
// However, too many common email clients don't use it.
String fileName = p.getFileName( );
if (fileName == null) { // likely inline
p.writeTo(System.out);
}
else if (fileName != null) {
File f = new File(fileName);
// find a version that does not yet exist
for (int i = 1; f.exists( ); i++) {
String newName = fileName + " " + i;
f = new File(newName);
}
FileOutputStream out = new FileOutputStream(f);
// We can't just use p.writeTo( ) here because it doesn't
// decode the attachment. Instead we copy the input stream // onto the output stream which does automatically decode
// Base-64, quoted printable, and a variety of other formats.
InputStream in = new BufferedInputStream(p.getInputStream( ));
int b;
while ((b = in.read( )) != -1) out.write(b); out.flush( );
out.close( );
in.close( );
}
} catch (Exception ex) {
System.err.println(e);
ex.printStackTrace( );
}
}
}
Flags
Folder
class has two methods for doing this:
public void setFlags(Message[] messages, Flags flag, boolean value) throws IllegalStateException, MessagingException public void setFlags(int start, int end, Flags flag, boolean value) throws IllegalStateException, MessagingException public void setFlags(int[] messageNumbers, Flags flag, boolean value)
throws IndexOutOfBoundsException, IllegalStateException, MessagingException
setFlags( )
method of the Message
class. In fact, the default implementation simply invokes that method on each message in the specified block of messages. The Folder
class also has a getPermanentFlags( )
method to return the flags that this folder will supply for all messages. This includes all the flags except the user-defined flags, which are applied only to particular messages that the user has flagged. For instance, not all folder implementations track whether messages have been answered:
public abstract Flags getPermanentFlags( )
Event Handling
javax.mail.event
package. They are all subclasses of MailEvent
:
public abstract class MailEvent extends EventObject
Folder
(or Store
or Transport
) has been opened, closed, or disconnected.
Folder
has been created, deleted, or renamed.
Folder
.
Store
.
Transport
that a message was delivered, partially delivered, or failed to be delivered.
public interface ConnectionListener extends EventListener public interface FolderListener extends EventListener public interface MessageChangedListener extends EventListener public interface MessageCountListener extends EventListener public interface StoreListener extends EventListener public interface TransportListener extends EventListener
ConnectionListener
class declares these three methods:
public void opened(ConnectionEvent e)
public void disconnected(ConnectionEvent e)
public void closed(ConnectionEvent e)
FolderListener
interface declares these three methods:
public void folderCreated(FolderEvent e)
public void folderDeleted(FolderEvent e)
public void folderRenamed(FolderEvent e)
add
XXX
Listener()
, remove
XXX
Listener()
, and notify
XXX
Listener()
methods in the Folder
class:
public void addConnectionListener(ConnectionListener l)
public void removeConnectionListener(ConnectionListener l)
protected void notifyConnectionListeners(int type)
public void addFolderListener(FolderListener l)
public void removeFolderListener(FolderListener l)
protected void notifyFolderListeners(int type)
protected void notifyFolderRenamedListeners(Folder folder)
public void addMessageCountListener(MessageCountListener l)
public void removeMessageCountListener(MessageCountListener l)
protected void notifyMessageAddedListeners(Message[] messages)
protected void notifyMessageRemovedListeners(boolean removed, Message[] messages)
public void addMessageChangedListener(MessageChangedListener l)
public void removeMessageChangedListener(MessageChangedListener l)
protected void notifyMessageChangedListeners(int type, Message message)
add
XXX
Listener()
methods add an implementation of the particular interface to the list of listeners. The remove
XXX
Listener()
methods remove an implementation from that list. The notify
XXX
Listener()
methods are not used directly; instead, they're used by instances of Folder
and its subclasses to notify registered listeners of particular events. All of this works exactly as it does in the AWT and Swing, just with different events.Utility Methods
Folder
class overrides two methods from java.lang.Object
, finalize( )
and toString( )
:
protected void finalize( ) throws Throwable public String toString( )