The Part
interface is
implemented by both Message
and
BodyPart
. Every Message
is a
Part
. However, some parts may contain other parts.
The Part
interface declares three kinds of
methods:
Methods for getting and setting the attributes of the part
Methods for getting and setting the headers of the part
Methods for getting and setting the contents of the part
The attributes of the part are things such as the size of the message or the date it was received. These aren’t explicitly specified in the message’s header. The headers, by contrast, are name-value pairs included at the front of the part. Finally, the content of the part is the actual data that the message is trying to transmit.
The JavaMail API defines five attributes for parts. These are:
The approximate number of bytes in the part
The number of lines in the part
Whether the part is an attachment or should be displayed inline
A brief text summary of the part
The name of the file that the attachment came from
Not all parts have all attributes. For instance, a part that does not represent an attached file is unlikely to have a filename attribute. Each attribute is mapped to a getter method:
public int getSize( ) throws MessagingException public int getLineCount( ) throws MessagingException public String getDisposition( ) throws MessagingException public String getDescription( ) throws MessagingException public String getFileName( ) throws MessagingException, ParseException
Generally, the getter method returns null or -1 if a part
doesn’t possess the requested attribute. It throws a
MessagingException
if there’s some problem
retrieving the message; for instance, if the connection goes down
while the message is being retrieved.
The getSize( )
method returns the approximate
number of bytes in the part. Depending on the server and protocol,
this may or may not account for changes in the size caused by
operations such as Base64 encoding the data.
The getLineCount( )
method returns the approximate
number of lines in the content of the part or -1 if the number of
lines isn’t known. Again, the number returned may or may not
account for changes in the size of the part caused by the
part’s encoding.
The getDisposition( )
method returns a string
indicating whether the content should be presented inline or as an
attachment. The value returned should either be null (the disposition
is not known) or one of the two named constants
Part.INLINE
or Part.ATTACHMENT
:
public static final String ATTACHMENT = "attachment"; public static final String INLINE = "inline";
If the disposition is Part.ATTACHMENT
, then the
getFileName( )
method should return the name of
the file to save the attachment in. Otherwise, getFileName( )
will probably return null. However, some email clients,
including Netscape 4.5 for Windows, do not properly set the
Content-disposition header for attachments. Consequently, when
receiving messages with attachments that were sent by Navigator,
you’ll often get a null disposition but a non-null filename. In
practice, it seems more reliable to assume that any body part with a
non-null filename is an attachment regardless of the
Content-disposition header, and any body part with no filename and no
Content-disposition header should be displayed inline if possible. If
it’s not possible—for instance, if you can’t handle
the MIME type—then you can either ask the user for a filename
or pick some reasonable default, such as
attachment1.tif
.
Normally, the filename includes only the actual name of the file but
not any of the directories that the file was in. It’s up to the
application receiving the message to decide where to put the incoming
file. For instance, Eudora generally stores attachments in the
Attachments folder inside the Eudora folder. However, the user has an
option to pick a different location. Since it’s not uncommon to
receive multiple attachments with the same name over time
(vcard.vcf
is a particularly common attachment),
always check to see whether a file with the attached file’s
name already exists before writing out the attachment. If a similarly
named file does exist, you’ll have to rename the attachment in
some reasonable fashion; for instance, by appending a 1 or a 2 to it;
e.g., vcard1.vcf
,
vcard2.vcf
, and so on.
The description, disposition, and filename attributes also have setter methods. However, the size and line count attributes are determined by the content of the part rather than a setter method:
public void setDisposition(String disposition) throws MessagingException, IllegalWriteException, IllegalStateException public void setFileName(String filename) throws MessagingException, IllegalWriteException, IllegalStateException public void setDescription(String description) throws MessagingException, IllegalWriteException, IllegalStateException
The setter methods all throw a MessagingException
if there’s some problem while changing the message. They can
also throw an IllegalWriteException
if the
relevant attribute of the part is not allowed to be modified or an
IllegalStateException
if the part belongs to a
read-only folder.
The setDisposition( )
method determines whether
the part is to be viewed inline or as an attachment. Although
it’s declared to take a String
as an
argument, this String
should be one of the two
named constants Part.INLINE
or
Part.ATTACHMENT
. Parts that are attachments will
generally have a filename included in their meta-information. This
name can be set with the setFileName( )
method.
Finally, the setDescriptionMethod( )
can take any
String
at all to add a description to the part.
Example 19.10 is a simple program that connects to a mail server and reads the attributes of the messages in the mailbox. Since each message is itself a part (even if it contains other parts), we can invoke these methods on the entire message.
Example 19-10. A Program to Read Mail Attributes
import javax.mail.*; import javax.mail.internet.*; import java.util.*; public class AttributeClient { public static void main(String[] args) { if (args.length == 0) { System.err.println( "Usage: java AttributeClient 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); // Get the messages from the server Message[] messages = folder.getMessages( ); for (int i = 0; i < messages.length; i++) { System.out.println("------------ Message " + (i+1) + " ------------"); String from = InternetAddress.toString(messages[i].getFrom( )); if (from != null) System.out.println("From: " + from); String to = InternetAddress.toString( messages[i].getRecipients(Message.RecipientType.TO)); if (to != null) System.out.println("To: " + to); String subject = messages[i].getSubject( ); if (subject != null) System.out.println("Subject: " + subject); Date sent = messages[i].getSentDate( ); if (sent != null) System.out.println("Sent: " + sent); System.out.println( ); // Here's the attributes... System.out.println("This message is approximately " + messages[i].getSize( ) + " bytes long."); System.out.println("This message has approximately " + messages[i].getLineCount( ) + " lines."); String disposition = messages[i].getDisposition( ); if (disposition == null) ; // do nothing else if (disposition.equals(Part.INLINE)) { System.out.println("This part should be displayed inline"); } else if (disposition.equals(Part.ATTACHMENT)) { System.out.println("This part is an attachment"); String fileName = messages[i].getFileName( ); if (fileName != null) { System.out.println("The file name of this attachment is " + fileName); } } String description = messages[i].getDescription( ); if (description != null) { System.out.println("The description of this message is " + description); } } // Close the connection // but don't remove the messages from the server folder.close(false); } catch (Exception e) { e.printStackTrace( ); } // Since we may have brought up a GUI to authenticate, // we can't rely on returning from main( ) to exit System.exit(0); } }
Here’s some typical output. I used an IMAP server because most of these methods don’t work nearly as well with POP servers. IMAP servers can give you the attributes of a message without making you download the entire message, but POP servers aren’t that sophisticated:
% java AttributeClient imap://[email protected]/INBOX
------------ Message 1 ------------
From: "Richman, Jeremy" <[email protected]>
To: 'xsl-list' <[email protected]>
Subject: Re: New twist: eliminating nodes with duplicate content
Sent: Mon Dec 06 08:37:51 PST 1999
This message is approximately 3391 bytes long.
This message has approximately 87 lines.
------------ Message 2 ------------
From: [email protected]
To: Unicode List <[email protected]>
Subject: Re: Number ordering
Sent: Mon Dec 06 11:00:28 PST 1999
This message is approximately 1554 bytes long.
This message has approximately 18 lines.
------------ Message 3 ------------
From: John Posner <[email protected]>
To: 'Nakita Watson' <[email protected]>
Subject: RE: Another conference Call
Sent: Mon Dec 06 11:16:38 PST 1999
This message is approximately 1398 bytes long.
This message has approximately 19 lines.
Classes
that implement the Part
interface—for
example, Message
—generally declare methods
to return specific headers such as To: or From:. The
Part
interface, by contrast, declares methods to
get and set arbitrary headers regardless of name.
The getHeader( )
method gets the values of all the
headers with a name that matches the name
argument. Some headers such as Received: can have multiple values and
can be included in a message multiple times, so this method returns
those values as an array of strings. It returns null if no header
with that name is present in this Part
:
public String[] getHeader(String name) throws MessagingException
The setHeader( )
method adds a new header to an outgoing
message:
public void setHeader(String name, String value) throws MessagingException, IllegalWriteException, IllegalStateException
If there’s already a header with this name, then that header is
deleted and the new one inserted in its place—unless the folder
in which the message resides is read only, in which case an
IllegalStateException
is thrown.
By contrast, the addHeader( )
method adds header with the specified
name but does not replace any that exist:
public void addHeader(String name, String value) throws MessagingException, IllegalWriteException, IllegalStateException
The removeHeader( )
method deletes all instances of the
named header from this Part
:
public void removeHeader(String name) throws MessagingException, IllegalWriteException, IllegalStateException
The getAllHeaders( )
method returns a
java.util.Enumeration
object containing all the
headers in this message:
public Enumeration getAllHeaders( ) throws MessagingException
The Enumeration
contains one
javax.mail.Header
object for each header in the
message.
public class Header extends Object
The Header
class is very simple with just a
constructor to set the name and value of the header, and
getName( )
and getValue( )
methods to return them:
public Header(String name, String value) public String getName( ) public String getValue( )
Finally, the getMatchingHeaders( )
method returns
an Enumeration
containing all the headers in this
message whose name is one of the strings in the argument
names
array. The getNonMatchingHeaders( )
method returns an Enumeration
containing all the headers in this message whose name is
not one of the strings in the argument
names
array. Again, the
Enumeration
contains Header
objects:
public Enumeration getMatchingHeaders(String[] names) throws MessagingException public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException
You may recall that Example 19.8,
HeaderClient
, printed only a few prespecified
headers, such as To: and From:. With the methods of the
Part
interface (that Message
implements), it’s easy to expand this to cover all headers in
the message, whether known in advance or not. Example 19.11 demonstrates. This is important because
Internet email can contain arbitrary headers. It’s not limited
to just a few headers mentioned in the relevant RFCs. For instance,
some graphical mail clients for X Windows use a completely
nonstandard X-Face: header whose value is a 48-pixel ×
48-pixel, black-and-white, uuencoded bitmap of the sender’s
countenance. Other clients use custom headers for purposes both more
serious and more silly.
Example 19-11. A Program to Read Mail Headers
import javax.mail.*; import javax.mail.internet.*; import java.util.*; public class AllHeaderClient { public static void main(String[] args) { if (args.length == 0) { System.err.println( "Usage: java AllHeaderClient 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); // Get the messages from the server Message[] messages = folder.getMessages( ); for (int i = 0; i < messages.length; i++) { System.out.println("------------ Message " + (i+1) + " ------------"); // Here's the difference... Enumeration headers = messages[i].getAllHeaders( ); while (headers.hasMoreElements( )) { Header h = (Header) headers.nextElement( ); System.out.println(h.getName() + ": " + h.getValue( )); } System.out.println( ); } // Close the connection // but don't remove the messages from the server folder.close(false); } catch (Exception e) { e.printStackTrace( ); } // Since we may have brought up a GUI to authenticate, // we can't rely on returning from main( ) to exit System.exit(0); } }
Here’s a typical run:
% java AllHeaderClient pop3://[email protected]/INBOX
------------ Message 1 ------------
Received: (from eharold@localhost)
by utopia.poly.edu (8.8.8/8.8.8) id QAA05728
for eharold; Tue, 30 Nov 1999 16:14:29 -0500 (EST)
Date: Tue, 30 Nov 1999 16:14:29 -0500 (EST)
From: Elliotte Harold <[email protected]>
Message-Id: <[email protected]>
To: [email protected]
Subject: test
Content-Type: text
X-UIDL: 87e3f1ba71738c8f772b15e3933241f0
Status: RO
------------ Message 2 ------------
Received: from russian.cloud9.net (russian.cloud9.net [168.100.1.4])
by utopia.poly.edu (8.8.8/8.8.8) with ESMTP id OAA28428
for <[email protected]>; Wed, 1 Dec 1999 14:05:06 -0500 (EST)
Received: from [168.100.203.234] (macfaq.dialup.cloud9.net [168.100.203.234])
by russian.cloud9.net (Postfix) with ESMTP id 24B93764F8
for <[email protected]>; Wed, 1 Dec 1999 14:02:50 -0500 (EST)
Mime-Version: 1.0
X-Sender: [email protected]
Message-Id: <v04210100b46b1f97969d@[168.100.203.234]>
Date: Wed, 1 Dec 1999 13:55:40 -0500
To: [email protected]
From: Elliotte Rusty Harold <[email protected]>
Subject: New system
Content-Type: text/plain; charset="us-ascii" ; format="flowed"
X-UIDL: 01fd5cbcf1768fc6c28f9c8f934534b5
Status: RO
------------ Message 3 ------------
Received: from russian.cloud9.net (russian.cloud9.net [168.100.1.4])
by utopia.poly.edu (8.8.8/8.8.8) with ESMTP id HAA17345
for <[email protected]>; Thu, 2 Dec 1999 07:55:04 -0500 (EST)
Received: from [168.100.203.234] (macfaq.dialup.cloud9.net [168.100.203.234])
by russian.cloud9.net (Postfix) with ESMTP id C036A7630E
for <[email protected]>; Thu, 2 Dec 1999 07:54:58 -0500 (EST)
Mime-Version: 1.0
X-Sender: [email protected]
Message-Id: <v04210100b46c0c686ecc@[168.100.203.234]>
Date: Thu, 2 Dec 1999 06:45:52 -0500
To: [email protected]
From: "Dr. Mickel" <[email protected]>(by way of Elliotte Rusty Harold)
Subject: Breath RX Products now available Online!
Sender: [email protected]
Content-Type: text/plain; charset="us-ascii" ; format="flowed"
X-UIDL: 40fa8af2aca1a8c11994f4c56b792720
Status: RO
Every part has a certain content that can be represented as a sequence of bytes. For instance, in a part that’s a simple email message, the content is the body of the message. However, in multipart messages, this content may itself contain other parts. The content of each of these parts can be represented as a sequence of bytes. Furthermore, this sequence of bytes may represent some more specific content type, such as a uuencoded GIF image or a Base64 encoded WAV audio clip.
The Part
interface declares two methods for
determining a part’s MIME content type. The
getContentType( )
method returns the MIME content
type of this part as a string; for example,
text/plain;
charset="us-ascii";
format= "flowed"
. It returns null if the content
type can’t be determined:
public String getContentType( ) throws MessagingException
The isMimeType( )
method returns true if this part has the
specified MIME type and subtype. Additional parameters, such as
charset, are ignored:
public boolean isMimeType(String mimeType) throws MessagingException
The Part
interface also declares several methods
that return the content as a variety of different Java objects
including InputStream
, String
,
DataHandler
, and more. The
getInputStream( )
method returns an
InputStream
from which the part’s content
can be read:
public InputStream getInputStream( ) throws IOException, MessagingException
If the part’s content has been encoded in some way—for
example, by Base64 encoding it—then the
InputStream
reads the decoded content. The
JavaMail API supports all the common encodings except the BinHex
format used for Macintosh files. If it encounters a BinHex-encoded
attachment, then it strips the MIME headers but otherwise leaves the
BinHex data untouched. BinHex documents are tough to deal with on
most platforms because of the unusual two-fork nature of a Mac file.
Unless you’re a real Mac expert, you’re probably better
off using a third-party utility such as StuffIt Expander (http://www.aladdinsys.com/) to decode the
file.
Another possibility is to request a DataHandler
for the content with the getDataHandler( )
method. The
DataHandler
class comes from the Java Activation
Framework. It declares methods to help you decide what to do with the
content; for instance, by finding the right Java bean or helper
application to display the content:
public javax.activation.DataHandler getDataHandler( ) throws MessagingException
A third possibility is to request the content as an unspecified Java
object using the getContent( )
method:
public Object getContent( ) throws IOException, MessagingException
This is reminiscent of the getContent( )
method of
java.net.URL
. However, rather than relying on the
poorly designed content handler mechanism, this getContent( )
method uses the Java Activation Framework, so the
behavior is a little more clearly specified. Most of the time, if the
content type is text/plain, then a String
will be
returned. If the content type is multipart, then regardless of the
subtype, a javax.mail.Multipart
object is
returned. If the content type is some other type that is recognized
by the underlying DataHandler
, then an appropriate
Java object is returned. Finally, if the type is unrecognized, then
an InputStream
is returned.
You can change which objects are returned for which content types by
providing your own DataHandler
. This would be
installed with the setDataHandler( )
method:
public void setDataHandler(javax.activation.DataHandler dh) throws MessagingException, IllegalWriteException, IllegalStateException
Although this method is declared to throw the usual group of
exceptions, it’s perhaps a little less likely to actually do
so, since setting the DataHandler
affects only the
Message
object rather than the actual message
stored on the server.
When sending a message, you naturally must set the message’s
contents. Since email messages are text, the most straightforward way
is just to provide the text of the part with setText( )
:
public void setText(String text) throws MessagingException, IllegalWriteException, IllegalStateException
The setText( )
method sets the MIME type to
text/plain. Other objects can be made into content as well, provided
the part has a DataHandler
that understands how to
convert them to encoded text. This is done with the
setContent( )
method:
public void setContent(Object o, String type) throws MessagingException, IllegalWriteException, IllegalStateException
Another way to write the contents of a part is by using an
OutputStream
. The writeTo( )
method writes the content of the Part
onto an
OutputStream
. If necessary, it will encode the
content using Base64, quoted-printable, or some other format as
specified by the DataHandler
:
public void writeTo(OutputStream out) throws IOException, MessagingException
In fact, this not only writes the content of this
Part
; it also writes the attributes and headers of
the part. Example 19.4 used this to provide a simple
way of getting an entire email message in one fell swoop. It’s
most convenient, though, when you want to send an entire message to
an SMTP server in one method call.
Finally, multiple parts can be added to a part by wrapping them in a
Multipart
object and passing that to
setContent( )
:
public void setContent(Multipart mp) throws MessagingException, IllegalWriteException, IllegalStateException
In this case, the entire message will typically have a content type such as multipart/mixed, multipart/signed, or multipart/alternative. The individual parts of the message are all enclosed in one envelope, but each part of the message has its own content type, content encoding, and data. The multiple parts may be used to present different forms of the same document (e.g., HTML and plain-text mail), a document and meta-information about the document (e.g., a message and the MD5 digest of the message), or several different documents (e.g., a message and several attached files). The next section expands on this process.
3.142.244.66