19.11 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 ObjectThis 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.
19.11.1 Opening Folders
You
cannot create folders directly. The only constructor is protected:
protected Folder(Store store)Instead, you get a Folder from a
Session, a Store, or another
Folder like this:
Folder outbox = container.getFolder("sent-mail");There are actually three 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 MessagingExceptionThese methods share an annoying idiosyncrasy with the
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 MessagingExceptionWhen you first get a folder, it's closed. Before you
can read the messages it contains, you have to open the folder using
the open( )
method:
public abstract void open(int mode)The mode argument should be one of the two named
throws FolderNotFoundException, MessagingException
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( )Generally, trying to do something with a closed folder that requires
the folder to be open or vice versa will throw a
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)If the expunge argument is
throws FolderNotFoundException, MessagingException
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.
19.11.2 Basic Folder Info
The
Folder class has eight methods that return
basic information about a folder:
public abstract String getName( )The getName( ) method returns the name
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
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.
19.11.3 Managing Folders
The
create( ) method creates a new folder in
this folder's Store:
public abstract boolean create(int type) throws MessagingExceptionThe type of the folder should be one of the named constants
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) throwsIf there are messages in this folder, they are deleted along with the
IllegalStateException, FolderNotFoundException, MessagingException
folder. If the folder contains subfolders, the subfolders are deleted
if the 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
19.11.4 Managing Messages in Folders
On occasion, you may find a need to
put a message in a folder. There's only one method
to do this, appendMessages():
public abstract void appendMessages(Message[] messages)As the name implies, the messages in the array are placed at the end
throws FolderNotFoundException, MessagingException
of this folder.The copyMessages() method copies messages into this folder
from a specified folder given as an argument:
public void copyMessages(Message[] messages, Folder destination) throwsThe copied messages are appended to the destination folder. They are
IllegalStateException, FolderNotFoundException, MessagingException
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
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,After a message has been expunged, there may still be
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.
19.11.5 Subscriptions
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 Folder object. A subscription to
the newsgroup's Folder indicates
that the user wants to retrieve messages from that newsgroup:
public boolean isSubscribed( )If a provider doesn't support subscription,
public void setSubscribed(boolean subscribe)
throws FolderNotFoundException, MethodNotSupportedException,
MessagingException
setSubscribed( ) throws a
MethodNotSupportedException and
isSubscribed( ) returns
false.
19.11.6 Listing the Contents of a Folder
Folders are hierarchical. That is, a
folder can contain other folders. There are four methods to list the
folders that a folder contains:
public Folder[] list( )The first method returns an array listing the folders that this
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
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.
19.11.7 Checking for Mail
The getMessageCount() method returns the number of messages in
this folder:
public abstract int getMessageCount( )This method can be invoked on an open or closed folder. However, in
throws FolderNotFoundException, MessagingException
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 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( )The getNewMessageCount() method uses a slightly different
throws FolderNotFoundException, MessagingException
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( )Unlike hasNewMessages( ),
throws FolderNotFoundException, MessagingException
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( )Like getNewMessageCount( ),
throws FolderNotFoundException, MessagingException
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.
19.11.8 Getting Messages from Folders
The Folder class
provides four methods for retrieving messages from open folders:
public abstract Message getMessage(int messageNumber) throwsThe getMessage( ) method returns the
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
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)The messages argument is an array containing the
throws IllegalStateException, MessagingException
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( )For example, suppose you wanted to download just the subjects, the
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( )
To: addresses, and the content information of a block of messages.
Fetch them like this:
Message[] messages = folder.getMessages( );
FetchProfile fp = new FetchProfile( );
fp.add(FetchProfile.Item.CONTENT_INFO);
fp.add("Subject");
fp.add("To");
19.11.9 Searching Folders
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 SearchTerm objects:
public abstract class SearchTerm extends ObjectThe SearchTerm class is abstract, but the JavaMail API
provides many subclasses for performing common searches:
public abstract class AddressTerm extends SearchTermIt also provides several classes for combining searches:
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 SearchTermAnd of course, you can write your own subclasses that implement your
public abstract class ComparisonTerm extends SearchTerm
public final class NotTerm extends SearchTerm
public final class OrTerm extends SearchTerm
own search logic. To implement a search, write a subclass and
override the subclass's match( )
method to describe your search:
public abstract boolean match(Message message)This method returns 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,A SearchException indicates that the search term
FolderNotFoundException, IllegalStateException, MessagingException
public Message[] search(SearchTerm term, Message[] messages)
throws SearchException, FolderNotFoundException,
IllegalStateException, MessagingException
is more complicated than the implementation can handle. For example,
this search term seeks out all messages from
Address billg = new InternetAddress("billg@microsoft.com");
SearchTerm term = new FromTerm(billg);This search term looks for all messages from
Address billg = new InternetAddress("billg@microsoft.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);Example 19-13 is a simple variation of the
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/INBOXOnly messages from the specified users will be returned. However, if
willis@nvx.com billg@microsoft.com
no email addresses are given, all messages will be returned.
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( );
}
}
}
19.11.10 Flags
It's
sometimes useful to be able to change the flags for an entire group
of messages at once. The Folder class has two
methods for doing this:
public void setFlags(Message[] messages, Flags flag, boolean value)Ultimately, these are just conveniences. There's
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
nothing you can do with these methods that you can't
do by setting the flags on each message individually with the
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( )
19.11.11 Event Handling
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 programming 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 javax.mail.event package. They are all
subclasses of MailEvent:
public abstract class MailEvent extends EventObjectThe six concrete kinds of mail events, the first four of which
involve folders, are:
A Folder (or Store or
Transport) has been opened, closed, or
disconnected.
A Folder has been created, deleted, or renamed.
The message's envelope or flags have changed.
A message was added to or deleted from a Folder.
A notification or alert from a Store.
A notification from a Transport that a message was
delivered, partially delivered, or failed to be delivered.
There are six listener interfaces corresponding to the six kinds of
events:
public interface ConnectionListener extends EventListenerEach of these interfaces declares one or more methods that must be
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
provided by implementing classes. For example, the
ConnectionListener class declares these three
methods:
public void opened(ConnectionEvent e)The FolderListener interface declares these three
public void disconnected(ConnectionEvent e)
public void closed(ConnectionEvent e)
methods:
public void folderCreated(FolderEvent e)Four of these events can be fired by folders. Consequently, there are
public void folderDeleted(FolderEvent e)
public void folderRenamed(FolderEvent e)
14
addXXXListener(),
removeXXXListener(), and
notifyXXXListener() methods in the Folder class:
public void addConnectionListener(ConnectionListener l)The
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)
addXXXListener() methods add an implementation of the particular interface
to the list of listeners. The
removeXXXListener() methods remove an implementation from that list. The
notifyXXXListener() 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.
19.11.12 Utility Methods
Finally, for
completeness's sake, I'll note that
the Folder class overrides two methods from
java.lang.Object, finalize( )
and toString( ):
protected void finalize( ) throws ThrowableNeither of these is especially important to the client
public String toString( )
programmer.