blob: bc49e08d081f176f170e5c07a600a80746ab81b8 [file] [log] [blame]
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed 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.
package com.google.enterprise.adaptor.prebuilt;
import com.google.enterprise.adaptor.IOHelper;
import java.io.*;
/**
* Exec helper that allows easy handling of stdin, stdout, and stderr. Normally
* you have to worry about deadlock when dealing with those streams (as
* mentioned briefly in {@link Process}), so this class handles that for you.
* This class is very similar to {@link Command}, except it allows streaming
* stdin, stdout, and stderr, instead of buffering them.
*/
public class StreamingCommand {
private static final InputSource NO_INPUT_SOURCE = new NoInputSource();
private static final OutputSink DROP_OUTPUT_SINK = new DropOutputSink();
// Prevent instantiation.
private StreamingCommand() {}
/**
* Same as {@code exec(command, null, stdin, stdout, stderr)}.
*
* @see #exec(String[], File, InputSource, OutputSink, OutputSink)
*/
public static int exec(String[] command, InputSource stdin, OutputSink stdout,
OutputSink stderr) throws IOException, InterruptedException {
return exec(command, null, stdin, stdout, stderr);
}
/**
* Create process {@code command} starting in the {@code workingDir} and
* providing {@code stdin} as input. This method blocks until the process
* exits. Stdout and stderr must be consumed via the {@link OutputSink}s. If
* {@code stdin}, {@code stdout}, or {@code stderr} is {@code null}, then a
* bare implemention will be used.
*
* @return Process return code
* @throws IOException if creating process fails
*/
public static int exec(String[] command, File workingDir, InputSource stdin,
OutputSink stdout, OutputSink stderr)
throws IOException, InterruptedException {
if (stdin == null) {
stdin = NO_INPUT_SOURCE;
}
if (stdout == null) {
stdout = DROP_OUTPUT_SINK;
}
if (stderr == null) {
stderr = DROP_OUTPUT_SINK;
}
Process proc = Runtime.getRuntime().exec(command, null, workingDir);
Thread in, out, err;
in = new Thread(new InputSourceRunnable(stdin, proc.getOutputStream()));
in.setDaemon(true);
in.start();
out = new Thread(new OutputSinkRunnable(proc.getInputStream(), stdout));
out.setDaemon(true);
out.start();
err = new Thread(new OutputSinkRunnable(proc.getErrorStream(), stderr));
err.setDaemon(true);
err.start();
int returnCode;
try {
in.join();
out.join();
err.join();
returnCode = proc.waitFor();
} catch (InterruptedException ex) {
// Our threads should stop once the process closes.
// This destroy() is quite rude to the subprocess, but there is not any
// way to inform it to abort.
proc.destroy();
throw ex;
}
return returnCode;
}
/**
* {@link InputStream} to {@link InputSource} adaptor.
*/
public static InputSource streamInputSource(InputStream in) {
return new StreamInputSource(in);
}
/**
* {@link OutputStream} to {@link OutputSink} adaptor.
*/
public static OutputSink streamOutputSink(OutputStream out) {
return new StreamOutputSink(out);
}
private static void silentClose(Closeable closeable) {
try {
closeable.close();
} catch (IOException ex) {
// ignore
}
}
/**
* Content source that generates content at the rate it can be consumed.
*/
public static interface InputSource {
/**
* Generate content and write it to {@code out}.
*/
public void source(OutputStream out) throws IOException;
}
/**
* Content sink that consumes content as soon as it becomes available.
*/
public static interface OutputSink {
/**
* Consume content from {@code in}.
*/
public void sink(InputStream in) throws IOException;
}
private static class NoInputSource implements InputSource {
public void source(OutputStream out) {
// Don't write anything out.
}
}
private static class DropOutputSink implements OutputSink {
public void sink(InputStream in) throws IOException {
byte[] buffer = new byte[1024];
while (in.read(buffer) != -1) {
// Do nothing with input.
}
}
}
private static class StreamInputSource implements InputSource {
private final InputStream in;
public StreamInputSource(InputStream in) {
this.in = in;
}
@Override
public void source(OutputStream out) throws IOException {
IOHelper.copyStream(in, out);
}
}
private static class StreamOutputSink implements OutputSink {
private final OutputStream out;
public StreamOutputSink(OutputStream out) {
this.out = out;
}
@Override
public void sink(InputStream in) throws IOException {
IOHelper.copyStream(in, out);
}
}
private static class InputSourceRunnable implements Runnable {
private final InputSource source;
private final OutputStream out;
public InputSourceRunnable(InputSource source, OutputStream out) {
this.source = source;
this.out = out;
}
@Override
public void run() {
try {
source.source(out);
} catch (IOException ex) {
// Ignore, but stop thread.
} finally {
silentClose(out);
}
}
}
private static class OutputSinkRunnable implements Runnable {
private final InputStream in;
private final OutputSink sink;
public OutputSinkRunnable(InputStream in, OutputSink sink) {
this.in = in;
this.sink = sink;
}
@Override
public void run() {
try {
sink.sink(in);
} catch (IOException ex) {
// Ignore, but stop thread.
} finally {
silentClose(in);
}
}
}
}