views:

1421

answers:

2

I have a question about Java Mail and how it works with streams. In Java Mail 1.4.1 there is a MimeMessage constructor that accepts a stream. My understanding is that I could pass a stream to this constructor and it would parse it for me into a MimeMessage. I wrote 2 tests to prove this. The first test sends in a stream that contains only a partial multi-part MIME message. The second test sends in a stream that contains 2 complete multi-part MIME messages. Neither work the way I expected. The first does not throw an exception and the second reads the entire stream into a single message somehow. Is this a bug in Java Mail or am I using the wrong kind of streams? Or am I missing something bigger?

It is a little long but here is the test code:

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;

import junit.framework.TestCase;


public class mimeTest extends TestCase {

    public void testPartialMulitpartMessage() throws MessagingException, IOException 
    {
        Properties props = new Properties();
        Session session = Session.getInstance(props, null);
        String testMsg1 = "test";
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        // Step 1 - Create first MIME message
        MimeMessage mesg = new MimeMessage(session);
        Multipart mp = new MimeMultipart("mixed");
        //create a child part
        BodyPart bodyPart = new MimeBodyPart();
        bodyPart.setContent(testMsg1, "application/x-special");
        bodyPart.setHeader("Content-Length", String.valueOf(testMsg1.length()));
        DataSource ds = new ByteArrayDataSource(testMsg1, "application/x-special");
        bodyPart.setDataHandler(new DataHandler(ds));
        bodyPart.setHeader("Content-Transfer-Encoding", "8bit");
        // Add the child part to the multipart
        mp.addBodyPart(bodyPart);
        // Put the MultiPart into the Message
        mesg.setContent(mp);

        // Step 2 - write to a stream
        mesg.writeTo(byteArrayOutputStream);

        byte bytes[] = byteArrayOutputStream.toByteArray();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes, 0, 10);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(byteArrayInputStream);
        boolean thrown = false;
        try
        {
            //Why does this not throw a messageexception.
            MimeMessage mesg2 = new MimeMessage(session, bufferedInputStream);
        }
        catch(MessagingException me){
            thrown = true;
        }

        if(!thrown) {
            assertTrue("Expected exception not thrown.", false);
        }
    }

    public void testMulitpleMulitpartMessages() throws MessagingException, IOException {
        Properties props = new Properties();
        Session session = Session.getInstance(props, null);
        String testMsg1 = "test";
        String testMsg2 = "test1";
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        // Step 1 - Create first MIME message
        MimeMessage mesg = new MimeMessage(session);
        Multipart mp = new MimeMultipart("mixed");
        //create a child part
        BodyPart bodyPart = new MimeBodyPart();
        bodyPart.setContent(testMsg1, "application/x-special");
        bodyPart.setHeader("Content-Length", String.valueOf(testMsg1.length()));
        DataSource ds = new ByteArrayDataSource(testMsg1, "application/x-special");
        bodyPart.setDataHandler(new DataHandler(ds));
        bodyPart.setHeader("Content-Transfer-Encoding", "8bit");
        // Add the child part to the multipart
        mp.addBodyPart(bodyPart);
        // Put the MultiPart into the Message
        mesg.setContent(mp);

        // Step 2 - write to a stream
        mesg.writeTo(byteArrayOutputStream);

        // Step 3 - Create second MIME message
        MimeMessage mesg2 = new MimeMessage(session);
        mp = new MimeMultipart("mixed");
        //create a child part
        bodyPart = new MimeBodyPart();
        bodyPart.setContent(testMsg2, "application/x-special");
        bodyPart.setHeader("Content-Length", String.valueOf(testMsg2.length()));
        ds = new ByteArrayDataSource(testMsg2, "application/x-special");
        bodyPart.setDataHandler(new DataHandler(ds));
        bodyPart.setHeader("Content-Transfer-Encoding", "8bit");
        // Add the child part to the multipart 
        mp.addBodyPart(bodyPart);
        // Put the MultiPart into the Message
        mesg2.setContent(mp);

        // Step 4 - write to the same stream
        mesg2.writeTo(byteArrayOutputStream);

        // Step 6 - read the two messages back
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        BufferedInputStream bufferedInputStream = new BufferedInputStream(byteArrayInputStream);
        List<MimeMessage> listMessages = new ArrayList<MimeMessage>();
        while (bufferedInputStream.available() > 0) {
            //http://java.sun.com/products/javamail/javadocs/javax/mail/internet/MimeMessage.html#MimeMessage(javax.mail.Session,%20java.io.InputStream)
            //The InputStream will be left positioned at the end of the data for the message.
            //WHY does this not work?  It reads the whole stream.
            mesg = new MimeMessage(session, bufferedInputStream);
            //output the message
            listMessages.add(mesg);
        }

        assertEquals(2, listMessages.size());

        assertTrue(listMessages.get(0).equals(mesg));
        assertTrue(listMessages.get(1).equals(mesg2));
    }
}
+2  A: 

The JavaMail API is a pig, and using it raw is a thankless task, in large because it doesn't behave as you'd expect.

I'd recommend using Spring's API layer around it, it's much less stressful. It doesn't hide JavaMail completely, it just makes it more predictable and test-friendly.

skaffman
A: 

JavaMail will read the whole stream, possibly buffering it. I agree with @skaffman, however my perspective is that it's just heavily optimized without comments as to why, and that makes it hard to use.

The MimeMessage.parse(InputStream) method is what you're dealing with. It does read the whole stream, without closing it, unless you're using a SharedInputStream. It will leave the InputStream positioned at the end, as you are seeing.

The MimeMessage doesn't do much to interpret the data in the stream, therefore I think you're "missing something bigger" to use your words. Why do you expect it to interpret the data? Can you just send the multipart messages that you're using to create your streams?

Jason Thrasher