blob: acee6fc11ac8b3ee9ff9fd5993b20b8147520226 [file] [log] [blame]
package org.apache.helix;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Wrapper for running commands outside of the JVM
* @see {@link Process}
*/
public class ExternalCommand {
public static final String MODULE = ExternalCommand.class.getName();
public static final Logger LOG = LoggerFactory.getLogger(MODULE);
private final ProcessBuilder _processBuilder;
private Process _process;
private InputReader _out;
private InputReader _err;
/**
* Stream redirector
*/
private static class InputReader extends Thread {
private static final int BUFFER_SIZE = 2048;
private final InputStream _in;
private final ByteArrayOutputStream _out;
private boolean _running = false;
InputReader(InputStream in) {
_in = in;
_out = new ByteArrayOutputStream();
}
@Override
public void run() {
_running = true;
byte[] buf = new byte[BUFFER_SIZE];
int n = 0;
try {
while ((n = _in.read(buf)) != -1)
_out.write(buf, 0, n);
} catch (IOException e) {
LOG.error("error while reading external command", e);
}
_running = false;
}
public byte[] getOutput() {
if (_running)
throw new IllegalStateException("wait for process to be completed");
return _out.toByteArray();
}
}
/**
* Initialize with a {@link ProcessBuilder}
* @param processBuilder initialized {@link ProcessBuilder} object
*/
public ExternalCommand(ProcessBuilder processBuilder) {
_processBuilder = processBuilder;
}
/**
* After creating the command, you have to start it...
* @throws IOException
*/
public void start() throws IOException {
_process = _processBuilder.start();
_out = new InputReader(new BufferedInputStream(_process.getInputStream()));
_err = new InputReader(new BufferedInputStream(_process.getErrorStream()));
_out.start();
_err.start();
}
/**
* @see {@link ProcessBuilder#environment()}
*/
public Map<String, String> getEnvironment() {
return _processBuilder.environment();
}
/**
* @see {@link ProcessBuilder#directory()}
*/
public File getWorkingDirectory() {
return _processBuilder.directory();
}
/**
* @see {@link ProcessBuilder#directory(File)}
*/
public void setWorkingDirectory(File directory) {
_processBuilder.directory(directory);
}
/**
* @see {@link ProcessBuilder#redirectErrorStream()}
*/
public boolean getRedirectErrorStream() {
return _processBuilder.redirectErrorStream();
}
/**
* @see {@link ProcessBuilder#redirectErrorStream(boolean)}
*/
public void setRedirectErrorStream(boolean redirectErrorStream) {
_processBuilder.redirectErrorStream(redirectErrorStream);
}
/**
* Get the contents of the output stream after completion
* @return bytes from the output stream
* @throws InterruptedException the process was interrupted before completion
*/
public byte[] getOutput() throws InterruptedException {
waitFor();
return _out.getOutput();
}
/**
* Get the contents of the error stream after completion
* @return bytes from the error stream
* @throws InterruptedException the process was interrupted before completion
*/
public byte[] getError() throws InterruptedException {
waitFor();
return _err.getOutput();
}
/**
* Returns the output as a string.
* @param encoding string encoding scheme, e.g. "UTF-8"
* @return encoded string
* @throws InterruptedException the process was interrupted before completion
* @throws UnsupportedEncodingException the encoding scheme is invalid
*/
public String getStringOutput(String encoding) throws InterruptedException,
UnsupportedEncodingException {
return new String(getOutput(), encoding);
}
/**
* Returns the output as a string. Uses encoding "UTF-8".
* @return utf8 encoded string
* @throws InterruptedException the process was interrupted before completion
*/
public String getStringOutput() throws InterruptedException {
try {
return getStringOutput("UTF-8");
} catch (UnsupportedEncodingException e) {
// should not happen
throw new RuntimeException(e);
}
}
/**
* Returns the error as a string.
* @param encoding the encoding scheme, e.g. "UTF-8"
* @return error as string
* @throws InterruptedException the process was interrupted before completion
* @throws UnsupportedEncodingException the encoding scheme is invalid
*/
public String getStringError(String encoding) throws InterruptedException,
UnsupportedEncodingException {
return new String(getError(), encoding);
}
/**
* Returns the error as a string. Uses encoding "UTF-8".
* @return error as string
* @throws InterruptedException the process was interrupted before completion
*/
public String getStringError() throws InterruptedException {
try {
return getStringError("UTF-8");
} catch (UnsupportedEncodingException e) {
// should not happen
throw new RuntimeException(e);
}
}
/**
* Properly waits until everything is complete: joins on the thread that
* reads the output, joins on the thread that reads the error and finally
* wait for the process to be finished.
* @return the status code of the process.
* @throws InterruptedException the process was interrupted before completion
*/
public int waitFor() throws InterruptedException {
if (_process == null)
throw new IllegalStateException("you must call start first");
_out.join();
_err.join();
return _process.waitFor();
}
/**
* Properly waits until everything is complete: joins on the thread that
* reads the output, joins on the thread that reads the error and finally
* wait for the process to be finished.
* If the process has not completed before the timeout, throws a {@link TimeoutException}
* @return the status code of the process.
* @throws TimeoutException the process timed out
* @throws InterruptedException the process was interrupted before completion
*/
public int waitFor(long timeout) throws InterruptedException, TimeoutException {
if (_process == null)
throw new IllegalStateException("you must call start first");
// Chronos c = new Chronos();
_out.join(timeout);
// timeout -= c.tick();
if (timeout <= 0)
throw new TimeoutException("Wait timed out");
_err.join(timeout);
// timeout -= c.tick();
if (timeout <= 0)
throw new TimeoutException("Wait timed out");
// there is no timeout in this API, not much we can do here
// waiting on the other two threads should give us some safety
return _process.waitFor();
}
/**
* @see {@link Process#exitValue()}
* @return the return code of the process
*/
public int exitValue() {
if (_process == null)
throw new IllegalStateException("you must call start first");
return _process.exitValue();
}
/**
* see {@link Process#destroy()}
*/
public void destroy() {
if (_process == null)
throw new IllegalStateException("you must call start first");
_process.destroy();
}
/**
* Creates an external process from the command. It is not started and you have to call
* start on it!
* @param commands the command to execute
* @return the process
*/
public static ExternalCommand create(String... commands) {
ExternalCommand ec = new ExternalCommand(new ProcessBuilder(commands));
return ec;
}
/**
* Creates an external process from the command. It is not started and you have to call
* start on it!
* @param commands the command to execute
* @return the process
*/
public static ExternalCommand create(List<String> commands) {
ExternalCommand ec = new ExternalCommand(new ProcessBuilder(commands));
return ec;
}
/**
* Creates an external process from the command. The command is executed.
* @param commands the commands to execute
* @return the process
* @throws IOException if there is an error
*/
public static ExternalCommand start(String... commands) throws IOException {
ExternalCommand ec = new ExternalCommand(new ProcessBuilder(commands));
ec.start();
return ec;
}
/**
* Executes the external command in the given working directory and waits for it to be
* finished.
* @param workingDirectory the root directory from where to run the command
* @param command the command to execute (should be relative to the working directory
* @param args the arguments to the command
* @return the process
*/
public static ExternalCommand execute(File workingDirectory, String command, String... args)
throws IOException, InterruptedException {
try {
return executeWithTimeout(workingDirectory, command, 0, args);
} catch (TimeoutException e) {
// Can't happen!
throw new IllegalStateException(MODULE + ".execute: Unexpected timeout occurred!");
}
}
/**
* Executes the external command in the given working directory and waits (until timeout
* is elapsed) for it to be finished.
* @param workingDirectory
* the root directory from where to run the command
* @param command
* the command to execute (should be relative to the working directory
* @param timeout
* the maximum amount of time to wait for this external command (in ms). If
* this value is less than or equal to 0, timeout is ignored
* @param args
* the arguments to the command
* @return the process
*/
public static ExternalCommand executeWithTimeout(File workingDirectory, String command,
long timeout, String... args) throws IOException, InterruptedException, TimeoutException {
List<String> arguments = new ArrayList<String>(args.length + 1);
arguments.add(new File(workingDirectory, command).getAbsolutePath());
arguments.addAll(Arrays.asList(args));
ExternalCommand cmd = ExternalCommand.create(arguments);
cmd.setWorkingDirectory(workingDirectory);
cmd.setRedirectErrorStream(true);
cmd.start();
/* Use timeout if it is a valid value! */
if (timeout <= 0)
cmd.waitFor();
else
cmd.waitFor(timeout);
if (LOG.isDebugEnabled())
LOG.debug(cmd.getStringOutput());
return cmd;
}
}