/*
 * blanco Framework
 * Copyright (C) 2004-2007 IGA Tosiki
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 */
package blanco.mail.core;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.util.ByteArrayDataSource;

import blanco.commons.util.BlancoStringUtil;
import blanco.log.BlancoLogBinaryLogger;
import blanco.mail.core.resourcebundle.BlancoMailCoreMessageResourceBundle;
import blanco.mail.core.valueobject.BlancoMailAddress;
import blanco.mail.core.valueobject.BlancoMailMessage;
import blanco.mail.core.valueobject.BlancoMailMessageAttachment;
import blanco.mail.core.valueobject.BlancoMailMessageHeader;
import blanco.mail.core.valueobject.BlancoMailSetting;
import blanco.mail.core.valueobject.BlancoMailStatistics;

/**
 * メール送信クラス実装。
 * 
 * 簡単で安全なメール送信を実現するための参照実装です。 blancoMail コアライブラリのひとつ。
 * クラス実装を取得するためには、BlancoMailSenderFactory#getInstance を利用します。
 * 
 * このクラスを拡張して利用する場面を想定し、クラスアクセスは public クラスと設定しています。
 * 
 * @author IGA Tosiki
 */
public class BlancoMailSenderImpl implements BlancoMailSender {
    /**
     * blancoMailCoreメッセージクラス。
     */
    protected final BlancoMailCoreMessageResourceBundle fMsg = new BlancoMailCoreMessageResourceBundle();

    /**
     * 統計情報オブジェクト。デフォルトではnullがセットされている点に注意してください。
     */
    protected BlancoMailStatistics fStatistics = null;

    /**
     * コンストラクタは隠します。
     * 
     * Factoryからアクセス可能になるようにパッケージアクセスを許容しています。
     */
    BlancoMailSenderImpl() {
    }

    /**
     * メールサーバに、メールを送信します。
     * 
     * @param argSetting
     *            blancoMailの各種設定情報をあらわすバリューオブジェクト。サーバ接続設定や送受信時のデフォルト値などが格納されます。(注意)このクラスのインスタンスは
     *            BlancoMailSettingFactory クラスから取得してください。
     * @param argMessage
     *            送信したいメール本体をあらわすバリューオブジェクト。このオブジェクトにメールの本文や宛先などの情報を入れておきます。
     * @throws ConnectException
     *             メールサーバへの接続失敗に関する例外。
     * @throws IOException
     *             メールデータ送信失敗に関する例外。
     */
    public void send(final BlancoMailSetting argSetting,
            final BlancoMailMessage argMessage) throws ConnectException,
            IOException {
        // パラメータチェック。
        if (argSetting == null) {
            throw new IllegalArgumentException(fMsg.getMbmcs023());
        }
        if (argMessage == null) {
            throw new IllegalArgumentException(fMsg.getMbmcs024());
        }

        if (fStatistics == null) {
            // 統計情報オブジェクトが設定されていない場合には これを設定します。
            // これにより、メソッド内では fStatisticsは常に非nullであることを保障します。
            fStatistics = new BlancoMailStatistics();
        }

        // BlancoMailSetting内容の妥当性の検証。
        checkSetting(argSetting);

        // メッセージオブジェクト内容の妥当性の検証。
        checkMessage(argSetting, argMessage);

        final Properties props = new Properties();
        prepareSessionProperties(argSetting, props);

        prepareSessionPropertiesWithMessage(argSetting, argMessage, props);

        if (argSetting.getTestMode()) {
            new BlancoMailTestStub().send(argSetting, argMessage);
            // 実際のメール送信処理は実施しません。すぐに returnします。
            return;
        }

        // 通信開始
        final long startTimeMillis = System.currentTimeMillis();

        final Session session = BlancoMailCoreUtil.getSendSession(argSetting,
                props);
        final ByteArrayOutputStream outWorkByteArrayOutputStream = BlancoMailCoreUtil
                .beginSessionLogOutputStream(argSetting, session);

        final Transport transport = connectTransport(argSetting, session);

        boolean isSuccessEnd = false;

        final MimeMessage mimeMessage = new MimeMessage(session);
        buildMessage(argSetting, argMessage, mimeMessage);

        try {
            // メール送信。
            transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());

            // 正常終了フラグをセットします。
            isSuccessEnd = true;

            // 統計情報の更新。
            fStatistics.setSendCount(fStatistics.getSendCount() + 1);
            // 統計情報のうち、送信サイズ情報を更新。
            long sendSize = argMessage.getText().getBytes(
                    argSetting.getSendEncoding()).length;
            for (int index = 0; index < argMessage.getAttachmentList().size(); index++) {
                final BlancoMailMessageAttachment attachment = (BlancoMailMessageAttachment) argMessage
                        .getAttachmentList().get(index);
                sendSize += attachment.getData().length;
            }
            fStatistics.setSendSize(fStatistics.getSendSize() + sendSize);

        } catch (MessagingException e) {
            fStatistics.setSendErrorCount(fStatistics.getSendErrorCount() + 1);
            throw new IOException(fMsg.getMbmcs301(e.toString()));
        } finally {
            try {
                transport.close();
            } catch (MessagingException e) {
                // 接続クローズ例外については、これは例外扱いとはしません。
                // TODO ロギングシステム導入時には、これをログ出力してください。
                System.out.println(fMsg.getMbmcs302(e.toString()));
            }

            // 通信終了
            final long endTimeMillis = System.currentTimeMillis();

            // 送信時間の統計情報を更新。
            fStatistics.setSendTime(fStatistics.getSendTime()
                    + (endTimeMillis - startTimeMillis));

            // メールサーバとの一連のやりとりをセッションログファイルに出力します。

            String sessionLogDirectory = null;
            if (isSuccessEnd) {
                sessionLogDirectory = argSetting
                        .getSessionLogDirectorySuccess();
            } else {
                sessionLogDirectory = argSetting.getSessionLogDirectoryError();
            }

            if (BlancoStringUtil.null2Blank(sessionLogDirectory).length() > 0) {
                final BlancoLogBinaryLogger logger = new BlancoLogBinaryLogger(
                        sessionLogDirectory, "mail", "s", "log");
                try {
                    logger.log(outWorkByteArrayOutputStream.toByteArray());
                } catch (IOException e) {
                    throw new IOException(fMsg.getMbmcs305(e.toString()));
                }
            }
        }
    }

    /**
     * 統計情報取得のためのバリューオブジェクトをセットします。
     * 
     * @param argStatistics
     *            統計情報。
     */
    public void setStatistics(final BlancoMailStatistics argStatistics) {
        fStatistics = argStatistics;
    }

    /**
     * 統計情報取得のためのバリューオブジェクトをゲットします。
     * 
     * @return 統計情報。
     */
    public BlancoMailStatistics getStatistics() {
        return fStatistics;
    }

    /**
     * blancoMailCore API バリューオブジェクト形式のメール内容をもとに、JavaMail API
     * メッセージオブジェクトにセットします。
     * 
     * 動作の前提条件。事前に checkMessage メソッドを呼び出し、argMessage内容の妥当性がチェック完了していること。
     * 
     * @param argSetting
     *            blancoMailの各種設定情報をあらわすバリューオブジェクト。サーバ接続設定や送受信時のデフォルト値などが格納されます。(注意)このクラスのインスタンスは
     *            BlancoMailSettingFactory クラスから取得してください。
     * @param argMessage
     *            送信したいメール本体をあらわすバリューオブジェクト。このオブジェクトの内容を入力とします。
     * @param argJavaMailMessage
     *            JavaMail APIのメッセージオブジェクト。
     * @throws IOException
     *             メールデータ送信失敗に関する例外。
     */
    private void buildMessage(final BlancoMailSetting argSetting,
            final BlancoMailMessage argMessage,
            final MimeMessage argJavaMailMessage) throws IOException {
        if (argMessage == null) {
            throw new IllegalArgumentException(
                    "BlancoMailSender#buildMessage: メソッドのパラメータ(argMessage)に nullが渡されました。このオブジェクトは nullであってはなりません。");
        }
        if (argJavaMailMessage == null) {
            throw new IllegalArgumentException(
                    "BlancoMailSender#buildMessage: メソッドのパラメータ(argMimeMessage)に nullが渡されました。このオブジェクトは nullであってはなりません。");
        }

        // メッセージ内容の設定。
        try {
            // Fromの設定。
            final Address addrFrom = BlancoMailCoreUtil.toInternetAddress(
                    argSetting, argMessage.getFrom());
            argJavaMailMessage.setFrom(addrFrom);

            // Toの設定。
            for (int index = 0; index < argMessage.getTo().size(); index++) {
                final Address addrTo = BlancoMailCoreUtil.toInternetAddress(
                        argSetting, (BlancoMailAddress) argMessage.getTo().get(
                                index));
                argJavaMailMessage.addRecipient(Message.RecipientType.TO,
                        addrTo);
            }

            // Ccの設定。
            for (int index = 0; index < argMessage.getCc().size(); index++) {
                final Address addrCc = BlancoMailCoreUtil.toInternetAddress(
                        argSetting, (BlancoMailAddress) argMessage.getCc().get(
                                index));
                argJavaMailMessage.addRecipient(Message.RecipientType.CC,
                        addrCc);
            }

            // Bccの設定。
            for (int index = 0; index < argMessage.getBcc().size(); index++) {
                final Address addrBcc = BlancoMailCoreUtil.toInternetAddress(
                        argSetting, (BlancoMailAddress) argMessage.getBcc()
                                .get(index));
                argJavaMailMessage.addRecipient(Message.RecipientType.BCC,
                        addrBcc);
            }

            // ReplyToの設定。
            if (argMessage.getReplyTo() != null) {
                // ReplyToの指定がある場合にのみ設定します。
                final Address addrReplyTo = BlancoMailCoreUtil
                        .toInternetAddress(argSetting, argMessage.getReplyTo());
                argJavaMailMessage.setReplyTo(new Address[] { addrReplyTo });
            }

            // Subjectの設定。
            argJavaMailMessage.setSubject(argMessage.getSubject(), argSetting
                    .getSendEncoding());

            // メール本文の設定。
            if (argMessage.getAttachmentList().size() == 0) {
                // 添付ファイルなしの場合。
                // setTextメソッドの呼び出しにより、Content-Typeは自動的に[text/plain]になる。

                argJavaMailMessage.setText(getActualMessageText(argSetting,
                        argMessage), argSetting.getSendEncoding());

                if (argSetting.getSendForthContentTransferEncoding7bit()) {
                    // setTextを呼び出した後に、ヘッダーを 7bitへと上書きします。
                    // これは、一部のケータイメールが quoted-printable を処理できないことへの対策となります。
                    argJavaMailMessage.setHeader("Content-Transfer-Encoding",
                            "7bit");
                }
            } else {
                // 添付ファイルありの場合。

                // ここからは、マルチパートのメール本文処理となります。
                final Multipart multipart = new MimeMultipart();

                // テキスト本文。
                final MimeBodyPart textPart = new MimeBodyPart();

                textPart.setText(getActualMessageText(argSetting, argMessage),
                        argSetting.getSendEncoding());

                // メールのメッセージボディであることを主張します。
                textPart.setHeader("Content-Description", "Mail message body");

                if (argSetting.getSendForthContentTransferEncoding7bit()) {
                    // setTextを呼び出した後に、ヘッダーを 7bitへと上書きします。
                    // これは、一部のケータイメールが quoted-printable を処理できないことへの対策となります。
                    textPart.setHeader("Content-Transfer-Encoding", "7bit");
                }

                multipart.addBodyPart(textPart);

                // 添付ファイルの数だけ添付処理をおこないます。
                for (int indexAttachment = 0; indexAttachment < argMessage
                        .getAttachmentList().size(); indexAttachment++) {

                    final BlancoMailMessageAttachment attachment = (BlancoMailMessageAttachment) argMessage
                            .getAttachmentList().get(indexAttachment);

                    // 添付ファイル部分
                    final BodyPart attachmentPart = new MimeBodyPart();
                    // 常にバイナリデータであるものとしてファイルを添付する。
                    attachmentPart.setDataHandler(new DataHandler(
                            new ByteArrayDataSource(attachment.getData(),
                                    attachment.getMimeType())));

                    // 添付ファイル名をセット
                    // MimeUtility.encodeText は 添付ファイル名にascii以外の文字が入る場合の対策。
                    attachmentPart.setFileName(MimeUtility.encodeText(
                            attachment.getFileName(), argSetting
                                    .getSendEncoding(), null));

                    multipart.addBodyPart(attachmentPart);
                }

                argJavaMailMessage.setContent(multipart);
            }

            // ユーザ指定のヘッダーをセットします。(addHeaderメソッドを使って「追加」します)
            for (int index = 0; index < argMessage.getHeaderList().size(); index++) {
                final BlancoMailMessageHeader header = (BlancoMailMessageHeader) argMessage
                        .getHeaderList().get(index);
                argJavaMailMessage.addHeader(header.getName(), header
                        .getValue());
            }

            // その他の付加情報。
            argJavaMailMessage.addHeader("X-Mailer", argSetting
                    .getSendMailerName()
                    + " ("
                    + BlancoMailCoreConstants.PRODUCT_NAME
                    + " "
                    + BlancoMailCoreConstants.VERSION + ")");

            // 送信日付を設定
            argJavaMailMessage.setSentDate(argMessage.getSentDate());

        } catch (MessagingException e) {
            fStatistics.setSendErrorCount(fStatistics.getSendErrorCount() + 1);
            throw new IOException(fMsg.getMbmcs303(e.toString()));
        } catch (UnsupportedEncodingException e) {
            fStatistics.setSendErrorCount(fStatistics.getSendErrorCount() + 1);
            throw new IOException(fMsg.getMbmcs304(e.toString()));
        }
    }

    /**
     * メッセージオブジェクト内容の妥当性を検証します。
     * 
     * うんざりとする入力チェック系はここで実装します。また、必要に応じて 最低限レベルの不足設定項目への値のセットを実施します。
     * 
     * @param argSetting
     *            blancoMailの各種設定情報をあらわすバリューオブジェクト。サーバ接続設定や送受信時のデフォルト値などが格納されます。(注意)このクラスのインスタンスは
     *            BlancoMailSettingFactory クラスから取得してください。
     * @param argMessage
     *            送信したいメール本体をあらわすバリューオブジェクト。このオブジェクトの内容をチェックします。
     */
    private void checkMessage(final BlancoMailSetting argSetting,
            final BlancoMailMessage argMessage) {
        if (argMessage == null) {
            throw new IllegalArgumentException(
                    "BlancoMailSender#checkMessage: メソッドのパラメータ(argMessage)に nullが渡されました。このオブジェクトは nullであってはなりません。");
        }

        if (argMessage.getFrom() == null) {
            throw new IllegalArgumentException(fMsg.getMbmcs001());
        }
        if (BlancoStringUtil.null2Blank(argMessage.getFrom().getAddress())
                .length() == 0) {
            throw new IllegalArgumentException(fMsg.getMbmcs028(argMessage
                    .getFrom().toString()));
        }
        if (argMessage.getTo() == null) {
            throw new IllegalArgumentException(fMsg.getMbmcs002());
        }
        if (argMessage.getTo().size() == 0) {
            throw new IllegalArgumentException(fMsg.getMbmcs003());
        }
        if (argMessage.getTo().size() > argSetting.getSendMaxRecipientAddrTo()) {
            // Toの個数が所定の個数を超えた場合にはエラー扱いにする。
            throw new IllegalArgumentException(fMsg.getMbmcs004(String
                    .valueOf(argMessage.getTo().size()), String
                    .valueOf(argSetting.getSendMaxRecipientAddrTo())));
        }

        if (argMessage.getCc() == null) {
            // Ccにnullがセットされている場合には、これを適切に初期化する。
            argMessage.setCc(new ArrayList());
        }
        if (argMessage.getCc().size() > argSetting.getSendMaxRecipientAddrCc()) {
            // Ccの個数が所定の個数を超えた場合にはエラー扱いにする。
            throw new IllegalArgumentException(fMsg.getMbmcs005(String
                    .valueOf(argMessage.getCc().size()), String
                    .valueOf(argSetting.getSendMaxRecipientAddrCc())));
        }

        if (argMessage.getBcc() == null) {
            // Bccにnullがセットされている場合には、これを適切に初期化する。
            argMessage.setBcc(new ArrayList());
        }
        // Bccの個数チェックは実施しません。Bccは いくつでも登録できるものとします。

        // Subjectは適切に設定されている必要があります。
        if (argMessage.getSubject() == null) {
            throw new IllegalArgumentException(fMsg.getMbmcs009());
        }
        if (argMessage.getSubject().trim().length() == 0) {
            throw new IllegalArgumentException(fMsg.getMbmcs010());
        }

        if (argMessage.getText() == null) {
            // 本文が無い状態は例外扱いとします。
            throw new IllegalArgumentException(fMsg.getMbmcs006());
        }

        // メールヘッダーのチェック。
        if (argMessage.getHeaderList() == null) {
            throw new IllegalArgumentException(fMsg.getMbmcs007());
        }
        for (int index = 0; index < argMessage.getHeaderList().size(); index++) {
            final Object objLook = argMessage.getHeaderList().get(index);
            if (objLook instanceof BlancoMailMessageHeader == false) {
                throw new IllegalArgumentException(fMsg.getMbmcs008(objLook
                        .getClass().toString(), objLook.toString()));
            }
        }

        // メール宛先のチェック。

        final HashMap mapTo = new HashMap();
        for (int index = 0; index < argMessage.getTo().size(); index++) {
            final Object objLook = argMessage.getTo().get(index);
            if (objLook instanceof BlancoMailAddress == false) {
                throw new IllegalArgumentException(fMsg.getMbmcs011(objLook
                        .getClass().toString(), objLook.toString()));
            }

            final BlancoMailAddress addr = (BlancoMailAddress) objLook;
            if (BlancoStringUtil.null2Blank(addr.getAddress()).length() == 0) {
                throw new IllegalArgumentException(fMsg.getMbmcs025(addr
                        .toString()));
            }

            // To宛先内に重複する同一アドレスがないかどうかチェック。
            if (mapTo.get(addr.getAddress()) != null) {
                throw new IllegalArgumentException(fMsg.getMbmcs012(addr
                        .getAddress(), addr.toString()));
            }
            mapTo.put(addr.getAddress(), addr);
        }

        final HashMap mapCc = new HashMap();
        for (int index = 0; index < argMessage.getCc().size(); index++) {
            final Object objLook = argMessage.getCc().get(index);
            if (objLook instanceof BlancoMailAddress == false) {
                throw new IllegalArgumentException(fMsg.getMbmcs013(objLook
                        .getClass().toString(), objLook.toString()));
            }

            final BlancoMailAddress addr = (BlancoMailAddress) objLook;
            if (BlancoStringUtil.null2Blank(addr.getAddress()).length() == 0) {
                throw new IllegalArgumentException(fMsg.getMbmcs026(addr
                        .toString()));
            }

            // Cc宛先内に重複する同一アドレスがないかどうかチェック。
            if (mapCc.get(addr.getAddress()) != null) {
                throw new IllegalArgumentException(fMsg.getMbmcs014(addr
                        .getAddress(), addr.toString()));
            }
            mapCc.put(addr.getAddress(), addr);
        }

        final HashMap mapBcc = new HashMap();
        for (int index = 0; index < argMessage.getBcc().size(); index++) {
            final Object objLook = argMessage.getBcc().get(index);
            if (objLook instanceof BlancoMailAddress == false) {
                throw new IllegalArgumentException(fMsg.getMbmcs015(objLook
                        .getClass().toString(), objLook.toString()));
            }

            final BlancoMailAddress addr = (BlancoMailAddress) objLook;
            if (BlancoStringUtil.null2Blank(addr.getAddress()).length() == 0) {
                throw new IllegalArgumentException(fMsg.getMbmcs027(addr
                        .toString()));
            }

            // Bcc宛先内に重複する同一アドレスがないかどうかチェック。
            if (mapBcc.get(addr.getAddress()) != null) {
                throw new IllegalArgumentException(fMsg.getMbmcs016(addr
                        .getAddress(), addr.toString()));
            }
            mapBcc.put(addr.getAddress(), addr);
        }

        // メッセージ内容について未設定フィールドがあれば 値の調整。
        if (argMessage.getSentDate() == null) {
            // 送信日付が未設定の場合には マシン日付をセットします。
            argMessage.setSentDate(new Date());
        }

        // 添付ファイルの内容チェックをおこないます。
        if (argMessage.getAttachmentList() == null) {
            throw new IllegalArgumentException(fMsg.getMbmcs017());
        }
        // 添付ファイルのトータルサイズ。
        long totalAttachmentSize = 0;
        for (int indexAttachment = 0; indexAttachment < argMessage
                .getAttachmentList().size(); indexAttachment++) {
            final Object objAttachment = argMessage.getAttachmentList().get(
                    indexAttachment);

            if (objAttachment instanceof BlancoMailMessageAttachment == false) {
                // objAttachment.toString()は呼び出さないようにしています。
                throw new IllegalArgumentException(fMsg
                        .getMbmcs018(objAttachment.getClass().toString()));
            }

            final BlancoMailMessageAttachment attachment = (BlancoMailMessageAttachment) argMessage
                    .getAttachmentList().get(indexAttachment);

            if (attachment.getData() == null) {
                // objAttachment.toString()は呼び出さないようにしています。
                throw new IllegalArgumentException(fMsg.getMbmcs019());
            }

            // 添付ファイルのサイズを積算します。
            totalAttachmentSize += attachment.getData().length;

            if (BlancoStringUtil.null2Blank(attachment.getMimeType()).length() == 0) {
                // objAttachment.toString()は呼び出さないようにしています。
                throw new IllegalArgumentException(fMsg.getMbmcs020());
            }
            if (BlancoStringUtil.null2Blank(attachment.getFileName()).length() == 0) {
                // objAttachment.toString()は呼び出さないようにしています。
                throw new IllegalArgumentException(fMsg.getMbmcs021());
            }
        }
        if (totalAttachmentSize > argSetting.getSendMaxTotalAttachmentSize()) {
            // 添付ファイルのトータルサイズがシステムとしての最大許容サイズを超えている場合。
            throw new IllegalArgumentException(fMsg.getMbmcs022(String
                    .valueOf(totalAttachmentSize), String.valueOf(argSetting
                    .getSendMaxTotalAttachmentSize())));
        }
    }

    /**
     * BlancoMailSettingの内容の妥当性を検証します。
     * 
     * @param argSetting
     *            blancoMailの各種設定情報をあらわすバリューオブジェクト。サーバ接続設定や送受信時のデフォルト値などが格納されます。(注意)このクラスのインスタンスは
     *            BlancoMailSettingFactory クラスから取得してください。
     */
    private void checkSetting(final BlancoMailSetting argSetting) {
        if (argSetting == null) {
            throw new IllegalArgumentException(
                    "BlancoMailSender#checkSetting: メソッドのパラメータ(argSetting)に nullが渡されました。このオブジェクトは nullであってはなりません。");
        }

        // BlancoMailSettingの内容をチェック。
        if (argSetting.getSendHost() == null) {
            throw new IllegalArgumentException(fMsg.getMbmcs101());
        }
        if (argSetting.getSendSmtpAuth()) {
            // SMTP認証が有効である場合にはユーザ名・パスワードの設定状況をチェック。
            if (argSetting.getSendUserName() == null) {
                throw new IllegalArgumentException(fMsg.getMbmcs102());
            }
            if (argSetting.getSendPassword() == null) {
                throw new IllegalArgumentException(fMsg.getMbmcs103());
            }
        }
        if (argSetting.getSendMailerName() == null) {
            throw new IllegalArgumentException(fMsg.getMbmcs104());
        }
        if (argSetting.getSendEncoding() == null) {
            throw new IllegalArgumentException(fMsg.getMbmcs105());
        }

        if (Charset.isSupported(argSetting.getSendEncoding()) == false) {
            throw new IllegalArgumentException(fMsg.getMbmcs106(argSetting
                    .getSendEncoding()));
        }

        // メール送受信で共通の BlancoMailSettingの設定内容チェック。
        BlancoMailCoreUtil.checkSetting(argSetting);
    }

    /**
     * JavaMail API セッションオブジェクト生成時に必要となるプロパティオブジェクトを準備します。
     * 
     * @param argSetting
     *            blancoMailの各種設定情報をあらわすバリューオブジェクト。サーバ接続設定や送受信時のデフォルト値などが格納されます。(注意)このクラスのインスタンスは
     *            BlancoMailSettingFactory クラスから取得してください。
     * @param argProps
     *            接続情報に関するプロパティオブジェクト。このオブジェクトにJavaMail API呼び出し時の接続情報がセットされます。
     */
    private void prepareSessionProperties(final BlancoMailSetting argSetting,
            final Properties argProps) {
        if (argSetting == null) {
            throw new IllegalArgumentException(
                    "BlancoMailSender#prepareSessionProperties: メソッドのパラメータ(argSetting)に nullが渡されました。このオブジェクトは nullであってはなりません。");
        }
        if (argProps == null) {
            throw new IllegalArgumentException(
                    "BlancoMailSender#prepareSessionProperties: メソッドのパラメータ(argProps)に nullが渡されました。このオブジェクトは nullであってはなりません。");
        }

        // 基本情報。
        argProps.setProperty("mail.smtp.host", argSetting.getSendHost());
        if (argSetting.getSendPort() >= 0) {
            // ポート番号を変更。
            argProps.setProperty("mail.smtp.port", String.valueOf(argSetting
                    .getSendPort()));
        }

        // タイムアウト設定
        if (argSetting.getMailConnectionTimeout() > 0) {
            argProps.setProperty("mail.smtp.connectiontimeout", String
                    .valueOf(argSetting.getMailConnectionTimeout()));
        }
        if (argSetting.getMailTimeout() > 0) {
            argProps.setProperty("mail.smtp.timeout", String.valueOf(argSetting
                    .getMailTimeout()));
        }

        // 認証
        if (argSetting.getSendSmtpAuth()) {
            argProps.setProperty("mail.smtp.auth", "true");
        }

        if (argSetting.getSendUseSsl()) {
            // SSL関連設定
            argProps.setProperty("mail.smtp.socketFactory.class", argSetting
                    .getSocketfactoryClass());
            argProps.setProperty("mail.smtp.socketFactory.fallback", String
                    .valueOf(argSetting.getSocketfactoryFallback()));
            if (argSetting.getSendPort() >= 0) {
                // ポート番号を変更。
                argProps.setProperty("mail.smtp.socketFactory.port", String
                        .valueOf(argSetting.getSendPort()));
            }
        }

        if (BlancoStringUtil.null2Blank(argSetting.getSendLocalHost()).length() > 0) {
            // SMTPサーバによっては、ローカルホスト名にドメイン名が含まれていないと接続拒否するものが存在するようです。
            // そのような場合には、ローカルホスト名を明示的に指定して動作させる必要が出てきます。
            argProps.setProperty("mail.smtp.localhost", argSetting
                    .getSendLocalHost());
        }
    }

    /**
     * JavaMail API セッションオブジェクト生成時に必要となるプロパティオブジェクトについて、メッセージから設定すべき値を準備します。
     * 
     * @param argSetting
     *            blancoMailの各種設定情報をあらわすバリューオブジェクト。サーバ接続設定や送受信時のデフォルト値などが格納されます。(注意)このクラスのインスタンスは
     *            BlancoMailSettingFactory クラスから取得してください。
     * @param argMessage
     *            送信したいメール本体をあらわすバリューオブジェクト。このオブジェクトの内容をチェックします。
     * @param argProps
     *            接続情報に関するプロパティオブジェクト。このオブジェクトにJavaMail API呼び出し時の接続情報がセットされます。
     */
    private void prepareSessionPropertiesWithMessage(
            final BlancoMailSetting argSetting,
            final BlancoMailMessage argMessage, final Properties argProps) {
        if (argSetting == null) {
            throw new IllegalArgumentException(
                    "BlancoMailSender#prepareSessionPropertiesWithMessage: メソッドのパラメータ(argSetting)に nullが渡されました。このオブジェクトは nullであってはなりません。");
        }
        if (argMessage == null) {
            throw new IllegalArgumentException(
                    "BlancoMailSender#prepareSessionPropertiesWithMessage: メソッドのパラメータ(argMessage)に nullが渡されました。このオブジェクトは nullであってはなりません。");
        }
        if (argProps == null) {
            throw new IllegalArgumentException(
                    "BlancoMailSender#prepareSessionPropertiesWithMessage: メソッドのパラメータ(argProps)に nullが渡されました。このオブジェクトは nullであってはなりません。");
        }

        if (argSetting.getSendUseFromAddressAsMessageId()) {
            // Fromアドレスを、Message-IDの一部に利用するように設定します。
            // 具体的には mail.from に対して「Fromアドレス」を設定します。
            // Fromアドレスのユニーク性をMessage-IDへと活用した方法です。
            argProps
                    .setProperty("mail.from", argMessage.getFrom().getAddress());
        }
    }

    /**
     * JavaMail API を経由してトランスポート(メールサーバ)に接続します。
     * 
     * @param argSetting
     *            blancoMailの各種設定情報をあらわすバリューオブジェクト。サーバ接続設定や送受信時のデフォルト値などが格納されます。(注意)このクラスのインスタンスは
     *            BlancoMailSettingFactory クラスから取得してください。
     * @param argSession
     *            JavaMailのセッションオブジェクト。
     * @return JavaMailのトランスポートオブジェクト。
     * @throws ConnectException
     *             メールサーバへの接続失敗に関する例外。
     */
    private Transport connectTransport(final BlancoMailSetting argSetting,
            final Session argSession) throws ConnectException {
        if (argSetting == null) {
            throw new IllegalArgumentException(
                    "BlancoMailSender#connectTransport: メソッドのパラメータ(argSetting)に nullが渡されました。このオブジェクトは nullであってはなりません。");
        }
        if (argSession == null) {
            throw new IllegalArgumentException(
                    "BlancoMailSender#connectTransport: メソッドのパラメータ(argSession)に nullが渡されました。このオブジェクトは nullであってはなりません。");
        }

        Transport transport = null;

        try {
            // 指定のプロバイダの取得に失敗した場合 (トランスポート層の取得時)。
            transport = argSession.getTransport(argSetting.getSendProtocol());
        } catch (NoSuchProviderException e) {
            fStatistics.setSendErrorCount(fStatistics.getSendErrorCount() + 1);
            throw new ConnectException(fMsg.getMbmcs201(argSetting
                    .getSendProtocol(), e.toString()));
        }

        try {
            transport.connect();
        } catch (AuthenticationFailedException e) {
            // 認証失敗は ここに入ります。
            fStatistics.setSendErrorCount(fStatistics.getSendErrorCount() + 1);
            throw new ConnectException(fMsg.getMbmcs202(argSetting
                    .getSendUserName(), e.toString()));
        } catch (MessagingException e) {
            // smtpサーバへの接続失敗は ここに入ります。
            fStatistics.setSendErrorCount(fStatistics.getSendErrorCount() + 1);
            throw new ConnectException(fMsg.getMbmcs203(argSetting
                    .getSendHost(), e.toString()));
        }

        return transport;
    }

    /**
     * 実際にJavaMailに引き渡すメッセージ本文文字列を取得します。
     * 
     * @param argSetting
     *            blancoMailの各種設定情報をあらわすバリューオブジェクト。サーバ接続設定や送受信時のデフォルト値などが格納されます。(注意)このクラスのインスタンスは
     *            BlancoMailSettingFactory クラスから取得してください。
     * @param argMessage
     *            送信したいメール本体をあらわすバリューオブジェクト。
     * @return 加工後のメッセージ本文。(設定値に従い改行を追加)
     */
    private String getActualMessageText(final BlancoMailSetting argSetting,
            final BlancoMailMessage argMessage) {
        if (argSetting.getSendForthNewlineAfterMessageText()) {
            // 日本のメールソフト事情に対応するために、本文テキストの後に空行を１行追加しています。
            // 添付ファイルなしメールの場合にも、問題が発生することを予防するために空行を１行追加しています。
            // 添付ファイル付きメールの場合、本文テキストの後に空行が１行無いと 適切に添付ファイル処理出来ないメーラへの対応です。
            return argMessage.getText() + "\n";
        } else {
            return argMessage.getText();
        }
    }
}