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