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