package com.allywll.core.exec.base; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeoutException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import com.allywll.core.util.PeriodicalTask; import com.gzit.core.util.ArrayUtil; /** * 对一个外部进程执行过程的抽象 * * @author gzit team * */ public class ProcessorExecutor implements CmdExecutor { private static Logger logger = Logger.getLogger("PROCESSOR"); // 进程的执行路径 protected String workingDirectory; // 进程输出的字符编码 // 默认使用平台的字符编码 private String outputEncoding = Charset.defaultCharset().name(); // 进程的执行命令 protected String cmd; // 进程的执行参数 protected String[] args; // 进程执行的超时时间 :单位为毫秒,-1不考虑超时机制,有超时的话将外部进程destroy掉 protected long timeout = -1; protected long beginTime;// 进程执行的开始时间 protected long endTime;// 进程执行的结束时间 protected Process process; // 当前执行的进程 protected boolean blocking;// 当前执行的进程是否正处在阻塞之中 protected boolean flagTimeout;// 是否发生超时异常 /** * * 进程执行过程的监控(超时以及其它的状态检测) * */ private static class ExecutorMonitor extends PeriodicalTask { private Logger elogger = Logger.getLogger("PROCESSOR"); private List<ProcessorExecutor> executors = new LinkedList<ProcessorExecutor>(); public ExecutorMonitor(int period) { super(period); } public void reg(ProcessorExecutor exec) { this.executors.add(exec); } public synchronized void deReg(ProcessorExecutor exec) { if (exec != null) { this.executors.remove(exec); } } @Override protected void doWork() throws Exception { long now = System.currentTimeMillis(); for (ProcessorExecutor exec : executors) { if (now - exec.beginTime > exec.timeout) { try { if (exec.isBlocking()) {// 还处在阻塞中,则删除掉 elogger.info("检测到进程【" + exec + "】执行超时,强制停止之"); exec.flagTimeout = true; // 设定超时标记 exec.process.destroy(); elogger.info("强制停止进程【" + exec + "】完成!"); } } catch (Exception e) { // ignore elogger.error("强制停止进程【" + exec + "】失败!原因:" + e.getMessage()); } finally { // 释放掉对外部进程的引用 this.deReg(exec); } } } } } // 10秒钟检测一次 static ExecutorMonitor monitor = new ExecutorMonitor(10 * 1000); static { logger.info("启动外部进程执行状态检测线程..."); monitor.setDaemon(true);// 设定为daemon线程... monitor.start(); } /** * 辅助类 对进程的标准输出和错误输出流处理,方便对相应流读取操作 * */ private class ProcessStream extends Thread { private InputStream stream = null; private String output = null; public ProcessStream(InputStream in) { this.stream = in; } public void run() { BufferedReader reader = null; try { if (this.stream == null) { logger.error("指定的流为空,流读取线程返回..."); return; } reader = new BufferedReader(new InputStreamReader(stream, outputEncoding)); StringBuffer sb = new StringBuffer(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line); sb.append("/n"); } this.output = sb.toString(); } catch (IOException e1) { // ignore } finally { IOUtils.closeQuietly(reader); } } public String getOutput() { return output; } public void setOutput(String output) { this.output = output; } } // 合并参数处理命令和参数 protected String[] mergeCmd() { String[] args = this.args(); String[] commands = new String[args.length + 1]; commands[0] = this.command(); for (int i = 0; i < args.length; i++) { commands[i + 1] = args[i]; } return commands; } // 等带进程执行 private int waitExecuting() throws InterruptedException, TimeoutException { int code = this.process.waitFor(); if (this.flagTimeout) { throw new TimeoutException(this + " 进程执行超时!exitCode【" + code + "】"); } return code; } /** * 初始化执行上下文 */ public void initContext() { } /** * 执行外部进程并等待返回 */ public CmdExecResult execute() { // 执行之前初始化执行上下文 this.initContext(); CmdExecResult result = new CmdExecResult(); // 1.合并参数 String[] commands = mergeCmd(); logger.debug(this + ":ProcessBuilder开始调用外部进程,参数【" + ArrayUtil.join(commands) + "】"); this.beginTime = System.currentTimeMillis(); try { ProcessBuilder pb = new ProcessBuilder(commands); // 设置可能的工作目录 if (!StringUtils.isBlank(this.workingDirectory)) { pb.directory(new File(workingDirectory)); } // 2. 执行命令 this.process = pb.start(); this.blocking = true;// 设置进程的阻塞状态 if (this.timeout != -1) { monitor.reg(this);// 需要超时控制的话,则将外部进程的执行注册进检测线程 } // 3.处理返回结果 // 开启独立的线程来处理外部进程的输入流和错误流 ProcessStream errStreamReader = new ProcessStream(process .getErrorStream()); ProcessStream stdStreamReader = new ProcessStream(process .getInputStream()); errStreamReader.start(); stdStreamReader.start(); int rcode = this.waitExecuting(); logger.debug(this + ":ProcessBuilder调用外部进程返回,返回码【" + rcode + "】"); String errorOutput = errStreamReader.getOutput(); String output = stdStreamReader.getOutput(); if (!justifyCmdOutput(errorOutput, output)) { result.setOk(true);// 命令执行没有异常 result.setCmdOk(false);// 命令返回指示有错误写入错误流 result.setOutput(errorOutput); return result; } // 外部进程执行成功 result.setOk(true); result.setCmdOk(true); result.setOutput(output); } catch (Exception e) { e.printStackTrace(); String msg = "进程执行出现异常,原因:" + e.getMessage(); logger.error(msg); result.setOk(false); result.setCmdOk(false); result.setDesc(msg); } finally { this.endTime = System.currentTimeMillis();// 记录外部进程执行退出的时间 if (this.timeout != -1) {// 有超时控制的话,执行完则取消在监视队列中的检测 monitor.deReg(this); } if (process != null) { try { process.destroy(); } catch (Exception e) { e.printStackTrace(); } } } return result; } // 判断命令是否执行成功 // 子类可以覆盖掉该方法,来对命令的输出结果做出决策 // 默认情况下,认为错误输出流中有内容命令就是执行了 protected boolean justifyCmdOutput(String errOutput, String stdOutput) { if (!StringUtils.isBlank(errOutput)) { return false; } return true; } // 获得进程的执行时间 public long getElapse() { return this.endTime - this.beginTime; } public String getWorkingDirectory() { return workingDirectory; } public void setWorkingDirectory(String workingDirectory) { this.workingDirectory = workingDirectory; } public String getOutputEncoding() { return outputEncoding; } public void setOutputEncoding(String outputEncoding) { this.outputEncoding = outputEncoding; } public String getCmd() { return cmd; } public void setCmd(String cmd) { this.cmd = cmd; } public String[] getArgs() { return args; } public void setArgs(String[] args) { this.args = args; } public String[] args() { return this.args; } public String command() { return this.cmd; } public long getTimeout() { return timeout; } public void setTimeout(long timeout) { if (timeout <= 0) { throw new IllegalArgumentException("进程的执行的超时参数不能<=0,当前【" + timeout + "】"); } this.timeout = timeout; } public long getBeginTime() { return beginTime; } public void setBeginTime(long beginTime) { this.beginTime = beginTime; } public long getEndTime() { return endTime; } public void setEndTime(long endTime) { this.endTime = endTime; } public boolean isBlocking() { return blocking; } public void setBlocking(boolean blocking) { this.blocking = blocking; } public String toString() { return this.cmd + this.args; } }