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.util;
017
018import java.io.File;
019import java.io.IOException;
020
021/**
022 * AbstractConnect.java は、共通的に使用される ファイル伝送関連の基本機能を実装した、Abstractクラスです。
023 *
024 * -host=サーバー -user=ユーザー -passwd=パスワード -remoteFile=接続先のファイル名 を必須設定します。
025 * -localFile=ローカルのファイル名は、必須ではありませんが、-command=DEL の場合にのみ不要であり、
026 * それ以外の command の場合は、必要です。
027 *
028 * -command=[GET/PUT/DEL/GETDIR/PUTDIR/DELDIR] は、サーバーに対しての処理の方法を指定します。
029 *   GET:サーバーからローカルにファイル転送します(初期値)
030 *   PUT:ローカルファイルをサーバーに PUT(STORE、SAVE、UPLOAD、などと同意語)します。
031 *   DEL:サーバーの指定のファイルを削除します。この場合のみ、-localFile 属性の指定は不要です。
032 *   GETDIR,PUTDIR,DELDIR:指定のフォルダ以下のファイルを処理します。
033 *
034 * -mkdirs=[true/false] は、受け側のファイル(GET時:LOCAL、PUT時:サーバー)に取り込むファイルのディレクトリが
035 * 存在しない場合に、作成するかどうかを指定します(初期値:true)
036 * 通常、サーバーに、フォルダ階層を作成してPUTする場合、動的にフォルダ階層を作成したいケースで便利です。
037 * 逆に、フォルダは確定しており、指定フォルダ以外に PUT するのはバグっていると事が分かっている場合には
038 * false に設定して、存在しないフォルダにPUT しようとすると、エラーになるようにします。
039 *
040 * 引数文字列中に空白を含む場合は、ダブルコーテーション("") で括って下さい。
041 * 引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に
042 * 繋げてください。
043 *
044 * @og.formSample
045 *  XXXConnect -host=サーバー -user=ユーザー -passwd=パスワード -remoteFile=接続先のファイル名 [-localFile=ローカルのファイル名]
046 *                   [-command=[GET/PUT/DEL/GETDIR/PUTDIR/DELDIR] ] [-display=[true/false] ] ・・・・
047 *
048 *    -host=サーバー                    :接続先のサーバーのアドレスまたは、サーバー名
049 *    -user=ユーザー                    :接続するユーザー名
050 *    -passwd=パスワード                :接続するユーザーのパスワード
051 *    -remoteFile=接続先のファイル名    :接続先のサーバー側のファイル名。PUT,GET 関係なくFTP側として指定します。
052 *   [-localFile=ローカルのファイル名]  :ローカルのファイル名。PUT,GET 関係なくローカルファイルを指定します。
053 *   [-port=ポート ]                    :接続するサーバーのポートを指定します。
054 *   [-command=[GET/PUT/DEL] ]          :サーバー側での処理の方法を指定します。
055 *             [GETDIR/PUTDIR/DELDIR]]          GET:FTP⇒LOCAL、PUT:LOCAL⇒FTP への転送です(初期値:GET)
056 *                                              DEL:FTPファイルを削除します。
057 *                                              GETDIR,PUTDIR,DELDIR 指定のフォルダ以下のファイルを処理します。
058 *   [-mkdirs=[true/false]  ]           :受け側ファイル(GET時:LOCAL、PUT時:サーバー)にディレクトリを作成するかどうか(初期値:true)
059 *                                              (false:ディレクトリが無ければ、エラーにします。)
060 *   [-encode=エンコード名 ]            :日本語ファイル名などのエンコード名を指定します(初期値:Windows-31J)
061 *   [-timeout=タイムアウト[秒] ]       :Dataタイムアウト(初期値:600 [秒])
062 *   [-display=[false/true] ]           :trueは、検索状況を表示します(初期値:false)
063 *   [-debug=[false|true]   ]           :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
064 *
065 * @og.rev 5.1.6.0 (2010/05/01) 新規追加
066 *
067 * @version  5.0
068 * @author       Kazuhiko Hasegawa
069 * @since    JDK5.0,
070 */
071public abstract class AbstractConnect implements ConnectIF {
072        public static final String CR = System.getProperty("line.separator");
073        private final StringBuilder errMsg = new StringBuilder( 200 );
074
075        /** 正常フラグ  {@value} */
076        public static final boolean FLAG_OK = true;
077        /** 異常フラグ  {@value} */
078        public static final boolean FLAG_NG = false;
079        /** Dataタイムアウト(初期値:{@value} [秒]) */
080        public static final int         TIMEOUT = 600 ;
081
082        /** サーバー */
083        protected String        host            = null;                 // サーバー
084        /** ユーザー */
085        protected String        user            = null;                 // ユーザー
086        /** パスワード */
087        protected String        passwd          = null;                 // パスワード
088        /** ポート */
089        protected String        port            = null;                 // ポート
090
091        /** ディレクトリを作成するかどうか */
092        protected boolean       isMkdirs        = true;                 // 受け側ファイルにディレクトリを作成するかどうか。true:作成する / false:無ければエラー
093        /** Dataタイムアウト  */
094        protected int           timeout         = TIMEOUT;              // Dataタイムアウト(初期値:600 [秒])
095        /** 検索状況を表示するかどうか  */
096        protected boolean       isDisplay       = false;                // trueは、検索状況を表示します(初期値:false)
097        /** デバッグ情報を表示するかどうか  */
098        protected boolean       isDebug         = false;                // デバッグ情報を標準出力に表示する(true)かしない(false)か
099
100        /**
101         * サーバーへの接続、ログインを行います。
102         */
103        @Override
104        public abstract void connect() ;
105
106        /**
107         * command , localFile , remoteFile を元に、FTP処理を行います。
108         *
109         * このメソッドは、connect( String , String , String )メソッド、および、
110         * paramInit() 実行後に、呼び出す必要があります。
111         *
112         * ※ 内部で、command に指定できない値をセットしたか、何らかのエラーが発生した場合。
113         *
114         * @param       command GET:HOST⇒LOCAL 、PUT:LOCAL⇒HOST 、DEL:HOSTファイルを削除
115         * @param       localFile       ローカルのファイル名
116         * @param       remoteFile      HOST接続先のファイル名
117         */
118        @Override
119        public void action( final String command, final String localFile, final String remoteFile ) {
120                String rmtFile = remoteFile.replace( '\\','/' );
121                if( isDisplay ) {
122                        System.out.println( "ACTION: command=" + command + ",localFile=" + localFile + ",remoteFile=" + rmtFile );
123                }
124
125                try {
126                        // 実際の処理を行います。(GET/PUT/DEL)
127                        if( "GET".equals( command ) ) {
128                                actionGET( localFile, rmtFile );
129                        }
130                        else if( "PUT".equals( command ) ) {
131                                actionPUT( localFile, rmtFile );
132                        }
133                        else if( "DEL".equals( command ) ) {
134                                actionDEL( rmtFile );
135                        }
136                        else if( "GETDIR".equals( command ) ) {
137                                actionGETdir( localFile, rmtFile );
138                        }
139                        else if( "PUTDIR".equals( command ) ) {
140                                actionPUTdir( localFile, rmtFile );
141                        }
142                        else if( "DELDIR".equals( command ) ) {
143                                actionDELdir( rmtFile );
144                        }
145                        else {
146                                errAppend( "commandは、GET/PUT/DEL/GETDIR/PUTDIR/DELDIR から指定ください。" );
147                                errAppend( "   command    = [" , command , "]" );
148                                throw new RuntimeException(  getErrMsg() );
149                        }
150                }
151                catch( Exception ex ) {
152                        errAppend( "Server action Error." );
153                        errAppend( "   command    = [" , command        , "]" );
154                        errAppend( "   localFile  = [" , localFile      , "]" );
155                        errAppend( "   remoteFile = [" , remoteFile , "]" );
156                        errAppend( ex.getMessage() );
157                        if( isDebug ) { ex.printStackTrace(); }
158                        throw new RuntimeException( getErrMsg(),ex );
159                }
160        }
161
162        /**
163         * サーバーとの接続をクローズします。
164         *
165         * ログインされている場合は、ログアウトも行います。
166         * コネクトされている場合は、ディスコネクトします。
167         */
168        @Override
169        public abstract void disconnect();
170
171        /**
172         * command="GET" が指定されたときの処理を行います。
173         *
174         * 接続先のサーバー側のファイル名をローカルにダウンロードします。
175         *
176         * @param       localFile       ローカルのファイル名
177         * @param       remoteFile      接続先のファイル名
178         * @throws Exception 何らかのエラーが発生した場合。
179         */
180        protected abstract void actionGET( final String localFile, final String remoteFile ) throws Exception ;
181
182        /**
183         * command="GETDIR" が指定されたときの処理を行います。
184         *
185         * 接続先のサーバー側のディレクトリ以下をローカルディレクトリに階層構造のままダウンロードします。
186         *
187         * @param       localDir        ローカルのディレクトリ名
188         * @param       remoteDir       接続先のディレクトリ名
189         * @throws Exception 何らかのエラーが発生した場合。
190         */
191        protected abstract void actionGETdir( final String localDir, final String remoteDir ) throws Exception ;
192
193        /**
194         * command="PUT" が指定されたときの処理を行います。
195         *
196         * ローカルファイルを、接続先のサーバー側にアップロードします。
197         *
198         * @param       localFile       ローカルのファイル名
199         * @param       remoteFile      接続先のファイル名
200         * @throws Exception 何らかのエラーが発生した場合。
201         */
202        protected abstract void actionPUT( final String localFile, final String remoteFile ) throws Exception ;
203
204        /**
205         * command="PUTDIR" が指定されたときの処理を行います。
206         *
207         * ローカルファイルのディレクトリ以下を、接続先のサーバー側のディレクトリに階層構造のままアップロードします。
208         *
209         * @og.rev 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラーを返します。
210         *
211         * @param       localDir        ローカルのディレクトリ名
212         * @param       remoteDir       接続先のディレクトリ名
213         * @throws Exception  何らかのエラーが発生した場合。
214         */
215        protected void actionPUTdir( final String localDir, final String remoteDir ) throws Exception {
216                File[] lclFiles = new File( localDir ).listFiles();
217
218                // 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラーを返します。
219                if( lclFiles == null ) {
220                        errAppend( "指定のディレクトリは、アクセスできません。" );
221                        errAppend( "   localDir   = [" , localDir       , "]" );
222                        throw new RuntimeException( getErrMsg() );
223                }
224
225                for( int i=0; i<lclFiles.length; i++ ) {
226                        String lcl = lclFiles[i].getName();
227                        if( lclFiles[i].isDirectory() ) {
228                                actionPUTdir( addFile( localDir,lcl ),addFile( remoteDir,lcl ) );
229                        }
230                        else {
231                                actionPUT( addFile( localDir,lcl ),addFile( remoteDir,lcl ) );
232                        }
233                }
234        }
235
236        /**
237         * command="DEL" が指定されたときの処理を行います。
238         *
239         * 接続先のサーバー側のファイル名を削除します。
240         *
241         * @param       remoteFile      接続先のファイル名
242         * @throws Exception 何らかのエラーが発生した場合。
243         */
244        protected abstract void actionDEL( final String remoteFile ) throws Exception ;
245
246        /**
247         * command="DELDIR" が指定されたときの処理を行います。
248         *
249         * 接続先のサーバー側のディレクトリ名を削除します。
250         *
251         * @param       remoteDir       接続先のディレクトリ名
252         * @throws Exception 何らかのエラーが発生した場合。
253         */
254        protected abstract void actionDELdir( final String remoteDir ) throws Exception ;
255
256        /**
257         * ローカルファイルのディレクトリを作成します。
258         *
259         * 引数のファイル名は、ファイル名です。作成するディレクトリは、そのファイルオブジェクトの
260         * getParentFile() で取得されるディレクトリまでを作成します。
261         *
262         * ※ ローカルファイルのディレクトリの作成に失敗した場合は、RuntimeException が throw されます。
263         *
264         * @param       localFile       ローカルのファイル名
265         * @throws IOException File#getCanonicalFile() で発生する入出力エラー
266         */
267        protected void makeLocalDir( final String localFile ) throws IOException {
268                File fileDir = new File( localFile ).getCanonicalFile().getParentFile();
269                // 6.0.0.1 (2014/04/25) These nested if statements could be combined
270                if( !fileDir.exists() && !fileDir.mkdirs() ) {
271                        errAppend( "ローカルファイルのディレクトリの作成に失敗しました。" );
272                        errAppend( "   localFile   = [" , localFile     , "]" );
273                        throw new RuntimeException( getErrMsg() );
274                }
275        }
276
277        /**
278         * ディレクトリとファイル名を合成します。
279         *
280         * 単純に、ディレクトリの最後と、ファイルの最初の、"/" をチェックし、
281         * 存在すれば、そのまま、結合し、存在しなければ、"/" を追加します。
282         * 両方に存在する場合は、片方をはずします。
283         *
284         * @param       dir     ディレクトリ名
285         * @param       file    ファイル名
286         *
287         * @return      合成されたファイル名
288         */
289        protected String addFile( final String dir,final String file ) {
290                final String filepath ;
291
292                char ch1 = dir.charAt( dir.length()-1 ) ;       // ディレクトリの最後の文字
293                char ch2 = file.charAt( 0 ) ;                           // ファイルの最初の文字
294
295                if( ch1 == '/' || ch1 == '\\' ) {
296                        if( ch2 == '/' || ch2 == '\\' ) {       // 両方とも存在する。
297                                filepath = dir + file.substring(1);
298                        }
299                        else {
300                                filepath = dir + file;
301                        }
302                }
303                else {
304                        if( ch2 == '/' || ch2 == '\\' ) {       // 片方のみ存在する。
305                                filepath = dir + file;
306                        }
307                        else {
308                                filepath = dir + "/" + file;
309                        }
310                }
311
312                return filepath ;
313        }
314
315        /**
316         * サーバーの、ホスト、ユーザー、パスワードを設定します。
317         *
318         * @param       host    サーバー
319         * @param       user    ユーザー
320         * @param       passwd  パスワード
321         */
322        @Override
323        public void setHostUserPass( final String host , final String user , final String passwd ) {
324                this.host = host;
325                this.user = user;
326                this.passwd = passwd;
327        }
328
329        /**
330         * 接続に利用するポート番号を設定します。
331         *
332         * @param       port    接続に利用するポート番号
333         */
334        @Override
335        public void setPort( final String port ) {
336                if( port != null ) {
337                        this.port = port ;
338                }
339        }
340
341        /**
342         * ポートを取得します。
343         * 設定されている生のport属性(nullもありうる)を返します。
344         *
345         * @return      ポート
346         */
347        protected String getPort() { return port; }
348
349        /**
350         * ポートを取得します。
351         * 設定されているport属性が、nullの場合は、defPortを返します。
352         *
353         * @param       defPort port が null の場合の初期値
354         *
355         * @return      ポート
356         */
357        protected int getPort( final int defPort) {
358                return ( port == null ) ? defPort : Integer.parseInt( port );
359        }
360
361        /**
362         * それぞれの受け側ファイルにディレクトリを作成するかどうか(初期値:true:作成する)。
363         *
364         * -mkdirs=[true/false] は、受け側のファイル(GET時:LOCAL、PUT時:サーバー)に取り込むファイルのディレクトリが
365         * 存在しない場合に、作成するかどうかを指定します(初期値:true)
366         * 通常、サーバーに、フォルダ階層を作成してPUTする場合、動的にフォルダ階層を作成したいケースで便利です。
367         * 逆に、フォルダは確定しており、指定フォルダ以外に PUT するのはバグっていると事が分かっている場合には
368         * false に設定して、存在しないフォルダにPUT しようとすると、エラーになるようにします。
369         *
370         * @param       isMkdirs        受け側ファイルにディレクトリを作成するかどうか。true:作成する
371         */
372        @Override
373        public void setMkdirs( final boolean isMkdirs ) {
374                this.isMkdirs = isMkdirs ;
375        }
376
377        /**
378         * タイムアウトを秒で指定します(初期値:600 [秒])。
379         *
380         * オリジナルの FTPClient#setDataTimeout( int ) は、ミリ秒でセット
381         * しますが、ここのメソッドでは、秒でセットします。
382         *
383         * @param       timeout タイムアウト[秒]
384         * @throws RuntimeException タイムアウトの指定が大きすぎた場合
385         */
386        @Override
387        public void setTimeout( final int timeout ) {
388                if( Integer.MAX_VALUE / 1000 < timeout ) {
389                        errAppend( "タイムアウトの指定が大きすぎます。" );
390                        errAppend( "   timeout   = [" , timeout , "]" );
391                        throw new RuntimeException( getErrMsg() );
392                }
393
394                this.timeout = timeout ;
395        }
396
397        /**
398         * 実行状況の表示可否 を設定します(初期値:false:表示しない)。
399         *
400         * @param       isDisplay       実行状況の表示可否
401         */
402        @Override
403        public void setDisplay( final boolean isDisplay ) {
404                this.isDisplay = isDisplay ;
405        }
406
407        /**
408         * デバッグ情報の表示可否 を設定します(初期値:false:表示しない)。
409         *
410         * @param       isDebug デバッグ情報の表示可否
411         */
412        @Override
413        public void setDebug( final boolean isDebug ) {
414                this.isDebug = isDebug ;
415        }
416
417        /**
418         * 処理中に発生したエラーメッセージをセットします。
419         *
420         * @param       msg  メッセージ化したいオブジェクト
421         */
422        protected void errAppend( final Object msg ) {
423                errMsg.append( String.valueOf(msg) ).append( CR );
424        }
425
426        /**
427         * 処理中に発生したエラーメッセージをセットします。
428         *
429         * @param       msgs  Object...
430         */
431        protected void errAppend( final Object... msgs ) {
432                for( int i=0; i<msgs.length; i++ ) {
433                        errMsg.append( String.valueOf(msgs[i]) );
434                }
435
436                errMsg.append( CR );
437        }
438
439        /**
440         * 処理中に発生したエラーメッセージを取り出します。
441         *
442         * @return      エラーメッセージ
443         */
444        @Override
445        public String getErrMsg() {
446                return errMsg.toString();
447        }
448}