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