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 = files.get(i).getFileName();
096                                if( name == null ) {    // message か、ファイル名未指定のケース
097                                        nms[i] = "noNameFile" + i + ".tmp" ;
098                                }
099                                // 4.3.3.5 (2008/11/08) 日本語添付ファイルが処理できるように修正
100                                else {
101                                        nms[i] = MailMessage.mimeDecode( name );
102                                }
103
104                                // 重複チェック
105                                if( !set.add( nms[i] ) ) {
106                                        nms[i] = i + "_" + nms[i] ;             // 重複時に名称変更します。
107                                }
108                        }
109                }
110                catch( MessagingException ex ) {
111                        String errMsg = "メッセージ情報のハンドリングに失敗しました。"
112                                                + ex.getMessage();                              // 5.1.8.0 (2010/07/01) errMsg 修正
113                        throw new RuntimeException( errMsg,ex );
114                }
115                catch( IOException ex ) {
116                        String errMsg = "テキスト情報の取り出しに失敗しました。"
117                                                + ex.getMessage();                              // 5.1.8.0 (2010/07/01) errMsg 修正
118                        throw new RuntimeException( errMsg,ex );
119                }
120                return nms;
121        }
122
123        /**
124         * 添付ファイルが存在するかどうかをサーチします。
125         *
126         * 添付ファイルは、マルチパートで指定されると、再帰的に検索する必要が
127         * 出てきます。このメソッドでは、再帰的にファイルかどうかを検索し、
128         * ファイルであれば、内部変数(List)に追加(add)していきます。
129         *
130         * @param part Partオブジェクト
131         *
132         * @return 再帰検索終了 true
133         * @throws MessagingException javax.mail 関連のエラーが発生したとき
134         * @throws IOException 入出力エラーが発生したとき
135         *
136         */
137        private boolean fileSearch( final Part part ) throws MessagingException ,IOException {
138                if( part.isMimeType( "multipart/*" ) ) {
139                        Multipart mpt = (Multipart)part.getContent();
140
141                        int count = mpt.getCount();
142                        for(int i = 0; i < count; i++) {
143                                BodyPart bpt = mpt.getBodyPart(i);
144                                fileSearch( bpt );
145                        }
146                }
147                else {
148                        if( part.isMimeType( "message/*" )      ||
149                                part.getFileName() != null              ||
150                                Part.INLINE.equalsIgnoreCase( part.getDisposition() ) ) {
151                                        files.add( part );
152                        }
153                }
154                return true;
155        }
156
157        /**
158         * 添付ファイルを指定のフォルダにセーブします。
159         *
160         * 内部変数List の 添付ファイルを持つ Part について、ファイルを抜出し、
161         * 指定のディレクトリに保存していきます。
162         * ファイル名は、基本的に添付ファイル名そのものですが、
163         * 同一名称の添付ファイルが複数登録されている場合は、その重複ファイルの番号を
164         * 頭につけ、番号 + "_" + 添付ファイル名 として、ユニーク化します。
165         *
166         * ※ ディレクトリの作成に失敗した場合、RuntimeException が throw されます。
167         *
168         * @param       dir     セーブするディレクトリ。null の場合は、セーブしない。
169         * @param       newNm   セーブするファイル名 null の場合は、非重複化された添付ファイル名
170         * @param       fno     添付ファイルの番号
171         */
172        public void saveFileName( final String dir, final String newNm, final int fno ) {
173                if( dir == null ) { return ; }          // ファイルをセーブしない。
174
175                File fileDir = new File( dir );
176                if( !fileDir.exists() ) {
177                        boolean isOk = fileDir.mkdirs();
178                        if( ! isOk ) {
179                                String errMsg = "ディレクトリの作成に失敗しました。[" + dir + "]";
180                                throw new RuntimeException( errMsg );
181                        }
182                }
183
184                String newName = ( newNm != null ) ? newNm : names[fno] ;
185
186                InputStream      input  = null;
187                FileOutputStream output = null;
188
189                try {
190                        Part prt = files.get( fno );
191                        input = prt.getInputStream();
192                        output = new FileOutputStream( new File( fileDir,newName ) );
193                        byte[] buf = new byte[1024];
194                        int len;
195                        while( (len = input.read(buf)) != -1 ) {
196                                output.write( buf,0,len );
197                        }
198                }
199                catch( MessagingException ex ) {
200                        String errMsg = "メッセージオブジェクトの操作中にエラーが発生しました。"
201                                                + "dir=[" + dir + "], file=[" + newName + "], No=[" + fno + "]"
202                                                + ex.getMessage();                      // 5.1.8.0 (2010/07/01) errMsg 修正
203                        throw new RuntimeException( errMsg,ex );
204                }
205                catch( IOException ex ) {
206                        String errMsg = "添付ファイルの取り扱い中にエラーが発生しました。"
207                                                + "dir=[" + dir + "], file=[" + newName + "], No=[" + fno + "]"
208                                                + ex.getMessage();                      // 5.1.8.0 (2010/07/01) errMsg 修正
209                        throw new RuntimeException( errMsg,ex );
210                }
211                finally {
212                        Closer.ioClose( output );
213                        Closer.ioClose( input  );
214                }
215        }
216}