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