Google App Engine(GAE)でメールを受信する

Google App Engine(GAE)でメール受信ができるようになりました!
詳しくはこちら

つまるところ、
【[アドレス]@[アプリケーションID].appspotmail.com】宛てにメール受信すると、
【[アプリケーションURL]/_ah/mail/[アドレス]】に対応したサーブレットが起動されるということです。

上のリンク先の下部を確認すると、以下の記述があります。

You can then use various methods to parse the message object:

* Call getContentType() to extract the message content type.
* Call getContentStream() to map the message to a stream object.
* The getContent() method returns an object that implements the Multipart interface. You can then call getCount() to determine the number of parts and getBodyPart(int index) to return a particular body part.

これを踏まえて、通常のメール受信のように、

Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);
MimeMessage message = new MimeMessage(session, req.getInputStream());
Part p = (Part)messege;
if (p.isMimeType("multipart/*")) { // マルチパートの場合
Multipart mp = (Multipart)p.getContent();
}

なんて素直に書くと、(Multipart)p.getContent()でClassCastExceptionが発生します。
これは、p.getContent()の戻り値が java.io.ByteArrayInputStreamであるためです。

an object that implements the Multipart interface.じゃないのか?

対応としては、以下のようにひと手間加えて、MimeMultipartを生成します。

InputStream inputStream = (InputStream)mimeMessage.getContent();
String ContentType = mimeMessage.getContentType();
ByteArrayDataSource byteArrayDataSource = new ByteArrayDataSource
(inputStream,ContentType);
Multipart mimeMultipart = new MimeMultipart(byteArrayDataSource);


クラスにまとめたのが、以下のソース。

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Properties;

import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.util.ByteArrayDataSource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.datastore.Blob;

@SuppressWarnings("serial")
public class SampleReceivingMailController extends HttpServlet {

    /** HTMLメールファイル名 */
    private int htmlFileNameCount = 0;

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse
resp)
            throws ServletException, IOException {

        try {

            Properties props = new Properties();
            Session session = Session.getDefaultInstance(props, null);
            MimeMessage message = new MimeMessage(session, req.getInputStream
());

            /* メール解析 */
            partAnalysis((Part) message);

        } catch (MessagingException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * メール解析
     *
     * @param part
     * @throws Exception
     */
    private void partAnalysis(Part part) throws Exception {

        if (part.isMimeType("multipart/*")) {
            /* マルチパートの場合 ------------------------------------------------- */

            ByteArrayDataSource byteArrayDataSource = new ByteArrayDataSource(
                    (InputStream) part.getContent(), part.getContentType());

            Multipart content = new MimeMultipart(byteArrayDataSource);

            // 含まれるパートを再帰的に処理
            int count = content.getCount();
            for (int i = 0; i < count; i++) {
                partAnalysis(content.getBodyPart(i));
            }

        } else if (part.isMimeType("text/plain")) {
            /* テキストの場合 ----------------------------------------------------- */

            InputStream is = (InputStream) part.getInputStream();
            Reader r = new InputStreamReader(is);
            BufferedReader buf = new BufferedReader(r);

            /* テキスト内容 */
            StringBuilder sb = new StringBuilder();
            for (String line; (line = buf.readLine()) != null;) {
                sb.append(line + "\n");
            }

        } else {
            // その他の場合

            /* 添付ファイルを保存 */
            getAttachments(part);
        }

    }

    /**
     * 添付ファイルを取得
     *
     * @param part
     * @throws MessagingException
     * @throws IOException
     */
    private void getAttachments(Part part) throws MessagingException,
            IOException {

        String disp = part.getDisposition();

        if (disp == null || disp.equalsIgnoreCase(Part.ATTACHMENT)) {
            /* 添付ファイルの場合 --------------------------------------------------- */

            /* ファイル名 */
            String filename = MimeUtility.decodeText(part.getFileName());
            if (part.isMimeType("text/html")) {
                filename = String.valueOf(htmlFileNameCount++) + ".html";
            }

            /* ファイルサイズ */
            int fileSize = part.getSize();

            /* コンテントタイプ */
            String contentType = part.getContentType();

            /* ファイル内容 */
            Blob blob = this.generateBlob(part.getInputStream());

        }
    }

    /**
     * Blob生成
     * @param in
     * @return Blob
     * @throws IOException
     */
    private Blob generateBlob(InputStream in) throws IOException{

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int len;
        byte[] buffer = new byte[1024];
        while ( (len = in.read(buffer, 0, buffer.length)) != -1) {
            out.write(buffer, 0, len);
        }
        byte[] b = out.toByteArray();

        return new Blob(b);

    }

} 

メール受信が簡単にできるようになったことで、提供できる機能の幅が広がりましたね!(´∀`)
以上です。

2009/12/04 追記

Google App Engine のバージョンが日本時間の2009/12/03に、1.2.8になったようです。
それに伴って、この記事で書いたクラスが動作しなくなったようです。
こちらで報告されて気がつきました。


2009-12-04 - DISってHONEY♪ @gungnir_odinに修正方法を書きました。