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