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