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;
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) 『1.Windows-31J + 8bit 送信』 の実装である、Mail_Windows31J_Charset</del>
042 * 『1.Windows-31J/UTF-8 + 8bit 送信』 の実装である、Mail_8bit_Charset
043 * サブクラスを返します。
044 * それ以外が指定された場合は、ISO-2022-JP を使用して、『2.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         * 『1.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         * 『2.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}