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 org.opengion.fukurou.util.Closer ;
019
020import javax.mail.MessagingException;
021import javax.mail.Part;
022import javax.mail.BodyPart;
023import javax.mail.Multipart;
024import java.io.File;
025import java.io.InputStream;
026import java.io.FileOutputStream;
027import java.io.IOException;
028import java.util.List;
029import java.util.ArrayList;
030import java.util.Set;
031import java.util.HashSet;
032
033/**
034 * メール添付ファイル処理クラス
035 *
036 * このクラスは、添付ファイルを処理するためのクラスです。
037 * 添付ファイルは、マルチパートに含まれている為、再帰的に探す必要があります。
038 *
039 * @version  4.0
040 * @author   Kazuhiko Hasegawa
041 * @since    JDK5.0,
042 */
043public class MailAttachFiles {
044        private final List<Part> files ;
045        private final String[] names ;
046
047        /**
048         * デフォルトコンストラクター
049         *
050         * 内部変数の初期化を行います。
051         *
052         * @param       part    Partオブジェクト
053         */
054        public MailAttachFiles( final Part part ) {
055                files = new ArrayList<Part>();
056                names = makeNames( part );
057        }
058
059        /**
060         * 添付ファイルの名称を文字列配列として求めます。
061         *
062         * @return 添付ファイルの名称を文字列配列
063         */
064        public String[] getNames() {
065                String[] rtn = null ;
066
067                if( names != null ) { rtn = names.clone(); }
068
069                return rtn ;
070        }
071
072        /**
073         * 添付ファイルの名称を文字列配列として求めます。
074         *
075         * この処理の中で、添付ファイルを持つ Part を見つけて内部配列(List)に登録します。
076         * ファイル名が未指定の場合は、"noNameFile" + i + ".tmp" というファイル名をつけます。
077         * i は、添付ファイルの連番です。
078         * また、同一添付ファイル名が存在する場合は、頭に添付ファイルの連番を付加して、
079         * ファイル名としてユニーク化します。
080         *
081         * @og.rev 4.3.3.5 (2008/11/08) 日本語添付ファイルが処理できるように修正
082         *
083         * @param part Partオブジェクト
084         *
085         * @return 添付ファイルの名称を文字列配列
086         */
087        private String[] makeNames( final Part part ) {
088                final String[] nms;
089                try {
090                        Set<String> set = new HashSet<String>();
091
092                        fileSearch( part );
093                        nms = new String[files.size()];
094                        for( int i=0; i<nms.length; i++ ) {
095        //                      String name = ((Part)files.get(i)).getFileName();
096                                String name = (files.get(i)).getFileName();
097                                if( name == null ) {    // message か、ファイル名未指定のケース
098                                        nms[i] = "noNameFile" + i + ".tmp" ;
099                                }
100//                              else {
101//                                      // encode-word の =? の前にはスペースが必要。
102//                                      StringBuilder buf = new StringBuilder( name );
103//                                      int pos = buf.indexOf( "?==?" );                // デコードの終了と開始が連結している箇所
104//                                      // 先頭でなく、かつ開始記号が含まれている。
105//                                      while( pos > 0 ) {
106//                                              buf.insert( pos+2," " );
107//                                              pos = buf.indexOf( "?==?",pos+4 );
108//                                      }
109//                              }
110                                // 4.3.3.5 (2008/11/08) 日本語添付ファイルが処理できるように修正
111                                else {
112                                        nms[i] = MailMessage.mimeDecode( name );
113                                }
114
115                                // 重複チェック
116                                if( !set.add( nms[i] ) ) {
117                                        nms[i] = i + "_" + nms[i] ;             // 重複時に名称変更します。
118                                }
119                        }
120                }
121                catch( MessagingException ex ) {
122                        String errMsg = "メッセージ情報のハンドリングに失敗しました。"
123                                                + ex.getMessage();                              // 5.1.8.0 (2010/07/01) errMsg 修正
124                        throw new RuntimeException( errMsg,ex );
125                }
126//              catch( UnsupportedEncodingException ex ) {
127//                      String errMsg = "テキスト情報のデコードに失敗しました。" ;
128//                      throw new RuntimeException( errMsg,ex );
129//              }
130                catch( IOException ex ) {
131                        String errMsg = "テキスト情報の取り出しに失敗しました。"
132                                                + ex.getMessage();                              // 5.1.8.0 (2010/07/01) errMsg 修正
133                        throw new RuntimeException( errMsg,ex );
134                }
135                return nms;
136        }
137
138        /**
139         * 添付ファイルが存在するかどうかをサーチします。
140         *
141         * 添付ファイルは、マルチパートで指定されると、再帰的に検索する必要が
142         * 出てきます。このメソッドでは、再帰的にファイルかどうかを検索し、
143         * ファイルであれば、内部変数(List)に追加(add)していきます。
144         *
145         * @param part Partオブジェクト
146         *
147         * @return 再帰検索終了 true
148         * @throws MessagingException javax.mail 関連のエラーが発生したとき
149         * @throws IOException 入出力エラーが発生したとき
150         *
151         */
152        private boolean fileSearch( final Part part ) throws MessagingException ,IOException {
153                if( part.isMimeType( "multipart/*" ) ) {
154                        Multipart mpt = (Multipart)part.getContent();
155
156                        int count = mpt.getCount();
157                        for(int i = 0; i < count; i++) {
158                                BodyPart bpt = mpt.getBodyPart(i);
159                                fileSearch( bpt );
160                        }
161                }
162                else {
163                        if( part.isMimeType( "message/*" )      ||
164                                part.getFileName() != null              ||
165                                Part.INLINE.equalsIgnoreCase( part.getDisposition() ) ) {
166                                        files.add( part );
167                        }
168                }
169                return true;
170        }
171
172        /**
173         * 添付ファイルを指定のフォルダにセーブします。
174         *
175         * 内部変数List の 添付ファイルを持つ Part について、ファイルを抜出し、
176         * 指定のディレクトリに保存していきます。
177         * ファイル名は、基本的に添付ファイル名そのものですが、
178         * 同一名称の添付ファイルが複数登録されている場合は、その重複ファイルの番号を
179         * 頭につけ、番号 + "_" + 添付ファイル名 として、ユニーク化します。
180         *
181         * ※ ディレクトリの作成に失敗した場合、RuntimeException が throw されます。
182         *
183         * @param       dir     セーブするディレクトリ。null の場合は、セーブしない。
184         * @param       newNm   セーブするファイル名 null の場合は、非重複化された添付ファイル名
185         * @param       fno     添付ファイルの番号
186         */
187        public void saveFileName( final String dir, final String newNm, final int fno ) {
188                if( dir == null ) { return ; }          // ファイルをセーブしない。
189
190                File fileDir = new File( dir );
191                if( !fileDir.exists() ) {
192                        boolean isOk = fileDir.mkdirs();
193                        if( ! isOk ) {
194                                String errMsg = "ディレクトリの作成に失敗しました。[" + dir + "]";
195                                throw new RuntimeException( errMsg );
196                        }
197                }
198
199                String newName = ( newNm != null ) ? newNm : names[fno] ;
200
201                InputStream      input  = null;
202                FileOutputStream output = null;
203
204                try {
205                        Part prt = files.get( fno );
206                        input = prt.getInputStream();
207                        output = new FileOutputStream( new File( fileDir,newName ) );
208                        byte[] buf = new byte[1024];
209                        int len;
210                        while( (len = input.read(buf)) != -1 ) {
211                                output.write( buf,0,len );
212                        }
213                }
214                catch( MessagingException ex ) {
215                        String errMsg = "メッセージオブジェクトの操作中にエラーが発生しました。"
216                                                + "dir=[" + dir + "], file=[" + newName + "], No=[" + fno + "]"
217                                                + ex.getMessage();                      // 5.1.8.0 (2010/07/01) errMsg 修正
218                        throw new RuntimeException( errMsg,ex );
219                }
220                catch( IOException ex ) {
221                        String errMsg = "添付ファイルの取り扱い中にエラーが発生しました。"
222                                                + "dir=[" + dir + "], file=[" + newName + "], No=[" + fno + "]"
223                                                + ex.getMessage();                      // 5.1.8.0 (2010/07/01) errMsg 修正
224                        throw new RuntimeException( errMsg,ex );
225                }
226                finally {
227                        Closer.ioClose( output );
228                        Closer.ioClose( input  );
229                }
230        }
231}