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// 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 */ 041public 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 * 第2引数には、コマンドが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; // プロセスの終了を待たないので、1:処理中 を返します。 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 ; // 1時間に設定 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}