    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.fukurou.mail;
    017
    018import java.io.InputStream;
    019import java.io.OutputStream;
    020// import java.io.ByteArrayOutputStream;
    021import java.io.ByteArrayInputStream;
    022import java.io.UnsupportedEncodingException;
    023import java.io.IOException;
    024
    025import javax.activation.DataHandler;
    026import javax.activation.DataSource;
    027import javax.mail.internet.InternetAddress;
    028import javax.mail.internet.MimeMessage;
    029import javax.mail.internet.MimeUtility;
    030import javax.mail.MessagingException;
    031import com.sun.mail.util.BASE64EncoderStream;
    032
    033import java.nio.charset.Charset;                // 5.5.2.6 (2012/05/25)
    034
    035/**
    036 * MailCharsetFactory は、MailCharset インターフェースを実装したサブクラスを
    037 * 作成する ファクトリクラスです。
    038 *
    039 * 引数のキャラクタセット名が、Windows-31J 、MS932 の場合は、
    040 * 『１．Windows-31J + 8bit 送信』 の実装である、Mail_Windows31J_Charset
    041 * サブクラスを返します。
    042 * それ以外が指定された場合は、ISO-2022-JP を使用して、『２．ISO-2022-JP に独自変換 + 7bit 送信』
    043 * の実装である、Mail_ISO2022JP_Charset サブクラスを返します。
    044 *
    045 * @version  4.0
    046 * @author   Kazuhiko Hasegawa
    047 * @since    JDK5.0,
    048 */
    049class MailCharsetFactory {
    050
    051        /**
    052         * インスタンスの生成を抑止します。
    053         */
    054        private MailCharsetFactory() {
    055                // 何もありません。(PMD エラー回避)
    056        }
    057
    058        /**
    059         * キャラクタセットに応じた、MailCharset オブジェクトを返します。
    060         *
    061         * Windows-31J 、MS932 、Shift_JIS の場合は、Mail_Windows31J_Charset
    062         * その他は、ISO-2022-JP として、Mail_ISO2022JP_Charset を返します。
    063         *
    064         * 注意：null の場合は、デフォルトではなく、Mail_ISO2022JP_Charset を返します。
    065         *
    066         * @param  charset キャラクタセット[Windows-31J/MS932/Shift_JIS/その他]
    067         *
    068         * @return MailCharset
    069         */
    070        static MailCharset newInstance( final String charset ) {
    071                final MailCharset mcset;
    072
    073                if( "MS932".equalsIgnoreCase( charset ) ||
    074                        "Shift_JIS".equalsIgnoreCase( charset ) ||
    075                        "Windows-31J".equalsIgnoreCase( charset ) ) {
    076                                mcset = new Mail_Windows31J_Charset( charset );
    077                }
    078                else {
    079                        mcset = new Mail_ISO2022JP_Charset();
    080                }
    081                return mcset ;
    082        }
    083
    084        /**
    085         * MailCharset インターフェースを実装した Windwos-31J エンコード時のサブクラスです。
    086         *
    087         * 『１．Windows-31J + 8bit 送信』 の実装です。
    088         *
    089         * @version  4.0
    090         * @author   Kazuhiko Hasegawa
    091         * @since    JDK5.0,
    092         */
    093        static class Mail_Windows31J_Charset implements MailCharset {
    094                private final String charset ;                  // "Windows-31J" or "MS932"
    095
    096                /**
    097                 * 引数に、エンコード方式を指定して、作成するコンストラクタです。
    098                 *
    099                 * @param charset String
    100                 */
    101                public Mail_Windows31J_Charset( final String charset ) {
    102                        this.charset = charset;
    103                }
    104
    105                /**
    106                 * テキストをセットします。
    107                 * Part#setText() の代わりにこちらを使うようにします。
    108                 *
    109                 * ※ 内部で、MessagingException が発生した場合は、RuntimeException に変換されて throw されます。
    110                 *
    111                 * @param mimeMsg MimeMessage
    112                 * @param text    String
    113                 */
    114                public void setTextContent( final MimeMessage mimeMsg, final String text ) {
    115                        try {
    116                                mimeMsg.setText( text,charset );                // "text/plain" Content
    117                        }
    118                        catch( MessagingException ex ) {
    119                                String errMsg = "指定のテキストをセットできません。"
    120                                                                        + "text=" + text + " , charset=" + charset ;
    121                                throw new RuntimeException( errMsg,ex );
    122                        }
    123                }
    124
    125                /**
    126                 * 日本語を含むヘッダ用テキストを生成します。
    127                 * 変換結果は ASCII なので、これをそのまま setSubject や InternetAddress
    128                 * のパラメタとして使用してください。
    129                 *
    130                 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
    131                 *
    132                 * @param text    String
    133                 *
    134                 * @return      日本語を含むヘッダ用テキスト
    135                 */
    136                public String encodeWord( final String text ) {
    137                        try {
    138                                return MimeUtility.encodeText( text, charset, "B" );
    139                        }
    140                        catch( UnsupportedEncodingException ex ) {
    141                                String errMsg = "指定のエンコードが出来ません。"
    142                                                                        + "text=" + text + " , charset=" + charset ;
    143                                throw new RuntimeException( errMsg,ex );
    144                        }
    145                }
    146
    147                /**
    148                 * 日本語を含むアドレスを生成します。
    149                 * personal に、日本語が含まれると想定しています。
    150                 * サブクラスで、日本語処理を行う場合の方法は、それぞれ異なります。
    151                 *
    152                 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
    153                 *
    154                 * @param address    String
    155                 * @param personal   String
    156                 *
    157                 * @return InternetAddress
    158                 */
    159                public InternetAddress getAddress( final String address,final String personal ) {
    160                        try {
    161                                return new InternetAddress( address,personal,charset );
    162                        }
    163                        catch( UnsupportedEncodingException ex ) {
    164                                String errMsg = "指定のエンコードが出来ません。"
    165                                                                        + "address=" + address + " , charset=" + charset ;
    166                                throw new RuntimeException( errMsg,ex );
    167                        }
    168                }
    169
    170                /**
    171                 * Content-Transfer-Encoding を指定する場合の ビット数を返します。
    172                 *
    173                 * Windows系は、8bit / ISO-2022-JP 系は、7bit になります。
    174                 *
    175                 * @return      ビット数("8bit" 固定)
    176                 */
    177                public String getBit() {
    178                        return "8bit" ;
    179                }
    180        }
    181
    182        /**
    183         * MailCharset インターフェースを実装した ISO-2022-JP エンコード時のサブクラスです。
    184         *
    185         * 『２．ISO-2022-JP に独自変換 + 7bit 送信』 の実装です。
    186         *
    187         * @version  4.0
    188         * @author   Kazuhiko Hasegawa
    189         * @since    JDK5.0,
    190         */
    191        static class Mail_ISO2022JP_Charset implements MailCharset {
    192
    193                /**
    194                 * プラットフォーム依存のデフォルトの Charset です。
    195                 * プラットフォーム依存性を考慮する場合、エンコード指定で作成しておく事をお勧めします。
    196                 *
    197                 * @og.rev 5.5.2.6 (2012/05/25) findbugs対応
    198                 */
    199                private static final Charset DEFAULT_CHARSET = Charset.defaultCharset() ;
    200
    201                /**
    202                 * テキストをセットします。
    203                 * Part#setText() の代わりにこちらを使うようにします。
    204                 *
    205                 * ※ 内部で、MessagingException が発生した場合は、RuntimeException に変換されて throw されます。
    206                 *
    207                 * @param mimeMsg MimeMessage
    208                 * @param text    String
    209                 */
    210                public void setTextContent( final MimeMessage mimeMsg, final String text ) {
    211                        try {
    212                                // mimeMsg.setText(text, "ISO-2022-JP");
    213                                mimeMsg.setDataHandler(new DataHandler(new JISDataSource(text)));
    214                        }
    215                        catch( MessagingException ex ) {
    216                                String errMsg = "指定のテキストをセットできません。"
    217                                                                        + "text=" + text ;
    218                                throw new RuntimeException( errMsg,ex );
    219                        }
    220                }
    221
    222                /**
    223                 * 日本語を含むヘッダ用テキストを生成します。
    224                 * 変換結果は ASCII なので、これをそのまま setSubject や InternetAddress
    225                 * のパラメタとして使用してください。
    226                 *
    227                 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
    228                 *
    229                 * @param text    String
    230                 *
    231                 * @return      日本語を含むヘッダ用テキスト
    232                 */
    233                public String encodeWord( final String text ) {
    234                        try {
    235                                return "=?ISO-2022-JP?B?" +
    236                                        new String(
    237                                                BASE64EncoderStream.encode(
    238                                                        CharCodeConverter.sjisToJis(
    239                                                                UnicodeCorrecter.correctToCP932(text).getBytes("Windows-31J")
    240                                                        )
    241                                                )
    242                                        ,DEFAULT_CHARSET ) + "?=";              // 5.5.2.6 (2012/05/25) findbugs対応
    243                        }
    244                        catch( UnsupportedEncodingException ex ) {
    245                                String errMsg = "指定のエンコードが出来ません。"
    246                                                                        + "text=" + text + " , charset=Windows-31J" ;
    247                                throw new RuntimeException( errMsg,ex );
    248                        }
    249                }
    250
    251                /**
    252                 * 日本語を含むアドレスを生成します。
    253                 * personal に、日本語が含まれると想定しています。
    254                 * サブクラスで、日本語処理を行う場合の方法は、それぞれ異なります。
    255                 *
    256                 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
    257                 *
    258                 * @param address    String
    259                 * @param personal   String
    260                 *
    261                 * @return InternetAddress
    262                 */
    263                public InternetAddress getAddress( final String address,final String personal ) {
    264                        try {
    265                                return new InternetAddress( address,encodeWord( personal ) );
    266                        }
    267                        catch( UnsupportedEncodingException ex ) {
    268                                String errMsg = "指定のエンコードが出来ません。"
    269                                                                        + "address=" + address ;
    270                                throw new RuntimeException( errMsg,ex );
    271                        }
    272                }
    273
    274                /**
    275                 * Content-Transfer-Encoding を指定する場合の ビット数を返します。
    276                 *
    277                 * Windows系は、8bit / ISO-2022-JP 系は、7bit になります。
    278                 *
    279                 * @return      ビット数("7bit" 固定)
    280                 */
    281                public String getBit() {
    282                        return "7bit" ;
    283                }
    284        }
    285
    286        /**
    287         * テキストの本文を送信するための DataSource です。
    288         *
    289         * Windows-31J でバイトコードに変換した後、独自エンコードにて、
    290         * Shift-JIS ⇒ JIS 変換しています。
    291         *
    292         * @version  4.0
    293         * @author   Kazuhiko Hasegawa
    294         * @since    JDK5.0,
    295         */
    296        static class JISDataSource implements DataSource {
    297                private final byte[] data;
    298
    299                public JISDataSource( final String str ) {
    300                        try {
    301                                data = CharCodeConverter.sjisToJis(
    302                                        UnicodeCorrecter.correctToCP932(str).getBytes("Windows-31J"));
    303
    304                        } catch (UnsupportedEncodingException e) {
    305                                String errMsg = "Windows-31J でのエンコーディングが出来ません。" + str;
    306                                throw new RuntimeException( errMsg,e );
    307                        }
    308                }
    309
    310                /**
    311                 * データの MIME タイプを文字列の形で返します。
    312                 * かならず有効なタイプを返すべきです。
    313                 * DataSource の実装がデータタイプを 決定できない場合は、
    314                 * getContentType は "application/octet-stream" を返すことを 提案します。
    315                 *
    316                 * @return      MIME タイプ
    317                 */
    318                public String getContentType() {
    319                        return "text/plain; charset=ISO-2022-JP";
    320                }
    321
    322                /**
    323                 * データを表す InputStream を返します。
    324                 * それができない場合は適切な例外をスローします。
    325                 *
    326                 * @return InputStream
    327                 * @throws IOException ※ このメソッドからは、IOException は throw されません。
    328                 */
    329                public InputStream getInputStream() throws IOException {
    330                        return new ByteArrayInputStream( data );
    331                }
    332
    333                /**
    334                 * データが書込可能なら OutputStream を返します。
    335                 * それができない場合は適切な例外をスローします。
    336                 *
    337                 * ※ このクラスでは実装されていません。
    338                 *
    339                 * @return OutputStream
    340                 * @throws IOException ※ このメソッドを実行すると、必ず throw されます。
    341                 */
    342                public OutputStream getOutputStream() throws IOException {
    343                        String errMsg = "このクラスでは実装されていません。";
    344                //      throw new UnsupportedOperationException( errMsg );
    345                        throw new IOException( errMsg );
    346                }
    347
    348                /**
    349                 * このオブジェクトの '名前' を返します。
    350                 * この名前は下層のオブジェクトの性質によります。
    351                 * ファイルをカプセル化する DataSource なら オブジェクトの
    352                 * ファイル名を返すようにするかもしれません。
    353                 *
    354                 * @return      オブジェクトの名前
    355                 */
    356                public String getName() {
    357                        return "JISDataSource";
    358                }
    359        }
    360}