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