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

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 MessagingException 

These 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 MessagingException 

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 open( ) method:

 public abstract void open(int mode)   throws FolderNotFoundException, MessagingException 

The 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( ) 

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)   throws FolderNotFoundException, MessagingException 

If the 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.

19.11.2 Basic Folder Info

The 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 

The 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.

19.11.3 Managing Folders

The create( ) method creates a new folder in this folder's Store :

 public abstract boolean create(int type) throws MessagingException 

The 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) throws   IllegalStateException, FolderNotFoundException, MessagingException 

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 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)   throws FolderNotFoundException, MessagingException 

As the name implies, the messages in the array are placed at the end 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) throws   IllegalStateException, FolderNotFoundException, MessagingException 

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

After a message has been expunged, there may still be 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( ) public void    setSubscribed(boolean subscribe)   throws FolderNotFoundException, MethodNotSupportedException,   MessagingException 

If a provider doesn't support subscription, 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( )  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 

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.

19.11.7 Checking for Mail

The getMessageCount() method returns the number of messages in this folder:

 public abstract int getMessageCount( )  throws FolderNotFoundException, MessagingException 

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

The 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 

Unlike 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 

Like 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.

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

The getMessage( ) method returns the n th 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 

The 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( ) 

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:

 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 Object 

The 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 

It also provides several classes for combining searches:

 public final    class AndTerm           extends SearchTerm public abstract class ComparisonTerm    extends SearchTerm public final    class NotTerm           extends SearchTerm public final    class OrTerm            extends SearchTerm 

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 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,   FolderNotFoundException, IllegalStateException, MessagingException public Message[] search(SearchTerm term, Message[] messages)   throws SearchException, FolderNotFoundException,   IllegalStateException, MessagingException 

A 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@microsoft.com:

 Address billg   = new InternetAddress("billg@microsoft.com"); SearchTerm term = new FromTerm(billg); 

This search term looks for all messages from billg@microsoft.com after 2003:

 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/INBOX   willis@nvx.com billg@microsoft.com  

Only messages from the specified users will be returned. However, if 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)   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 

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

The six concrete kinds of mail events, the first four of which involve folders, are:


ConnectionEvent

A Folder (or Store or Transport ) has been opened, closed, or disconnected.


FolderEvent

A Folder has been created, deleted, or renamed.


MessageChangedEvent

The message's envelope or flags have changed.


MessageCountEvent

A message was added to or deleted from a Folder .


StoreEvent

A notification or alert from a Store .


TransportEvent

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

Each of these interfaces declares one or more methods that must be provided by implementing classes. For example, the ConnectionListener class declares these three methods:

 public void opened(ConnectionEvent e) public void disconnected(ConnectionEvent e) public void closed(ConnectionEvent e) 

The FolderListener interface declares these three methods:

 public void folderCreated(FolderEvent e) public void folderDeleted(FolderEvent e) public void folderRenamed(FolderEvent e) 

Four of these events can be fired by folders. Consequently, there are 14 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) 

The 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.

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 Throwable public String toString( ) 

Neither of these is especially important to the client programmer.



Java Network Programming
Java Network Programming, Third Edition
ISBN: 0596007213
EAN: 2147483647
Year: 2003
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net