    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                public void setTextContent( final MimeMessage mimeMsg, final String text ) {
    128                        try {
    129                                mimeMsg.setText( text,charset );                // "text/plain" Content
    130                        }
    131                        catch( final MessagingException ex ) {
    132                                final String errMsg = "指定のテキストをセットできません。"
    133                                                                                + "text=" + text + " , charset=" + charset ;
    134                                throw new OgRuntimeException( errMsg,ex );
    135                        }
    136                }
    137
    138                /**
    139                 * 日本語を含むヘッダ用テキストを生成します。
    140                 * 変換結果は ASCII なので、これをそのまま setSubject や InternetAddress
    141                 * のパラメタとして使用してください。
    142                 *
    143                 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
    144                 *
    145                 * @param text    テキスト
    146                 *
    147                 * @return      日本語を含むヘッダ用テキスト
    148                 * @og.rtnNotNull
    149                 */
    150                public String encodeWord( final String text ) {
    151                        try {
    152                                return MimeUtility.encodeText( text, charset, "B" );
    153                        }
    154                        catch( final UnsupportedEncodingException ex ) {
    155                                final String errMsg = "指定のエンコードが出来ません。"
    156                                                                                + "text=" + text + " , charset=" + charset ;
    157                                throw new OgRuntimeException( errMsg,ex );
    158                        }
    159                }
    160
    161                /**
    162                 * 日本語を含むアドレスを生成します。
    163                 * personal に、日本語が含まれると想定しています。
    164                 * サブクラスで、日本語処理を行う場合の方法は、それぞれ異なります。
    165                 *
    166                 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
    167                 *
    168                 * @param address    RFC822形式のアドレス
    169                 * @param personal   個人名
    170                 *
    171                 * @return InternetAddressオブジェクト
    172                 * @og.rtnNotNull
    173                 */
    174                public InternetAddress getAddress( final String address,final String personal ) {
    175                        try {
    176                                return new InternetAddress( address,personal,charset );
    177                        }
    178                        catch( final UnsupportedEncodingException ex ) {
    179                                final String errMsg = "指定のエンコードが出来ません。"
    180                                                                                + "address=" + address + " , charset=" + charset ;
    181                                throw new OgRuntimeException( errMsg,ex );
    182                        }
    183                }
    184
    185                /**
    186                 * Content-Transfer-Encoding を指定する場合の ビット数を返します。
    187                 *
    188                 * Windows系は、8bit / ISO-2022-JP 系は、7bit になります。
    189                 *
    190                 * @return      ビット数("8bit" 固定)
    191                 * @og.rtnNotNull
    192                 */
    193                public String getBit() {
    194                        return "8bit" ;
    195                }
    196        }
    197
    198        /**
    199         * MailCharset インターフェースを実装した ISO-2022-JP エンコード時のサブクラスです。
    200         *
    201         * 『２．ISO-2022-JP に独自変換 + 7bit 送信』 の実装です。
    202         *
    203         *
    204         * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。
    205         * @version  4.0
    206         * @author   Kazuhiko Hasegawa
    207         * @since    JDK5.0,
    208         */
    209        private static final class Mail_ISO2022JP_Charset implements MailCharset {
    210
    211                /**
    212                 * プラットフォーム依存のデフォルトの Charset です。
    213                 * プラットフォーム依存性を考慮する場合、エンコード指定で作成しておく事をお勧めします。
    214                 *
    215                 * @og.rev 5.5.2.6 (2012/05/25) findbugs対応
    216                 */
    217                private static final Charset DEFAULT_CHARSET = Charset.defaultCharset() ;
    218
    219                /**
    220                 * テキストをセットします。
    221                 * Part#setText() の代わりにこちらを使うようにします。
    222                 *
    223                 * ※ 内部で、MessagingException が発生した場合は、RuntimeException に変換されて throw されます。
    224                 *
    225                 * @param mimeMsg MimeMessageオブジェクト
    226                 * @param text    テキスト
    227                 */
    228                public void setTextContent( final MimeMessage mimeMsg, final String text ) {
    229                        try {
    230                                // mimeMsg.setText(text, "ISO-2022-JP");
    231                                mimeMsg.setDataHandler(new DataHandler(new JISDataSource(text)));
    232                        }
    233                        catch( final MessagingException ex ) {
    234                                final String errMsg = "指定のテキストをセットできません。"
    235                                                                                + "text=" + text ;
    236                                throw new OgRuntimeException( errMsg,ex );
    237                        }
    238                }
    239
    240                /**
    241                 * 日本語を含むヘッダ用テキストを生成します。
    242                 * 変換結果は ASCII なので、これをそのまま setSubject や InternetAddress
    243                 * のパラメタとして使用してください。
    244                 *
    245                 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
    246                 *
    247                 * @param text    テキスト
    248                 *
    249                 * @return      日本語を含むヘッダ用テキスト
    250                 * @og.rtnNotNull
    251                 */
    252                public String encodeWord( final String text ) {
    253                        try {
    254                                return "=?ISO-2022-JP?B?" +
    255                                        new String(
    256                                                BASE64EncoderStream.encode(
    257                                                        CharCodeConverter.sjisToJis(
    258                                                                UnicodeCorrecter.correctToCP932(text).getBytes("Windows-31J")
    259                                                        )
    260                                                )
    261                                        ,DEFAULT_CHARSET ) + "?=";              // 5.5.2.6 (2012/05/25) findbugs対応
    262                        }
    263                        catch( final UnsupportedEncodingException ex ) {
    264                                final String errMsg = "指定のエンコードが出来ません。"
    265                                                                        + "text=" + text + " , charset=Windows-31J" ;
    266                                throw new OgRuntimeException( errMsg,ex );
    267                        }
    268                }
    269
    270                /**
    271                 * 日本語を含むアドレスを生成します。
    272                 * personal に、日本語が含まれると想定しています。
    273                 * サブクラスで、日本語処理を行う場合の方法は、それぞれ異なります。
    274                 *
    275                 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
    276                 *
    277                 * @param address    RFC822形式のアドレス
    278                 * @param personal   個人名
    279                 *
    280                 * @return InternetAddressオブジェクト
    281                 * @og.rtnNotNull
    282                 */
    283                public InternetAddress getAddress( final String address,final String personal ) {
    284                        try {
    285                                return new InternetAddress( address,encodeWord( personal ) );
    286                        }
    287                        catch( final UnsupportedEncodingException ex ) {
    288                                final String errMsg = "指定のエンコードが出来ません。"
    289                                                                        + "address=" + address ;
    290                                throw new OgRuntimeException( errMsg,ex );
    291                        }
    292                }
    293
    294                /**
    295                 * Content-Transfer-Encoding を指定する場合の ビット数を返します。
    296                 *
    297                 * Windows系は、8bit / ISO-2022-JP 系は、7bit になります。
    298                 *
    299                 * @return      ビット数("7bit" 固定)
    300                 * @og.rtnNotNull
    301                 */
    302                public String getBit() {
    303                        return "7bit" ;
    304                }
    305        }
    306
    307        /**
    308         * テキストの本文を送信するための DataSource です。
    309         *
    310         * Windows-31J でバイトコードに変換した後、独自エンコードにて、
    311         * Shift-JIS ⇒ JIS 変換しています。
    312         *
    313         * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。
    314         *
    315         * @version  4.0
    316         * @author   Kazuhiko Hasegawa
    317         * @since    JDK5.0,
    318         */
    319        private static final class JISDataSource implements DataSource {
    320                private final byte[] data;
    321
    322                /**
    323                 * JIS(Windows-31J) に対応した DataSource オブジェクトのコンストラクタ
    324                 *
    325                 * Windows-31J でバイトコードに変換した後、独自エンコードにて、
    326                 * Shift-JIS ⇒ JIS 変換しています。
    327                 *
    328                 * @param       str 変換する文字列
    329                 */
    330                public JISDataSource( final String str ) {
    331                        try {
    332                                data = CharCodeConverter.sjisToJis(
    333                                        UnicodeCorrecter.correctToCP932(str).getBytes("Windows-31J"));
    334                        } catch( final UnsupportedEncodingException ex ) {
    335                                final String errMsg = "Windows-31J でのエンコーディングが出来ません。" + str;
    336                                throw new OgRuntimeException( errMsg,ex );
    337                        }
    338                }
    339
    340                /**
    341                 * データの MIME タイプを文字列の形で返します。
    342                 * かならず有効なタイプを返すべきです。
    343                 * DataSource の実装がデータタイプを 決定できない場合は、
    344                 * getContentType は "application/octet-stream" を返すことを 提案します。
    345                 *
    346                 * @return      MIMEタイプ("text/plain; charset=ISO-2022-JP" 固定)
    347                 * @og.rtnNotNull
    348                 */
    349                public String getContentType() {
    350                        return "text/plain; charset=ISO-2022-JP";
    351                }
    352
    353                /**
    354                 * データを表す InputStreamオブジェクト を返します。
    355                 * それができない場合は適切な例外をスローします。
    356                 *
    357                 * @return InputStreamオブジェクト
    358                 * @throws IOException ※ このメソッドからは、IOException は throw されません。
    359                 * @og.rtnNotNull
    360                 */
    361                public InputStream getInputStream() throws IOException {
    362                        return new ByteArrayInputStream( data );
    363                }
    364
    365                /**
    366                 * データが書込可能なら OutputStreamオブジェクト を返します。
    367                 * それができない場合は適切な例外をスローします。
    368                 *
    369                 * ※ このクラスでは実装されていません。
    370                 *
    371                 * @return OutputStreamオブジェクト
    372                 * @throws IOException ※ このメソッドを実行すると、必ず throw されます。
    373                 */
    374                public OutputStream getOutputStream() throws IOException {
    375                        final String errMsg = "このクラスでは実装されていません。";
    376                //      throw new UnsupportedOperationException( errMsg );
    377                        throw new IOException( errMsg );
    378                }
    379
    380                /**
    381                 * このオブジェクトの '名前' を返します。
    382                 * この名前は下層のオブジェクトの性質によります。
    383                 * ファイルをカプセル化する DataSource なら オブジェクトの
    384                 * ファイル名を返すようにするかもしれません。
    385                 *
    386                 * @return      オブジェクトの名前( "JISDataSource" 固定)
    387                 * @og.rtnNotNull
    388                 */
    389                public String getName() {
    390                        return "JISDataSource";
    391                }
    392        }
    393}