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