/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.gmx.util.jvmcontrol; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.util.log.Log; /** * <p>Title: LaunchedJVMProcess</p> * <p>Description: Represents a launched JVM process.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.gmx.util.jvmcontrol.LaunchedJVMProcess</code></p> */ public class LaunchedJVMProcess { /** The native OS process ID */ private final String processId; /** The Java Process */ private final Process process; /** The exit code from the process */ protected final AtomicInteger exitCode = new AtomicInteger(Integer.MAX_VALUE); /** A list of the arguments to this process */ private final List<String> arguments; /** The process standard out reader thread */ private final Thread stdOutThread; /** The out queue */ private final Queue<String> outQueue = new ArrayBlockingQueue<String>(100, false); /** The process standard err reader thread */ private final Thread stdErrThread; /** The err queue */ private final Queue<String> errQueue = new ArrayBlockingQueue<String>(100, false); /** A map of LaunchedJVMProcesses keyed by the processId */ private static final Map<String, LaunchedJVMProcess> processes = new ConcurrentHashMap<String, LaunchedJVMProcess>(); /** The thread group that stream reader threads run in */ private static final ThreadGroup STREAMER_THREAD_GROUP = new ThreadGroup("LaunchedJVMProcessStreamThreads"); /** * Returns an existing LaunchedJVMProcess * @param processId The process ID of the LaunchedJVMProcess to retrieve * @return The keyed LaunchedJVMProcess or null if one was not found for the passed process ID. */ static LaunchedJVMProcess getLaunchedJVMProcess(String processId) { if(processId==null) throw new IllegalArgumentException("The passed processId was null", new Throwable()); return processes.get(processId); } /** * Creates a new LaunchedJVMProcess * @param processId The native OS process ID * @param process The java process * @param arguments A list of the arguments to this JVM launch * @return a new LaunchedJVMProcess or an existing cached process. */ static LaunchedJVMProcess newInstance(String processId, Process process, List<String> arguments) { if(processId==null) throw new IllegalArgumentException("The passed processId was null", new Throwable()); LaunchedJVMProcess jvmProcess = processes.get(processId); if(jvmProcess==null) { synchronized(processes) { jvmProcess = processes.get(processId); if(jvmProcess==null) { if(process==null) throw new IllegalArgumentException("The passed process was null", new Throwable()); jvmProcess = new LaunchedJVMProcess(processId, process, arguments); } } } return jvmProcess; } /** * Creates a new LaunchedJVMProcess * @param processId The native OS process ID * @param process The java process * @param arguments A list of the arguments to this JVM launch */ private LaunchedJVMProcess(final String processId, final Process process, final List<String> arguments) { super(); this.processId = processId; this.process = process; this.arguments = arguments; stdOutThread = newStreamReaderThread("Out", this.processId, this.process.getInputStream(), outQueue); stdErrThread = newStreamReaderThread("Err", this.processId, this.process.getErrorStream(), errQueue); final LaunchedJVMProcess jvmp = this; Thread pWatcher = new Thread(STREAMER_THREAD_GROUP, "ProcessWatcher-" + processId) { public void run() { try { exitCode.set(process.waitFor()); if(stdOutThread.isAlive()) stdOutThread.interrupt(); if(stdErrThread.isAlive()) stdErrThread.interrupt(); processes.remove(jvmp); } catch (InterruptedException ie) { Thread.interrupted(); } } }; pWatcher.setDaemon(true); pWatcher.start(); } /** * Returns the next line of standard out from the process * @return the next line of standard out from the process or null if one was not available. */ public String nextOut() { return outQueue.poll(); } /** * Returns the next line of standard err from the process * @return the next line of standard err from the process or null if one was not available. */ public String nextErr() { return errQueue.poll(); } /** * Drains the out queue and returns the content in a string array * @return a string array */ public String[] drainOut() { List<String> drain = new ArrayList<String>(outQueue.size()); outQueue.removeAll(drain); return drain.toArray(new String[drain.size()]); } /** * Drains the err queue and returns the content in a string array * @return a string array */ public String[] drainErr() { List<String> drain = new ArrayList<String>(errQueue.size()); errQueue.removeAll(drain); return drain.toArray(new String[drain.size()]); } /** * Returns the native OS process ID * @return the processId */ public String getProcessId() { return processId; } /** * Returns the Java process representing the process * @return the process */ public Process getProcess() { return process; } /** * {@inheritDoc} * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((processId == null) ? 0 : processId.hashCode()); return result; } /** * {@inheritDoc} * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; LaunchedJVMProcess other = (LaunchedJVMProcess) obj; if (processId == null) { if (other.processId != null) return false; } else if (!processId.equals(other.processId)) return false; return true; } /** * Kills the JVM process * @see java.lang.Process#destroy() */ public void destroy() { try { stdOutThread.interrupt(); } catch (Exception e) {} try { stdErrThread.interrupt(); } catch (Exception e) {} outQueue.clear(); errQueue.clear(); process.destroy(); } public int stop() { OutputStream os = process.getOutputStream(); try { os.write("STOP\n".getBytes()); os.flush(); Thread.sleep(200); System.out.println("Pending JVM Output:" + outQueue); System.err.println("Pending JVM Errput:" + errQueue); // try { stdOutThread.interrupt(); } catch (Exception e) {} // try { stdErrThread.interrupt(); } catch (Exception e) {} outQueue.clear(); errQueue.clear(); int code = process.waitFor(); return code; } catch (Exception e) { throw new RuntimeException("Failed to call stop on JVM", e); } } /** * Returns the exit value for the subprocess. * @return the exit value for the subprocess. * @see java.lang.Process#exitValue() */ public Integer exitValue() { if(isRunning()) { return null; } else { return exitCode.get(); } } /** * Determines if the process is still running * @return true if the process is still running, false otherwise */ public boolean isRunning() { return exitCode.get()==Integer.MAX_VALUE; } /** * Returns the error stream of the subprocess. * @return the error stream of the subprocess. * @see java.lang.Process#getErrorStream() */ public InputStream getErrorStream() { return process.getErrorStream(); } /** * Returns the input stream of the subprocess. * @return the input stream of the subprocess. * @see java.lang.Process#getInputStream() */ public InputStream getInputStream() { return process.getInputStream(); } /** * Returns the output stream of the subprocess. * @return the output stream of the subprocess. * @see java.lang.Process#getOutputStream() */ public OutputStream getOutputStream() { return process.getOutputStream(); } /** * Causes the current thread to wait, if necessary, until the process represented by this Process object has terminated. * @return the exit value of the process. By convention, 0 indicates normal termination. * @throws InterruptedException thra string representation of this processown if the waiting thread is interrupted while waiting for the process to complete. * @see java.lang.Process#waitFor() */ public int waitFor() throws InterruptedException { return process.waitFor(); } /** * Creates a new stream reader for the process * @param type The type of reader ("Out" or "Err") * @param processId The process Id. * @param is The input stream to read * @param lineQueue The queue to write output to * @return the thread */ protected static Thread newStreamReaderThread(String type, String processId, final InputStream is, final Queue<String> lineQueue) { if(type==null || (!type.equals("Out") && !type.equals("Err"))) throw new IllegalArgumentException("The passed type [" + type + "] was invalid", new Throwable()); Thread t = new Thread(STREAMER_THREAD_GROUP, processId + "-" + type + "-Reader") { InputStreamReader isr = null; BufferedReader br = null; public void run() { while(true) { try { if(isr==null) isr = new InputStreamReader(is); if(br==null) br = new BufferedReader(isr); lineQueue.add(br.readLine()); } catch (Exception e) { if(e instanceof InterruptedException) { break; } } finally { if(br!=null) try { br.close(); } catch (Exception e){} if(isr!=null) try { isr.close(); } catch (Exception e){} } } } }; t.setDaemon(true); t.start(); return t; } /** * Constructs a <code>String</code> with all attributes in <code>name:value</code> format. * @return a <code>String</code> representation of this object. */ public String toString() { final String TAB = "\n\t"; StringBuilder retValue = new StringBuilder(); retValue.append("LaunchedJVMProcess [") .append(TAB).append("processId:").append(this.processId) .append(TAB).append("isRunning:").append(this.isRunning()) .append(TAB).append("process:").append(this.process) .append(TAB).append("outQueue ["); for(String line: new ArrayList<String>(outQueue)) { retValue.append("\n\t\t").append(line); } retValue.append("\n\t]") .append(TAB).append("errQueue ["); for(String line: new ArrayList<String>(errQueue)) { retValue.append("\n\t\t").append(line); } retValue.append("\n\t]") .append(TAB).append("arguments ["); for(String arg: arguments) { retValue.append("\n\t\t").append(arg); } retValue.append("\n\t]") .append("\n]"); return retValue.toString(); } }