/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program 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 General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.core.system;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.rhq.core.util.exec.ProcessExecutionOutputStream;
/**
* Provides information on what process to execute and how to execute it.
* <p/>
* Agent plugin developers should also see the ProcessExecutionUtility class in the plugin-api module, which provides
* handy methods for creating <code>ProcessExecution</code>s.
*
* @author John Mazzitelli
* @author Ian Springer
* @author Jay Shaughnessy
*
* @see org.rhq.core.system.JavaSystemInfo#executeProcess(ProcessExecution)
*/
public class ProcessExecution {
private String executable;
private List<String> arguments;
private Map<String, String> environmentVariables;
private String workingDirectory;
private long waitForCompletion = 30000L;
private boolean killOnTimeout = false;
private boolean checkExecutableExists = true;
private CaptureMode captureMode = CaptureMode.none();
/**
* Constructor for {@link ProcessExecution} that defines the full path to the executable that will be run. See the
* other setter methods in this class for the additional things you can set when executing a process.
*
* @param executable the full path to the executable that will be run
*/
public ProcessExecution(String executable) {
if (executable == null)
throw new IllegalArgumentException("executable cannot be null");
setExecutable(executable);
}
@NotNull
public String getExecutable() {
return executable;
}
/**
* Sets the full path to the executable that will be run.
*
* @param executable the full path to the executable that will be run
*/
public void setExecutable(@NotNull
String executable) {
this.executable = executable;
}
/**
* Obtain the optional set of arguments to the executable as List.
* @return List of arguments or null if no arguments are set.
*/
@Nullable
public List<String> getArguments() {
return arguments;
}
/**
* Obtain the optional set of arguments to the executable as String array
* @return Array of arguments or null if no arguments are set.
*/
@Nullable
public String[] getArgumentsAsArray() {
String[] argArray;
if (this.arguments == null) {
argArray = null;
} else {
argArray = this.arguments.toArray(new String[this.arguments.size()]);
}
return argArray;
}
/**
* Sets an optional set of arguments to pass to the executable.
* <p/>
* Windows Note! This will overwrite internal arguments set by the constructor. Use {@link #addArguments} on Windows.
*
* @param arguments an optional set of arguments to pass to the executable
*/
public void setArguments(@Nullable
List<String> arguments) {
this.arguments = arguments;
}
/**
* Adds an optional set of arguments to the current arguments passed to the executable.
*
* @param arguments an optional set of arguments to pass to the executable. Not null.
*/
public void addArguments(List<String> arguments) {
if (null == this.arguments) {
this.arguments = arguments;
} else {
this.arguments.addAll(arguments);
}
}
/**
* Sets an optional set of arguments to pass to the executable.
*
* @param arguments an optional set of arguments to pass to the executable
*/
public void setArguments(@Nullable
String[] arguments) {
this.arguments = new ArrayList<String>(Arrays.asList(arguments));
}
@Nullable
public Map<String, String> getEnvironmentVariables() {
return environmentVariables;
}
/**
* Returns a copy of this ProcessExecution's environment variables as an array of "name=value" Strings. Note, since
* the array is only a copy of the environmentVariables property, modifications made to it will have no effect on
* this ProcessExecution.
*
* @return a copy of this ProcessExecution's environment variables as an array of "name=value" Strings
*/
@Nullable
public String[] getEnvironmentVariablesAsArray() {
String[] envVarArray;
if (this.environmentVariables == null) {
envVarArray = null;
} else {
envVarArray = new String[this.environmentVariables.size()];
int i = 0;
for (String varName : this.environmentVariables.keySet()) {
envVarArray[i++] = varName + "=" + this.environmentVariables.get(varName);
}
}
return envVarArray;
}
/**
* Sets an optional set of environment variables to pass to the process. If <code>null</code>, the new process will
* inherit the environment of the caller.
*
* @param environmentVariables an optional set of environment variables to pass to the process
*/
public void setEnvironmentVariables(@Nullable
Map<String, String> environmentVariables) {
this.environmentVariables = environmentVariables;
}
@Nullable
public String getWorkingDirectory() {
return workingDirectory;
}
/**
* If not <code>null</code>, will be the working directory of the new process (if <code>null</code>, the new
* process's working directory will be the current working directory of caller).
*
* @param workingDirectory The directory the process should get as working directory.
*/
public void setWorkingDirectory(@Nullable
String workingDirectory) {
this.workingDirectory = workingDirectory;
}
public long getWaitForCompletion() {
return waitForCompletion;
}
/**
* The time, in milliseconds, to wait for the process to exit (will not wait if 0 or less).
*
* @param waitForCompletion The wait time in ms.
*/
public void setWaitForCompletion(long waitForCompletion) {
this.waitForCompletion = waitForCompletion;
}
/**
*
* @return whether capture process output
* @deprecated
*/
public boolean isCaptureOutput() {
return this.captureMode.isCapture();
}
/**
* If <code>true</code>, the process's output will be captured and returned in the results. This may be ignored if
* <code>waitForCompletion</code> is 0 or less. Be careful setting this to <code>true</code>, you must ensure that
* the process will not write a lot of output - you might run out of memory if the process is a long-lived daemon
* process that outputs a lot of log messages, for example. By default, output is *not* captured.
* @deprecated @see {@link #setCaptureMode(CaptureMode)}
* @param captureOutput whether or not this process's output (stdout+stderr) should be captured and returned in the
* results
*/
public void setCaptureOutput(boolean captureOutput) {
this.captureMode = captureOutput ? CaptureMode.memory() : CaptureMode.none();
}
/**
* get process output capture mode
* @return process output capture mode
*/
public CaptureMode getCaptureMode() {
return captureMode;
}
public void setCaptureMode(CaptureMode captureMode) {
this.captureMode = captureMode;
}
public boolean isKillOnTimeout() {
return killOnTimeout;
}
/**
* If <code>true</code>, then the process will be forcibly killed if it doesn't exit within the
* {@link #getWaitForCompletion() wait time}. If <code>false</code>, the process will be allowed to continue to run
* for as long as it needs - {@link #getWaitForCompletion()} will only force the caller to "wake up" and not block
* waiting for the process to finish.
*
* @param killOnTimeout Should the process be killed after the timeout timed out?
*/
public void setKillOnTimeout(boolean killOnTimeout) {
this.killOnTimeout = killOnTimeout;
}
/**
* If <code>true</code>, then the executable should first be checked for its existence.
* If the executable does not exist, the execution should fail-fast. If <code>false</code>,
* the process will attempt to be executed no matter what. This will allow the operating
* system to check its executable PATH to find the executable as necessary.
*
* @return check flag (default is <code>true</code>)
*/
public boolean isCheckExecutableExists() {
return checkExecutableExists;
}
public void setCheckExecutableExists(boolean checkExecutableExists) {
this.checkExecutableExists = checkExecutableExists;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder("ProcessExecution: ");
buf.append("executable=[").append(this.executable);
buf.append("], args=[").append(this.arguments);
buf.append("], env-vars=[").append(this.environmentVariables);
buf.append("], working-dir=[").append(this.workingDirectory);
buf.append("], wait=[").append(this.waitForCompletion);
buf.append("], capture-mode=[").append(this.captureMode);
buf.append("], kill-on-timeout=[").append(this.killOnTimeout);
buf.append("], executable-is-command=[").append(this.checkExecutableExists);
buf.append("]");
return buf.toString();
}
/**
* Process output capture mode.
*
* @author lzoubek@redhat.com
*
*/
public static class CaptureMode {
/**
* The process's output is *not* captured, this is the default.
* @return captureMode
*/
public static CaptureMode none() {
return new CaptureMode(false);
}
/**
* The process's output will be captured and returned in the results. This may be ignored if
* <code>waitForCompletion</code> is 0 or less. By default capturing to memory is limited to 2MB of
* process output. If the process writes more output, it will be ignored.
* @return captureMode
*/
public static CaptureMode memory() {
return new CaptureMode(true);
}
/**
* The process's output will be captured and returned in the results. This may be ignored if
* <code>waitForCompletion</code> is 0 or less. With <code>limit</code> parameter you can set maximum captured output size.
* If the process writes more output, it will be ignored.
*
* @param limit in Bytes (if given value < 0, it's ignored and default 2MB is used instead)
* @return captureMode
*/
public static CaptureMode memory(int limit) {
return new CaptureMode(true, limit);
}
/**
* The process's output will be captured and returned in the results. This may be ignored if
* <code>waitForCompletion</code> is 0 or less. Process output will be redirected to agent.log and at the same time
* captured into memory. By default capturing to memory is limited to 2MB of process output. If the process writes more output,
* it will only be redirected to agent.log.
*
* @return captureMode
*/
public static CaptureMode agentLog() {
return new CaptureMode(true, true, -1);
}
/**
* The process's output will be captured and returned in the results. This may be ignored if
* <code>waitForCompletion</code> is 0 or less. Process output will be logged into agent.log and at the same time
* captured into memory. With <code>limit</code> parameter you can set maximum memory buffer to be captured (and possibly returned)
* captured output size. If the process writes more output, it will only be redirected to agent.log.
*
* @param limit in Bytes (if given value < 0, it's ignored and default 2MB is used instead)
* @return captureMode
*/
public static CaptureMode agentLog(int limit) {
return new CaptureMode(true, true, limit);
}
private final boolean capture;
private final int limit;
private final boolean log;
private CaptureMode(boolean capture) {
this(capture, -1);
}
private CaptureMode(boolean capture, int limit) {
this(capture, false, limit);
}
private CaptureMode(boolean capture, boolean log, int limit) {
this.capture = capture;
this.log = log;
this.limit = limit;
}
/**
*
* @return true if capturing is enabled
*/
public boolean isCapture() {
return capture;
}
/**
*
* @return captured output size limit in Bytes, -1 if default should be used
*/
public int getLimit() {
return limit;
}
/**
*
* @return true if output should be forwarded to logging subsystem
*/
public boolean isLog() {
return log;
}
ProcessExecutionOutputStream createOutputStream() {
if (!this.capture) {
// capturing is disabled still return some output stream (this instance ignores everything)
return new ProcessExecutionOutputStream(0, this.log);
}
if (this.limit > 0) {
return new ProcessExecutionOutputStream(this.limit, this.log);
}
return new ProcessExecutionOutputStream(this.log);
}
@Override
public String toString() {
return new StringBuilder("CaptureMode: ")
.append(" [capture="+isCapture())
.append("], [memory-limit=" + getLimit() / 1024 + "kB")
.append("], [log="+isLog())
.append("]")
.toString();
}
}
}