Multipart Messages and File Attachments
The way all the different text and binary file types are encoded into raw text that can be passed through 7-bit email gateways is fairly ingenious and rather detailed. Fortunately, the JavaMail API shields you from those details, interesting as they are. To send a multipart message using the JavaMail API, all you have to do is add the parts to a MimeMultipart
object, then pass that object to the Message
's setContent( )
method. To receive a multipart message, you simply process each of the parts individually. Most of the methods for building and deconstructing multipart messages are in the abstract javax.mail.Multipart
class:
public abstract class Multipart extends Object
However, since this class is abstract, you'll generally start with a javax.mail.internet.MimeMultipart
object instead:
public class MimeMultipart extends Multipart
Each part you add to a Multipart
is an instance of the abstract javax.mail.BodyPart
class that implements the Part
interface of the last section:
public abstract class BodyPart extends Object implements Part
In Internet email, the concrete subclass of BodyPart
you'll use is javax.mail.internet.MimeBodyPart
:
public class MimeBodyPart extends BodyPart implements MimePart
Most of the methods you need in the MimeBodyPart
and BodyPart
classes are the ones you're already familiar with from the Part
interface, methods such as setContent( )
and setDataHandler( )
. There are also three methods to read the contents of a Multipart
object:
public String getContentType( ) public int getCount( ) throws MessagingException public BodyPart getBodyPart(int index) throws IndexOutOfBoundsException, MessagingException
The You can also get a part from a multipart message by passing an However, this differs from the approach taken in Example 19-12 in that it does not decode the part before writing it. It leaves whatever Base64, BinHex, or quoted-printable encoding the sender applied to the attachment alone. Instead, it simply writes the raw data. Attaching files (or other documents) to messages you send is more complicated. To attach a file to a message, you first have to wrap the data in a The first variant simply appends the part to the end of the message. The second variant adds the given part at the specified position. If the position is greater than the number of parts in the message, the part is simply added to the end. If it's added somewhere in the middle, this may cause the positions of other parts to change. If the message can't be changed, an If the message can't be changed, an getContentType()
method returns the MIME media type of the entire Multipart
, which is typically something like multipart/mixed
or multipart/alternative
. This is not the same as the MIME types of the individual parts, which are something like text/plain
or image/gif
. The getCount( )
method returns the number of parts in this Multipart
. The getBodyPart( )
method returns a particular part. Parts are numbered starting at 0, like the components of an array. Example 19-12 is very similar to Example 19-11, AllHeaderClient
. However, Example 19-12 adds the necessary code to handle the body of the message. If the message is a single-part message, it's simply printed on System.out
. However, if the message has multiple parts, each part is handled separately. If the part has a multipart content type itself, processMultipart()
is called recursively. If the part has no filename, does not have the disposition Part.ATTACHMENT
, and has MIME type text/plain
, it's assumed to be an inline message and is printed on System.out
. Otherwise, it's assumed to be an attachment and is saved into an appropriate file. If necessary, the static File.createTempFile( )
method generates a reasonable name for the file.
Example 19-12. A mail client that handles multipart messages with attached files
import javax.mail.*;
import javax.mail.internet.*;
import java.util.*;
import java.io.*;
public class AllPartsClient {
public static void main(String[] args) {
if (args.length == 0) {
System.err.println(
"Usage: java AllPartsClient protocol://username@host:port/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) + " ------------");
// 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 {
String fileName = p.getFileName( );
String disposition = p.getDisposition( );
String contentType = p.getContentType( );
if (contentType.toLowerCase( ).startsWith("multipart/")) {
processMultipart((Multipart) p.getContent( ) );
}
else if (fileName == null && (Part.ATTACHMENT.equalsIgnoreCase(disposition) || !contentType.equalsIgnoreCase("text/plain"))) {
// pick a random file name. This requires Java 1.2 or later.
fileName = File.createTempFile("attachment", ".txt").getName( );
}
if (fileName == null) { // likely inline
p.writeTo(System.out);
}
else {
File f = new File(fileName);
// find a file that does not yet exist
for (int i = 1; f.exists( ); i++) {
String newName = fileName + " " + i;
f = new File(newName);
}
OutputStream out = new BufferedOutputStream(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( );
}
}
}
OutputStream
to the part's writeTo( )
method:
public abstract void writeTo(OutputStream out) throws IOException, MessagingException
BodyPart
object and add it to the Multipart
using one of the two addBodyPart( )
methods:
public void addBodyPart(BodyPart part) throws IllegalWriteException, MessagingException public void addBodyPart(BodyPart part, int index) throws IllegalWriteException, MessagingException
IllegalWriteException
is thrown. The tricky part is creating the BodyPart
object. You first need to guess a reasonable MIME content type for the file (text/plain
and app/octet-stream
are the most common types). Next, read the file and convert it into some class of Java object. Then install a javax.activation.DataHandler
class that knows how to convert your data class according to your chosen MIME type. Once you've done all this, you can create a new MimeBodyPart
object and use the various methods of the Part
interface to set attributes such as the filename and the content disposition. There are also two removeBodyPart()
methods that delete a specified part from the message, although these aren't as commonly used:
public boolean removeBodyPart(BodyPart part) throws IllegalWriteException, MessagingException public void removeBodyPart(int index) throws IndexOutOfBoundsException, MessagingException
IllegalWriteException
is thrown. If the specified index doesn't identify a part, an IndexOutOfBoundsException
is thrown. If the specified part isn't present in the message, a MessagingException
is thrown.