blob: 65d2fa8a9895e3da49a7dc07d407c3415587eb08 [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 adaptorlib.prebuilt;
import adaptorlib.DocumentTransform;
import adaptorlib.IOHelper;
import adaptorlib.TransformException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* A conduit that allows a simple way to create a document transform based on
* a command line program.
*/
public class CommandLineTransform extends DocumentTransform {
private static final int STDERR_BUFFER_SIZE = 51200; // 50 kB
public CommandLineTransform(String name) {
super(name);
}
@Override
public void transform(ByteArrayOutputStream contentIn, ByteArrayOutputStream metadataIn,
OutputStream contentOut, OutputStream metadataOut,
Map<String, String> params) throws TransformException, IOException {
// Java Processes can only take input on 1 channel, stdin. If we want
// to have two separate channels, we would need to either:
// - Have two separate commands that get called for content and metadata
// - Write metadata to file and send filename as parameter.
// - Send them both in through stdin with some separator.
//
// We don't yet support sending metadata with content, so this version of
// CommandLineTransform only handles the content. In the case of HTML, the
// metadata is baked in anyway.
List<String> command = Arrays.asList(transformCommand.split(" "));
if (commandAcceptsParameters) {
for (Map.Entry<String, String> param : params.entrySet()) {
command.add("-" + param.getKey());
command.add(param.getValue());
}
}
ProcessBuilder pb = new ProcessBuilder(command);
pb.redirectErrorStream(false); // We want 2 streams to come out for stdout and stderr.
if (workingDirectory != null && !workingDirectory.isEmpty()) {
pb.directory(new File(workingDirectory));
}
Process p = pb.start();
// Streams can be confusing because an input to one component is an output
// to another based on the frame of reference.
// Here, stdin is an OutputStream, because we write to it.
// stdout and stderr are InputStreams, because we read from them.
OutputStream stdin = p.getOutputStream();
InputStream stdout = p.getInputStream();
InputStream stderr = p.getErrorStream();
try {
// Run it
contentIn.writeTo(stdin);
stdin.close(); // Necessary for some commands that expect EOF.
int exitCode = p.waitFor();
// Handle stderr
if (exitCode != 0 || stderr.available() > 0) {
byte[] buf = new byte[STDERR_BUFFER_SIZE];
stderr.read(buf);
throw new TransformException(new String(buf));
}
// Copy stdout
IOHelper.copyStream(stdout, contentOut);
} catch (InterruptedException e) {
throw new TransformException(e);
} finally {
stdin.close();
stdout.close();
stderr.close();
p.destroy();
}
}
/**
* This controls whether the input parameters to the transform call are passed
* along to the actual call to the command. This is useful in the case where a
* binary might return erros when unexpected command line flags are passed in.
*/
public void commandAcceptsParameters(boolean commandAcceptsParameters) {
this.commandAcceptsParameters = commandAcceptsParameters;
}
public boolean commandAcceptsParameters() {
return commandAcceptsParameters;
}
/**
* Sets the command that is in charge of transforming the document content.
* This command should take input on stdin, and print the output to stdout.
* Transform parameters are sent on the command line as flags.
* e.g. /path/to/command -param1 value1 -param2 value2 ...
*
* Errors should be printed to stderr. If anything is printed to stderr, it
* will cause a failure for this transform operation.
*/
public void transformCommand(String transformCommand) {
this.transformCommand = transformCommand;
}
/**
* Sets the working directory. Must be valid.
* @return true on success.
*/
public boolean workingDirectory(String dir) {
File file = new File(dir);
if (file.isDirectory()) {
workingDirectory = dir;
return true;
}
return false;
}
/**
* @return The working directory for the command line process.
*/
public String workingDirectory() {
return workingDirectory;
}
private boolean commandAcceptsParameters = true;
private String transformCommand = "";
private String workingDirectory = null;
}