    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 org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
    019import java.io.FileInputStream;
    020import java.io.FileOutputStream;
    021import java.io.InputStream;
    022import java.io.OutputStream;
    023import java.io.BufferedInputStream;
    024import java.io.BufferedOutputStream;
    025import java.io.IOException;
    026import java.util.Map;
    027import java.util.LinkedHashMap ;
    028import java.net.MalformedURLException ;
    029
    030import jcifs.smb.SmbFile;
    031
    032import org.opengion.fukurou.system.Closer;                                                      // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
    033
    034/**
    035 * SMBConnect.java は、共通的に使用される Smb関連の基本機能を実装した、クラスです。
    036 *
    037 * これは、jcifs.smb パッケージをベースに開発されています。
    038 * このクラスの実行には、jcifs-1.3.14.jar が必要です。
    039 *
    040 * 接続先のURL schemeは、以下の形式をしています。
    041 *     smb://[[[domain;]username[:password]@]server[:port]/[[share/[dir/]file]]][?[param=value[param2=value2[...]]]
    042 * このURLは、内部的に自動作成します。/[[share/[dir/]file]] 部分が、remoteFile として指定する部分になります。 *
    043 *
    044 * -host=Smbサーバー -user=ユーザー -passwd=パスワード -remoteFile=Smb先のファイル名 を必須設定します。
    045 * -localFile=ローカルのファイル名は、必須ではありませんが、-command=DEL の場合にのみ不要であり、
    046 * それ以外の command の場合は、必要です。
    047 *
    048 * -command=[GET/PUT/DEL/GETDIR/PUTDIR/DELDIR] は、SFTPサーバーに対しての処理の方法を指定します。
    049 *   GET:Smbサーバーからローカルにファイル転送します(初期値)
    050 *   PUT:ローカルファイルをSmbサーバーに PUT(STORE、SAVE、UPLOAD、などと同意語)します。
    051 *   DEL:Smbサーバーの指定のファイルを削除します。この場合のみ、-localFile 属性の指定は不要です。
    052 *   GETDIR,PUTDIR,DELDIR:指定のフォルダ以下のファイルを処理します。
    053 *
    054 * -mkdirs=[true/false] は、受け側のファイル(GET時:LOCAL、PUT時:Smbサーバー)に取り込むファイルのディレクトリが
    055 * 存在しない場合に、作成するかどうかを指定します(初期値:true)。
    056 * 通常、Smbサーバーに、フォルダ階層を作成してPUTする場合、動的にフォルダ階層を作成したいケースで便利です。
    057 * 逆に、フォルダは確定しており、指定フォルダ以外に PUT するのはバグっていると事が分かっている場合には
    058 * false に設定して、存在しないフォルダにPUT しようとすると、エラーになるようにします。
    059 *
    060 * 引数文字列中に空白を含む場合は、ダブルコーテーション("") で括って下さい。
    061 * 引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に
    062 * 繋げてください。
    063 *
    064 * @og.formSample
    065 *  SMBConnect -host=Smbサーバー -user=ユーザー -passwd=パスワード -remoteFile=Smb先のファイル名 [-localFile=ローカルのファイル名]
    066 *                   [-command=[GET/PUT/DEL/GETDIR/PUTDIR/DELDIR] ] ]
    067 *
    068 *    -host=Smbサーバー                 ：接続先のSmbサーバーのアドレスまたは、サーバー名
    069 *    -user=ユーザー                    ：接続するユーザー名
    070 *    -passwd=パスワード                ：接続するユーザーのパスワード
    071 *    -remoteFile=Smb先のファイル名     ：接続先のSmbサーバー側のファイル名。PUT,GET 関係なくSmb側として指定します。
    072 *   [-localFile=ローカルのファイル名]  ：ローカルのファイル名。PUT,GET 関係なくローカルファイルを指定します。
    073 *   [-domain=ドメイン ]                ：接続するサーバーのドメインを指定します。
    074 *   [-port=ポート ]                    ：接続するサーバーのポートを指定します。
    075 *   [-command=[GET/PUT/DEL] ]          ：Smbサーバー側での処理の方法を指定します。
    076 *             [GETDIR/PUTDIR/DELDIR]]          GET:Smb⇒LOCAL、PUT:LOCAL⇒Smb への転送です(初期値:GET)
    077 *                                              DEL:Smbファイルを削除します。
    078 *                                              GETDIR,PUTDIR,DELDIR 指定のフォルダ以下のファイルを処理します。
    079 *   [-mkdirs=[true/false]  ]           ：受け側ファイル(GET時:LOCAL、PUT時:Smbサーバー)にディレクトリを作成するかどうか(初期値:true)
    080 *                                              (false:ディレクトリが無ければ、エラーにします。)
    081 *   [-display=[false/true] ]           ：trueは、検索状況を表示します(初期値:false)
    082 *   [-debug=[false|true]   ]           ：デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
    083 *
    084 * @og.rev 5.1.6.0 (2010/05/01) 新規追加
    085 *
    086 * @version  5.0
    087 * @author       Kazuhiko Hasegawa
    088 * @since    JDK5.0,
    089 */
    090public final class SMBConnect extends AbstractConnect {
    091        private String  domain  ;                       // ドメイン
    092        private String  connURI ;                       // Smb接続する場合のURI文字列(の先頭)
    093
    094        /**
    095         * Smbサーバーへの接続、ログインを行います。
    096         *
    097         * このメソッドは、初期化メソッドです。
    098         * Smbサーバーへの接続、ログインを行いますので、複数ファイルを転送する
    099         * ケースでは、最初に１度だけ呼び出すだけです。
    100         * 接続先を変更する場合は、もう一度このメソッドをコールする必要があります。
    101         * (そのような場合は、通常、オブジェクトを構築しなおす方がよいと思います。)
    102         * 接続時のアドレスは、下記の形式になりますが、ファイル名の箇所は、action 時に指定するため、
    103         * ここでは、先頭部分を先に用意しておきます。
    104         *     smb://[[[domain;]username[:password]@]server[:port]/[[share/[dir/]file]]][?[param=value[param2=value2[...]]]
    105         *
    106         */
    107        @Override       // AbstractConnect#ConnectIF
    108        public void connect() {
    109                if( isDisplay ) { System.out.println( "CONNECT: HOST=" + host + ",USER=" + user + ",PORT=" + port ); }
    110
    111                if( host == null ) {
    112                        errAppend( "host は、必須です。" );
    113                        throw new OgRuntimeException( getErrMsg() );
    114                }
    115
    116                // smb://[[[domain;]username[:password]@]server[:port] ここまで作成
    117                connURI = "smb://"
    118                                + ((domain == null) ? "" : domain + ";" )
    119                                + ((user   == null) ? "" : user )
    120                                + ((passwd   == null) ? "" : ":" + passwd )
    121                                + ((user   == null) ? "" : "@" )
    122                                + host
    123                                + ((port   == null) ? "" : ":" + port ) ;
    124
    125                if( isDebug ) { System.out.println( "connURI=" + connURI ); }
    126        }
    127
    128        /**
    129         * Smbサーバーとの接続をクローズします。
    130         *
    131         * ここでは、何も処理を行いません。
    132         *
    133         */
    134        @Override       // AbstractConnect#ConnectIF
    135        public void disconnect() {
    136                if( isDisplay ) { System.out.println( "DISCONNECT:" ); }
    137        }
    138
    139        /**
    140         * command="GET" が指定されたときの処理を行います。
    141         *
    142         * 接続先のSmbサーバー側のファイル名をローカルにダウンロードします。
    143         *
    144         * @param       localFile       ローカルのファイル名
    145         * @param       remoteFile      Smb先のファイル名
    146         * @throws IOException 入出力エラーが発生したとき
    147         */
    148        @Override       // AbstractConnect
    149        protected void actionGET( final String localFile, final String remoteFile ) throws IOException {
    150                if( isDebug ) { System.out.println( "GET: " + remoteFile + " => " + localFile ); }
    151
    152                final SmbFile rmtFile = makeSmbURI( remoteFile );
    153
    154                // GET(DOWNLOAD)取得時は、ローカルファイルのディレクトリを作成する必要がある。
    155                if( isMkdirs ) {
    156                        makeLocalDir( localFile );
    157                }
    158
    159                InputStream     input  = null;
    160                OutputStream    output = null;
    161                try {
    162                        input  = new BufferedInputStream( rmtFile.getInputStream() );
    163                        output = new FileOutputStream( localFile );
    164                        FileUtil.copy( input,output );
    165                }
    166                finally {
    167                        Closer.ioClose( input );
    168                        Closer.ioClose( output );
    169                }
    170        }
    171
    172        /**
    173         * command="GETDIR" が指定されたときの処理を行います。
    174         *
    175         * 接続先のSmbサーバー側のディレクトリ以下をローカルディレクトリに階層構造のままダウンロードします。
    176         *
    177         * @param       localDir        ローカルのディレクトリ名
    178         * @param       remoteDir       Smb先のディレクトリ名
    179         * @throws IOException 入出力エラーが発生したとき
    180         */
    181        @Override       // AbstractConnect
    182        protected void actionGETdir( final String localDir, final String remoteDir ) throws IOException {
    183                final SmbFile rmtFile = makeSmbURI( remoteDir );
    184
    185                final SmbFile[] rmtFiles = rmtFile.listFiles();
    186                for( int i=0; i<rmtFiles.length; i++ ) {
    187                        final String rmt = rmtFiles[i].getName();
    188                        if( rmtFiles[i].isDirectory() ) {
    189                                actionGETdir( addFile( localDir,rmt ),addFile( remoteDir,rmt ) );
    190                        }
    191                        else {
    192                                actionGET( addFile( localDir,rmt ),addFile( remoteDir,rmt ) );
    193                        }
    194                }
    195        }
    196
    197        /**
    198         * command="PUT" が指定されたときの処理を行います。
    199         *
    200         * ローカルファイルを、接続先のSmbサーバー側にアップロードします。
    201         *
    202         * @param       localFile       ローカルのファイル名
    203         * @param       remoteFile      Smb先のファイル名
    204         * @throws IOException 入出力エラーが発生したとき
    205         */
    206        @Override       // AbstractConnect
    207        protected void actionPUT( final String localFile, final String remoteFile ) throws IOException {
    208                if( isDebug ) { System.out.println( "PUT: " + localFile + " => " + remoteFile ); }
    209
    210                final SmbFile rmtFile = makeSmbURI( remoteFile );
    211
    212                // 存在チェック：すでに存在している場合は、先に削除します。
    213                if( rmtFile.exists() ) { rmtFile.delete() ; }
    214                else {
    215                        // PUT(UPLOAD)登録時は、リモートファイルのディレクトリを作成する必要がある。
    216                        if( isMkdirs ) {
    217                                final String tmp = rmtFile.getParent();
    218                                final SmbFile dir = new SmbFile( tmp );
    219                                if( !dir.exists() ) { dir.mkdirs() ; }
    220                        }
    221                }
    222
    223                InputStream     input  = null;
    224                OutputStream    output = null;
    225                try {
    226                        input  = new FileInputStream( localFile );
    227                        output = new BufferedOutputStream( rmtFile.getOutputStream() );
    228
    229                        FileUtil.copy( input,output );
    230                }
    231                finally {
    232                        Closer.ioClose( input );
    233                        Closer.ioClose( output );
    234                }
    235        }
    236
    237        /**
    238         * command="DEL" が指定されたときの処理を行います。
    239         *
    240         * 接続先のSmbサーバー側のファイル名を削除します。
    241         *
    242         * @param       remoteFile      Smb先のファイル名
    243         * @throws IOException 入出力エラーが発生したとき
    244         */
    245        @Override       // AbstractConnect
    246        protected void actionDEL( final String remoteFile ) throws IOException {
    247                if( isDebug ) { System.out.println( "DEL: " + remoteFile ); }
    248
    249                final SmbFile rmtFile = makeSmbURI( remoteFile );
    250                rmtFile.delete() ;
    251        }
    252
    253        /**
    254         * command="DELDIR" が指定されたときの処理を行います。
    255         *
    256         * 接続先のSmbサーバー側のディレクトリ以下をローカルディレクトリに階層構造のままダウンロードします。
    257         *
    258         * @param       remoteDir       Smb先のディレクトリ名
    259         * @throws IOException 入出力エラーが発生したとき
    260         */
    261        @Override       // AbstractConnect
    262        protected void actionDELdir( final String remoteDir ) throws IOException {
    263                final SmbFile rmtFile = makeSmbURI( remoteDir );
    264                final SmbFile[] rmtFiles = rmtFile.listFiles();
    265                for( int i=0; i<rmtFiles.length; i++ ) {
    266                        final String rmt = addFile( remoteDir,rmtFiles[i].getName() );
    267                        if( rmtFiles[i].isDirectory() ) {
    268                                actionDELdir( rmt );
    269                        }
    270                        else {
    271                                actionDEL( rmt );
    272                        }
    273                }
    274                rmtFile.delete();
    275        }
    276
    277        /**
    278         * SMB形式のURLを作成します。
    279         *
    280         * 処理的には、内部で先に作成済みの connURI と、引数のファイルパスを文字列連結します。
    281         * connURI の最後には、"/" を付加していませんので、引数のファイルパスに、
    282         * "/" を含まない場合は、"/" を付加します。
    283         *
    284         *     smb://[[[domain;]username[:password]@]server[:port]/[[share/[dir/]file]]]
    285         *
    286         * @param       remoteFile      Smb先のファイル名
    287         *
    288         * @return      SMB形式のURL
    289         * @og.rtnNotNull
    290         */
    291        private SmbFile makeSmbURI( final String remoteFile ) throws MalformedURLException {
    292                final String smbFile ;
    293
    294                if( remoteFile.startsWith( "smb://" ) ) {
    295                        smbFile = remoteFile;
    296                }
    297                else if( StringUtil.startsChar( remoteFile , '/' ) ) {                  // 6.2.0.0 (2015/02/27) １文字 String.startsWith
    298                        smbFile = connURI + remoteFile ;
    299                }
    300                else {
    301                        smbFile = connURI + "/" + remoteFile ;
    302                }
    303
    304                return new SmbFile( smbFile );
    305        }
    306
    307        /**
    308         * 接続先にログインするドメインを設定します。
    309         *
    310         * @param       domain  接続先にログインするドメイン
    311         */
    312        public void setDomain( final String domain ) {
    313                this.domain = domain ;
    314        }
    315
    316        /**
    317         * このクラスの動作確認用の、main メソッドです。
    318         *
    319         * @param       args    コマンド引数配列
    320         */
    321        public static void main( final String[] args ) {
    322
    323                final String[] CMD_LST  = new String[] { "GET","PUT","DEL","GETDIR","PUTDIR","DELDIR" };
    324
    325                final Map<String,String> mustProparty   ;               // ［プロパティ］必須チェック用 Map
    326                final Map<String,String> usableProparty ;               // ［プロパティ］整合性チェック Map
    327
    328                mustProparty = new LinkedHashMap<>();
    329                mustProparty.put( "host",               "接続先のSmbサーバーのアドレスまたは、サーバー名(必須)" );
    330                mustProparty.put( "user",               "接続するユーザー名(必須)" );
    331                mustProparty.put( "passwd",             "接続するユーザーのパスワード(必須)" );
    332                mustProparty.put( "command",    "Smbサーバー側での処理の方法(GET/PUT/DEL/GETDIR/PUTDIR/DELDIR)を指定します。(必須)" );
    333                mustProparty.put( "remoteFile", "接続先のSmbサーバー側のファイル名(必須)" );
    334
    335                usableProparty = new LinkedHashMap<>();
    336                usableProparty.put( "localFile",        "ローカルのファイル名" );
    337                usableProparty.put( "domain",           "接続先にログインするドメインを指定します。" );
    338                usableProparty.put( "port",                     "接続に利用するポート番号を設定します。" );
    339                usableProparty.put( "mkdirs",           "受け側ファイル(GET時:LOCAL、PUT時:Smbサーバー)にディレクトリを作成するかどうか(初期値:true)" );
    340                usableProparty.put( "display",          "[false/true]:trueは、検索状況を表示します(初期値:false)" );
    341                usableProparty.put( "debug",            "デバッグ情報を標準出力に表示する(true)かしない(false)か" +
    342                                                                                        CR + "(初期値:false:表示しない)" );
    343
    344                // ******************************************************************************************************* //
    345                //       以下、単独で使用する場合の main処理
    346                // ******************************************************************************************************* //
    347                final Argument arg = new Argument( "org.opengion.fukurou.util.SMBConnect" );
    348                arg.setMustProparty( mustProparty );
    349                arg.setUsableProparty( usableProparty );
    350                arg.setArgument( args );
    351
    352                final SMBConnect smb = new SMBConnect();
    353
    354                final String host   = arg.getProparty( "host");                 // Smbサーバー
    355                final String user   = arg.getProparty( "user" );                        // ユーザー
    356                final String passwd = arg.getProparty( "passwd" );              // パスワード
    357
    358                smb.setHostUserPass( host,user,passwd );
    359
    360                smb.setDomain(  arg.getProparty( "domain"       ) );                                    // 接続先にログインするドメインを指定します。
    361                smb.setPort(    arg.getProparty( "port"         ) );                                    // 接続に利用するポート番号を設定します。
    362                smb.setMkdirs(  arg.getProparty( "mkdirs"       ,true           ) );            // 受け側ファイルにディレクトリを作成するかどうか
    363                smb.setDisplay( arg.getProparty( "display"      ,false          ) );            // trueは、検索状況を表示します(初期値:false)
    364                smb.setDebug(   arg.getProparty( "debug"        ,false          ) );            // デバッグ情報を標準出力に表示する(true)かしない(false)か
    365
    366                try {
    367                        // コネクトします。
    368                        smb.connect();
    369
    370                        final String command            = arg.getProparty( "command" ,"GET" ,CMD_LST  );        // Smb処理の方法を指定します。
    371                        final String localFile  = arg.getProparty( "localFile"  );                                      // ローカルのファイル名
    372                        final String remoteFile = arg.getProparty( "remoteFile" );                                      // Smb先のファイル名
    373
    374                        // command , localFile , remoteFile を元に、SFTP処理を行います。
    375                        smb.action( command,localFile,remoteFile );
    376                }
    377                catch( final RuntimeException ex ) {
    378                        System.err.println( smb.getErrMsg() );
    379                }
    380                finally {
    381                        // ホストとの接続を終了します。
    382                        smb.disconnect();
    383                }
    384        }
    385}