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     */
016    package org.opengion.fukurou.util;
017    
018    import java.io.BufferedReader;
019    import java.io.InputStream;
020    import java.io.InputStreamReader;
021    import java.io.File;
022    import java.io.IOException;
023    // import java.text.DateFormat;
024    // import java.text.SimpleDateFormat;
025    // import java.util.Date;
026    // import java.util.Locale;
027    
028    /**
029     * Shell は、Runtime.exec の簡易的に実行するクラスです?
030     * ?な処??通常の Runtime.exec を使用する?がありますが?ほとんどの
031     * プロセス実行につ?は、このクラスで十?であると?て?す?
032     *
033     * こ?クラスでは、OS(特にWindows)でのバッチファイルの実行において?
034     * OS自動認識を行い、簡易的なコマンドをセ?する?で実行できるように
035     * して?す?
036     *
037     * @version  4.0
038     * @author   Kazuhiko Hasegawa
039     * @since    JDK5.0,
040     */
041    public class Shell {
042            /** Shell オブジェクト?状態を表します?正常  {@value} */
043            public static final int OK      = 0;            // 0:正常
044            /** Shell オブジェクト?状態を表します?実行中  {@value} */
045            public static final int RUNNING = 1;            // 1:実行中
046            /** Shell オブジェクト?状態を表します?取? {@value} */
047            public static final int CANCEL  = 9;            // 9:取?
048            /** Shell オブジェクト?状態を表します?異常終??  {@value} */
049            public static final int ERROR   = -1;           // -1:異常終??
050    
051            // private static final String CMD_95  = "C:\\windows\\command.com /c ";
052            private static final String CMD_NT  = "C:\\WINNT\\system32\\cmd.exe /c ";
053            private static final String CMD_XP  = "C:\\WINDOWS\\system32\\cmd.exe /c ";
054            private static final String OS_NAME = System.getProperty("os.name");
055            private static final String CR      = System.getProperty("line.separator");
056            private String          command         = null;
057            private File            workDir         = null;
058            private String[]        envp            = null;
059            private boolean         isWait          = true;         // プロセスの終??かど? (?ォル??)
060            private Process         prcs            = null;
061            private ProcessReader pr1               = null;
062            private ProcessReader pr2               = null;
063            private int     rtnCode                 = ERROR;        // 0:正常  1:実行中  9:取? -1:異常終??
064    //      private final DateFormat formatter = new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss",Locale.JAPAN );
065    
066            // 3.6.1.0 (2005/01/05) タイ?ウト時間を設?
067            private long timeout                    = 0 ;   // 初期値は、タイ?ウトな?
068    
069            // 3.8.9.2 (2007/07/13) Windows Vista対?
070            // 5.6.7.1 (2013/07/09) NTでもunknown時?CMD_XPとする
071            private static final String CMD_COM ;
072            static {
073                    if( (OS_NAME.indexOf( "NT" ) >= 0 ||
074                                    OS_NAME.indexOf( "2000" ) >= 0)
075                            && OS_NAME.indexOf( "unknown" ) < 0 ) {
076                                    CMD_COM = CMD_NT ;
077                    }
078            //      else if( OS_NAME.indexOf( "XP" ) >= 0 ||
079            //                       OS_NAME.indexOf( "2003" ) >= 0
080            //                       OS_NAME.indexOf( "Vista" ) >= 0 ) {
081            //                      CMD_COM = CMD_XP ;
082            //      }
083                    else {
084                            CMD_COM = CMD_XP ;
085                    }
086            }
087    
088            /**
089             * プロセスを実行する時に引き渡すコマン?
090             * 第?引数には、コマンドがBATかEXEかを?できます?
091             * true の場合??バ?コマンドとして処?れます?
092             *
093             * @og.rev 3.3.3.0 (2003/07/09) Windows XP 対?
094             * @og.rev 3.7.0.1 (2005/01/31) Windows 2003 対? Windows 95 除?
095             * @og.rev 3.8.9.2 (2007/07/13) Windows Vista 対?
096             *
097             * @param       cmd     コマン?
098             * @param       batch   true:バッチファイル/false:EXEファイル
099             */
100            public void setCommand( final String cmd,final boolean batch ) {
101                    if( batch ) {
102                            command = CMD_COM + cmd;
103                    }
104                    else {
105                            command = cmd ;
106                    }
107            }
108    
109            /**
110             * プロセスを実行する時に引き渡すコマン?
111             *
112             * @param   cmd EXEコマン?
113             */
114            public void setCommand( final String cmd ) {
115                    setCommand( cmd,false );
116            }
117    
118            /**
119             * プロセスの実行???終??かど?
120             *
121             * @param       flag    true:?(?ォル?/ false:?な?
122             */
123            public void setWait( final boolean flag ) {
124                    isWait = flag;
125            }
126    
127            /**
128             * プロセスの実行???タイ?ウトを設定します?
129             * ゼロ(0) の場合?、割り込みが?るまで?つづけます?
130             *
131             * @param       tout    タイ?ウト時?? ゼロは、無制?
132             *
133             */
134            public void setTimeout( final int tout ) {
135                    timeout = (long)tout * 1000;
136            }
137    
138            /**
139             * 作業?レクトリを指定します?
140             *
141             * シェルを実行する?作業?レクトリを指定します?
142             * ?しな??合?、このJava仮想マシンの作業?レクトリで実行されます?
143             *
144             * @param   dir 作業?レクトリ
145             */
146            public void setWorkDir( final File dir ) {
147                    workDir = dir;
148            }
149    
150            /**
151             * 環?数設定?配??します?
152             *
153             * 環?数を?name=value と?形式で、文字?配?で?します?
154             * null の場合?、現在のプロセスの環?定を継承します?
155             *
156             * @param   env ??の配?。?列????、name=value と?形式で環?数設定を保持する?
157             */
158            public void setEnvP( final String[] env ) {
159                    if( env != null && env.length > 0 ) {
160                            int size = env.length;
161                            envp = new String[size];
162                            System.arraycopy( env,0,envp,0,size );
163                    }
164                    else {
165                            envp = null;
166                    }
167            }
168    
169            /**
170             * プロセスの実行??
171             *
172             * @return  サブ?ロセスの終?ードを返します?0 は正常終?示?
173             */
174            public int exec() {
175                    Runtime rt = Runtime.getRuntime();
176                    Thread wait = null;
177                    try {
178                            prcs = rt.exec( command,envp,workDir );         // 3.3.3.0 (2003/07/09)
179                            pr1 = new ProcessReader( prcs.getInputStream() );
180                            pr1.start();
181                            pr2 = new ProcessReader( prcs.getErrorStream() );
182                            pr2.start();
183    
184                            if( isWait ) {
185                                    // 3.6.1.0 (2005/01/05)
186                                    wait = new WaitJoin( timeout,prcs );
187                                    wait.start();
188                                    rtnCode = prcs.waitFor();
189                                    if( rtnCode > OK ) { rtnCode = -rtnCode; }
190                            }
191                            else {
192                                    rtnCode = RUNNING;      // プロセスの終??な??で?:処? を返します?
193                            }
194                    }
195                    catch(IOException ex) {
196                            String errMsg = "入出力エラーが発生しました?;
197                            LogWriter.log( errMsg );
198                            LogWriter.log( ex );
199                    }
200                    catch(InterruptedException ex) {
201                            String errMsg = "現在のスレ?が?中にほか?スレ?によって強制終?れました?;
202                            LogWriter.log( errMsg );
203                            LogWriter.log( ex );
204                    }
205                    finally {
206                            if( wait != null ) { wait.interrupt(); }
207                    }
208    
209                    return rtnCode;
210            }
211    
212            /**
213             * プロセスの実行時の標準?力を取得します?
214             *
215             * @return 実行時の標準?力文字?
216             */
217            public String getStdoutData() {
218                    final String rtn ;
219                    if( pr1 == null ) {
220                            rtn = "\n.......... Process is not Running. ....";
221                    }
222                    else if( pr1.isEnd() ) {
223                            rtn = pr1.getString();
224                    }
225                    else {
226                            rtn = pr1.getString() + "\n......... stdout Process is under execution. ...";
227                    }
228                    return rtn ;
229            }
230    
231            /**
232             * プロセスの実行時のエラー出力を取得します?
233             *
234             * @return 実行時の標準?力文字?
235             */
236            public String getStderrData() {
237                    final String rtn ;
238                    if( pr2 == null ) {
239                            rtn = "\n.......... Process is not Running. ....";
240                    }
241                    else if( pr2.isEnd() ) {
242                            rtn = pr2.getString();
243                    }
244                    else {
245                            rtn = pr2.getString() + "\n......... stderr Process is under execution. ...";
246                    }
247                    return rtn ;
248            }
249    
250            /**
251             * プロセスが実際に実行するコマンドを取得します?
252             * バッチコマンドかど?で、実行されるコマンドが異なります?で?
253             * ここで取得して確認することができます?
254             * 主に??用途です?
255             *
256             * @return 実行時の標準?力文字?
257             */
258            public String getCommand() {
259                    return command;
260            }
261    
262            /**
263             * サブ?ロセスを終?ます?
264             * こ? Process オブジェクトが表すサブ?ロセスは強制終?れます?
265             *
266             */
267            public void destroy() {
268                    if( prcs != null ) { prcs.destroy() ; }
269                    rtnCode = CANCEL;
270            }
271    
272            /**
273             * プロセスが終?て?かど?[true/false]を確認します?
274             * こ? Process オブジェクトが表すサブ?ロセスは強制終?れます?
275             *
276             * @return      プロセスが終?て?かど?[true/false]
277             */
278            public boolean isEnd() {
279                    boolean flag = true;
280                    if( rtnCode == RUNNING ) {
281                            flag = pr1.isEnd() && pr2.isEnd() ;
282                            if( flag ) { rtnCode = OK; }
283                    }
284                    return flag ;
285            }
286    
287            /**
288             * サブ?ロセスの終?ードを返します?
289             *
290             * @return こ? Process オブジェクトが表すサブ?ロセスの終?ード?0 は正常終?示?
291             * @throws  IllegalThreadStateException こ? Process オブジェクトが表すサブ?ロセスがま??て????
292             */
293            public int exitValue() {
294                    if( rtnCode == RUNNING && isEnd() ) {
295                            rtnCode = prcs.exitValue();
296                            if( rtnCode > OK ) { rtnCode = -rtnCode ; }
297                    }
298                    return rtnCode;
299            }
300    
301            /**
302             * こ? Shell のインフォメーション(??)を?力します?
303             * コマンド?開始時刻、終?刻、状?実行中、終?などの??を?
304             * 出力します?
305             *
306             * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します?
307             *
308             * @return      インフォメーション(??)
309             */
310            @Override
311            public String toString() {
312                    boolean isEnd = isEnd() ;
313    //              String st = formatter.format( new Date( pr1.getStartTime() ) ) ;
314    //              String ed = ( isEnd ) ? formatter.format( new Date( pr1.getEndTime() ) ) : "----/--/-- --:--:--" ;
315                    String st = HybsDateUtil.getDate( pr1.getStartTime() , "yyyy/MM/dd HH:mm:ss" ) ;
316                    String ed = ( isEnd ) ? HybsDateUtil.getDate( pr1.getEndTime() , "yyyy/MM/dd HH:mm:ss" ) : "----/--/-- --:--:--" ;
317    
318                    StringBuilder buf = new StringBuilder();
319                    buf.append( "command     = [" ).append( getCommand() ).append( "]\n" );
320                    buf.append( "  isEnd     = [" ).append( isEnd        ).append( "]\n" );
321                    buf.append( "  rtnCode   = [" ).append( exitValue()  ).append( "]\n" );
322                    buf.append( "  startTime = [" ).append( st           ).append( "]\n" );
323                    buf.append( "  endTime   = [" ).append( ed           ).append( "]\n" );
324    
325                    return buf.toString();
326            }
327    
328            /**
329             * stdout と stderr の取得をスレ?化する為のインナ?クラスです?
330             * これ自身が?Thread の サブクラスになって?す?
331             *
332             * @version  4.0
333             * @author   Kazuhiko Hasegawa
334             * @since    JDK5.0,
335             */
336            static class ProcessReader extends Thread {
337                    private final BufferedReader in ;
338                    private final StringBuilder inStream = new StringBuilder();
339                    private boolean  endFlag = false;
340                    private long    startTime       = -1;
341                    private long    endTime         = -1;
342    
343                    /**
344                     * コンストラクター?
345                     *
346                     * ここで、スレ?化したい入力ストリー?引数に、オブジェクトを生?します?
347                     *
348                     * @param ins InputStream 入力ストリー?
349                     *
350                     */
351                    ProcessReader( InputStream ins ) {
352    //                      in = new BufferedReader( new InputStreamReader(ins) );
353                            in = new BufferedReader( new InputStreamReader(ins,StringUtil.DEFAULT_CHARSET) );       // 5.5.2.6 (2012/05/25) findbugs対?
354                            setDaemon( true );              // 3.5.4.6 (2004/01/30)
355                    }
356    
357                    /**
358                     * Thread が実行された場合に呼び出される?run メソ?です?
359                     *
360                     * Thread のサブクラスは、このメソ?をオーバ?ライドしなければなりません?
361                     *
362                     */
363                    public void run() {
364                            startTime = System.currentTimeMillis() ;
365                            String outline;
366                            try {
367                                    while ((outline = in.readLine()) != null) {
368                                            inStream.append( outline );
369                                            inStream.append( CR );
370                                    }
371                            }
372                            catch(IOException ex) {
373                                    String errMsg = "入出力エラーが発生しました?;
374                                    LogWriter.log( errMsg );
375                                    LogWriter.log( ex );
376                            }
377                            finally {
378                                    Closer.ioClose( in );
379                            }
380                            endTime = System.currentTimeMillis() ;
381                            endFlag = true;
382                    }
383    
384                    /**
385                     * 現在書き込みが行われて?ストリー???にして返します?
386                     *
387                     * @return      ストリー????
388                     *
389                     */
390                    public String getString() {
391                            return inStream.toString();
392                    }
393    
394                    /**
395                     * ストリー?ら?読取が終?て?か確認します?
396                     *
397                     * @return      読取終?true) / 読み取り中(false)
398                     *
399                     */
400                    public boolean isEnd() {
401                            return endFlag;
402                    }
403    
404                    /**
405                     * ストリー????開始時刻を返します?
406                     * 開始して??態??1 を返します?
407                     *
408                     * @return      開始時刻
409                     *
410                     */
411                    public long getStartTime() {
412                            return startTime;
413                    }
414    
415                    /**
416                     * ストリー????終?刻を返します?
417                     * 終?て??態??1 を返します?
418                     *
419                     * @return      終?刻
420                     *
421                     */
422                    public long getEndTime() {
423                            return endTime;
424                    }
425            }
426    
427            /**
428             * スレ?のウェイト??ラス
429             * ??タイ?ウト時間が来ると、設定されたプロセスを?強制終?destroy)します?
430             * ??プロセス側は、??終?た?合?、このThreadに、割り込み(interrupt)
431             * をかけて、この処?のも?を終?せてください?
432             *
433             * @version  4.0
434             * @author   Kazuhiko Hasegawa
435             * @since    JDK5.0,
436             */
437            static class WaitJoin extends Thread {
438                    private static final long MAX_WAIT = 3600 * 1000 ;      // ?時間に設?
439    
440                    private final long wait ;
441                    private final Process prcs;
442    
443                    /**
444                     * コンストラクター
445                     *
446                     * @param wait long ウェイトする時?ミリ?
447                     * @param prcs Process 強制終?destroy) させる?ロセス
448                     */
449                    WaitJoin( final long wait,Process prcs ) {
450                            this.wait = ( wait > 0L ) ? wait : MAX_WAIT ;
451                            this.prcs = prcs;
452                    }
453    
454                    /**
455                     * Thread の run() メソ?
456                     * コンストラクタで??ミリ秒だけウェイトし、それが経過すると?
457                     * ??プロセスを強制終?destroy)させます?
458                     * 外部より割り込み(interrupt)があると、ウェイト状態から復帰します?
459                     * 先に割り込みが?って?場合?、wait せずに抜けます?
460                     *
461                     * @og.rev 5.4.2.2 (2011/12/14) Threadでwaitをかける場合?synchronized しな?エラーにな?対?
462                     */
463                    public void run() {
464                            try {
465                                    long startTime = System.currentTimeMillis() ;
466                                    boolean waitFlag = true;
467                                    synchronized( this ) {
468                                            while( ! isInterrupted() && waitFlag ) {
469                                                    wait( wait );
470                                                    waitFlag = ( startTime + wait ) > System.currentTimeMillis() ;
471                                            }
472                                    }
473                                    prcs.destroy() ;
474                                    System.out.println( "タイ?ウトにより強制終?ました? );
475                            }
476                            catch( InterruptedException ex ) {
477                                    LogWriter.log( "終?ました? );
478                            }
479                    }
480            }
481    }