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