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