/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.enterprise.util;
//JDK imports
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.FileReader;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.lang.IllegalArgumentException;
/**
@author Kedar
@version 1.0
*/
public class ProcessExecutor
{
public static final long kDefaultTimeoutMillis = 600000;
public static final long kSleepTime = 2000;
private static final long DEFAULT_TIMEOUT_SEC = 600;
private static final String NEWLINE = System.getProperty("line.separator");
private long mTimeoutMilliseconds = 0;
protected String[] mCmdStrings = null;
protected File mOutFile = null;
protected File mErrFile = null;
private OutputStream mOutStream = null;
private OutputStream mErrStream = null;
private File mWorkingDir = null; //working directory
private String[] mEnv = null; //environment
private String[] mInputLines = null; // strings to set in process's InputStream (like from redirection)
private int mExitValue = -1;
private Process mSubProcess=null; // used to get handle to child process for ProcessManager funtionality
private boolean mVerboseMode = false;
private boolean retainExecutionLogs = false;
private String lastExecutionOutputString = null;
private String lastExecutionErrorString = null;
private static boolean bDebug=false;
/**
Creates new ProcessExecutor
*/
public ProcessExecutor(String[] cmd)
{
this(cmd, DEFAULT_TIMEOUT_SEC, null);
}
/**
Creates new ProcessExecutor
*/
public ProcessExecutor(String[] cmd, String[] inputLines)
{
this(cmd, DEFAULT_TIMEOUT_SEC, inputLines);
}
/**
Creates new ProcessExecutor
*/
public ProcessExecutor(String[] cmd, long timeoutSeconds)
{
this(cmd, timeoutSeconds, null);
}
public ProcessExecutor(String[] cmd, long timeoutSeconds, String[] inputLines)
{
this(cmd, timeoutSeconds, inputLines, null, null);
}
/**
Creates a new ProcessExecutor
that executes the given
command.
@param cmd String that has command name and its command line arguments
@param timeoutSeconds long integer timeout to be applied in seconds. After this time
if the process to execute does not end, it will be destroyed.
*/
public ProcessExecutor(String[] cmd, long timeoutSeconds, String[] inputLines,
String[] env, File workingDir)
{
mCmdStrings = cmd;
mInputLines = inputLines;
mEnv = env;
mWorkingDir = workingDir;
char fwdSlashChar = '/';
char backSlashChar = '\\';
if (System.getProperty("Debug") != null) {
// turn on debug, this option was added to help developers
// debug the their code
bDebug=true;
}
for(int i=0; i= mTimeoutMilliseconds;
try
{
mExitValue = mSubProcess.exitValue();
isSubProcessFinished = true;
}
catch(IllegalThreadStateException itse)
{
isSubProcessFinished = false;
//ignore exception
}
shouldBeDone = timeoutReached || isSubProcessFinished;
}
if (!isSubProcessFinished)
{
mSubProcess.destroy();
mExitValue = -255;
throw new ExecException("Subprocess timed out after "+mTimeoutMilliseconds +"mS");
}
else
{
mExitValue = mSubProcess.exitValue();
if (debug()) {
System.out.println("Subprocess command line = " + a2s(mCmdStrings));
System.out.println("Subprocess exit value = " + mExitValue);
}
if (mExitValue != 0)
{
mExitValue = mSubProcess.exitValue();
if (mExitValue != 0)
{
throw new ExecException(getExceptionMessage());
}
}
}
}
}
catch(SecurityException se)
{
throw new ExecException(se.getMessage());
}
catch(IOException ioe)
{
throw new ExecException(ioe.getMessage());
}
finally {
// retain buffers before deleting them
retainBuffers();
// only delete files if the time is limited
// for processes that don't return, the temp files will remain
if (bStartUpTimeLimit) {
deleteTempFiles();
}
}
if(bReturnOutputLines) {
return getInputStrings(inputStream);
} else {
return null;
}
}
/**
* Get the exit value of the process executed. If this method is called
* before process execution is complete (i.e. before execute() method has
* returned, it will return -1. If sub process is terminated at timeout,
* the method will return -255
*/
public int getProcessExitValue() {
return mExitValue;
}
private void addInputLinesToProcessInput(Process subProcess) throws ExecException {
if(mInputLines==null)
return;
PrintWriter out = null;
try
{
out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(subProcess.getOutputStream())));
for(int i=0; i" + mInputLines[i] + "<-");
}
out.println(mInputLines[i]);
}
out.flush();
}
catch (Exception e)
{
throw new ExecException(e.getMessage());
}
finally
{
try
{
out.close();
}
catch (Throwable t)
{
}
}
}
private String[] getInputStrings(InputStream inputStream) throws ExecException
{
if(inputStream==null)
return null;
BufferedReader in = null;
ArrayList list = new ArrayList();
String str;
try
{
in = new BufferedReader( new InputStreamReader(inputStream));
while((str=in.readLine())!=null)
list.add(str);
if(list.size()<1)
return null;
return (String[])list.toArray(new String[list.size()]);
}
catch (Exception e)
{
throw new ExecException(e.getMessage());
}
finally
{
try
{
in.close();
}
catch (Throwable t)
{
}
}
}
private OutputStream redirectProcessOutput(Process subProcess) throws ExecException
{
OutputStream out = null;
try
{
InputStream in = subProcess.getInputStream();
// Redirect stderr for verbose mode
if(mVerboseMode) {
// send output to stderr
out=System.err;
} else {
// send to temp file
out=new FileOutputStream(mOutFile);
}
new FlusherThread(in, out).start();
}
catch (Exception e)
{
throw new ExecException(e.getMessage());
}
return out;
}
private OutputStream redirectProcessError(Process subProcess) throws ExecException
{
OutputStream out = null;
try
{
InputStream in = subProcess.getErrorStream();
// Redirect stderr for verbose mode
if(mVerboseMode) {
// send output to stderr
out=System.err;
} else {
// send to temp file
out=new FileOutputStream(mErrFile);
}
new FlusherThread(in, out).start();
}
catch (Exception e)
{
throw new ExecException(e.getMessage());
}
return out;
}
public void setVerbose(boolean verbose) {
mVerboseMode=verbose;
}
private void sleep (long millis)
{
try
{
Thread.sleep(millis);
}
catch(InterruptedException ie)
{
//ignore exception
}
}
/** Returns the contents of a file as a String. It never returns a null. If
* the file is empty, an empty string is returned.
* @param file the file to read
*/
protected String getFileBuffer(File file) {
final StringBuffer sb = new StringBuffer();
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append(NEWLINE);
}
}
catch(Exception e) {
//squelch the exception
}
finally {
try {
reader.close();
}
catch(Exception e) {}
}
return ( sb.toString() );
}
protected String getLatestOutput(final File f) {
return ( new RAFileReader(f).readLastBytesAsString() );
}
public void retainBuffers() {
if (this.retainExecutionLogs) {
this.lastExecutionErrorString = this.getLatestOutput(this.mErrFile);
this.lastExecutionOutputString = this.getLatestOutput(this.mOutFile);
}
}
private boolean debug() {
final String td = System.getProperty("java.io.tmpdir");
final String n = "as_debug_process_executor"; // a debug hook
final File f = new File(td, n);
return ( f.exists() );
}
private String a2s(String[] a) {
final StringBuffer s = new StringBuffer();
if (a != null) {
for (int i = 0 ; i < a.length ; i++) {
s.append(a[i]);
s.append(" ");
}
}
return ( s.toString() );
}
private static class RAFileReader {
final File file;
final int LAST_BYTES = 16384;
final String RMODE = "r"; //read
RAFileReader(final File file) {
this.file= file;
}
String readLastBytesAsString() {
final int n = getNumberOfBytes(LAST_BYTES);
final StringBuffer sb = new StringBuffer();
final long ln = file.length(); //if SecurityManager is not present, this is safe.
if (ln == 0)
return ( sb.toString() ); //nothing to read, file may not exist, is protected, is a directory etc.
assert (n <= ln) : ("Asked to read number of bytes more than size of file");
final long s = ln - n;
return ( readWithoutCheck(s) );
}
private String readWithoutCheck(final long seekPos) {
final StringBuffer sb = new StringBuffer();
RandomAccessFile rf = null;
long ln = 0L;
int lines = 0;
try {
rf = new RandomAccessFile(file, RMODE);
ln = rf.length();
rf.seek(seekPos);
String tmp = rf.readLine();
while (tmp != null) {
lines++;
sb.append(tmp);
//sb.append(Character.LINE_SEPARATOR);
sb.append('\n'); // adding a newline character is going to add one extra byte
tmp = rf.readLine();
}
}
catch (Exception e) {
//e.printStackTrace(); //ignore
}
finally {
try {
if (rf != null)
rf.close();
}
catch(Exception e) {}//ignore;
}
//System.out.println("ln-seekPos = " + (ln - seekPos) );
//System.out.println("bytes = " + sb.toString().getBytes().length);
//System.out.println("lines = " + lines);
//assert ((ln - seekPos) == (sb.toString().getBytes().length + lines)) : "Wrong number of bytes read";
return ( sb.toString() );
}
private int getNumberOfBytes(final int max) {
final long ln = file.length();
return ( max >= ln ? (int)ln : max );
}
}
// used for ProcessManager to watchdog subProcess
public Process getSubProcess() {
return mSubProcess;
}
public static void main(String args[])
{
testProcessError();
}
/* This method tests the condition of process throwing an error.
* On Unixlike systems this throws an error in error file. On non-unixlike
* Systems it will throw IOException for CreateProcess, which is desired */
private static void testProcessError() {
ProcessExecutor executor = new ProcessExecutor(
new String[]{"/usr/bin/ls", "-wrongPARAMS123"});
try {
executor.execute();
}
catch (ExecException ee) {
System.out.println(ee.getMessage());
}
}
}
/**
* inner class to flush runtime.exec process so it doesn't hang
*/
class FlusherThread extends Thread {
InputStream mInStream = null;
OutputStream mOutStream = null;
public static final int kSize = 1024;
FlusherThread(InputStream in, OutputStream out) {
mInStream = in;
mOutStream = out;
}
public void run() {
// check for null stream
if (mInStream == null) return;
// transfer bytes from input to output stream
try {
int byteCnt=0;
byte[] buffer=new byte[4096];
while ((byteCnt=mInStream.read(buffer)) != -1) {
if (mOutStream != null && byteCnt > 0) {
mOutStream.write(buffer, 0, byteCnt);
mOutStream.flush();
}
yield();
}
} catch (IOException e) {
// ignore
} finally {
try {
mOutStream.close();
} catch (IOException ioe) {
// ignore
}
}
}
}