/**
* Copyright (C) 2010 Orbeon, Inc.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
*
* 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 Lesser General Public License for more details.
*
* The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
*/
package org.orbeon.oxf.processor.execute;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.ExecTask;
import org.apache.tools.ant.types.Commandline;
import org.orbeon.dom.Document;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.processor.*;
import org.orbeon.oxf.xml.XMLReceiver;
import org.orbeon.oxf.xml.XMLReceiverHelper;
import org.orbeon.oxf.xml.XPathUtils;
import org.xml.sax.ContentHandler;
import java.io.File;
/**
* The Execute Processor allows executing an external command.
*
* TODO: Currently, we store the outputs using ant properties. This can cause the processor to take up a lot of memory.
*
* TODO: Handle stdin if connected.
*/
public class ExecuteProcessor extends ProcessorImpl {
// private static Logger logger = LoggerFactory.createLogger(ExecuteProcessor.class);
public static final String EXECUTE_PROCESSOR_CONFIG_NAMESPACE_URI = "http://orbeon.org/oxf/xml/execute-processor-config";
private static final String OUTPUT_PROPERTY = "org.orbeon.oxf.processor.execute.output";
private static final String ERROR_PROPERTY = "org.orbeon.oxf.processor.execute.error";
private static final String RESULT_PROPERTY = "org.orbeon.oxf.processor.execute.result";
// private static final String INPUT_STDIN = "stdin";
private static final String OUTPUT_STDOUT = "stdout";
private static final String OUTPUT_STDERR = "stderr";
private static final String OUTPUT_RESULT = "result";
public ExecuteProcessor() {
addInputInfo(new ProcessorInputOutputInfo(INPUT_CONFIG, EXECUTE_PROCESSOR_CONFIG_NAMESPACE_URI));
// addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_PROPERTY)); // optional
// addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_STDERR)); // optional
// addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_RESULT )); // optional
}
private static class Config {
public String executable;
public String dir;
public boolean spawn;
public String output;
public String error;
public boolean logError;
public boolean append;
public String input;
public String inputstring;
public String resultproperty;
public Integer timeout;
public boolean failonerror; // -> exception?
public boolean failifexecutionfails; // -> exception?
public boolean newenvironment;
public boolean vmlauncher;
public boolean resolveexecutable;
public boolean searchpath;
public String argLine;
public Config(Document document) {
// Directory and file
executable = XPathUtils.selectStringValueNormalize(document, "/*/@executable");
dir = XPathUtils.selectStringValueNormalize(document, "/*/@dir");
spawn = ProcessorUtils.selectBooleanValue(document, "/*/@spawn", false);
output = XPathUtils.selectStringValueNormalize(document, "/*/@output");
error = XPathUtils.selectStringValueNormalize(document, "/*/@error");
logError = ProcessorUtils.selectBooleanValue(document, "/*/@logError", false);
append = ProcessorUtils.selectBooleanValue(document, "/*/@append", false);
input = XPathUtils.selectStringValueNormalize(document, "/*/@input");
inputstring = XPathUtils.selectStringValueNormalize(document, "/*/@inputstring");
timeout = XPathUtils.selectIntegerValue(document, "/*/@timeout");
failonerror = ProcessorUtils.selectBooleanValue(document, "/*/@error", false);
failifexecutionfails = ProcessorUtils.selectBooleanValue(document, "/*/@failifexecutionfails", true);
newenvironment = ProcessorUtils.selectBooleanValue(document, "/*/@newenvironment", false);
vmlauncher = ProcessorUtils.selectBooleanValue(document, "/*/@vmlauncher", true);
resolveexecutable = ProcessorUtils.selectBooleanValue(document, "/*/@resolveexecutable", false);
searchpath = ProcessorUtils.selectBooleanValue(document, "/*/@searchpath", false);
// TODO: other args options
argLine = XPathUtils.selectStringValue(document, "/*/arg[1]/@line");
// value
// file
// path
// pathref [no]
// TODO: set max size for output
}
}
public void doIt(PipelineContext pipelineContext, String outputName, XMLReceiver xmlReceiver) {
try {
// Read config
final Config config = readCacheInputAsObject(pipelineContext, getInputByName(INPUT_CONFIG), new CacheableInputReader<Config>() {
public Config read(PipelineContext context, ProcessorInput input) {
return new Config(readInputAsOrbeonDom(context, input));
}
});
// Create task
final ExecTask execTask = new ExecTask();
final Project project = new Project();
execTask.setProject(project);
// Set mandatory executable
execTask.setExecutable(config.executable);
if (config.dir != null)
execTask.setDir(new File(config.dir));
// Set optional command-line arguments
if (config.argLine != null) {
final Commandline.Argument argument = execTask.createArg();
argument.setLine(config.argLine);
}
// Set optional spawn
execTask.setSpawn(config.spawn);
// Set output file if available
final File outputFile;
if (config.output != null) {
outputFile = new File(config.output);
} else {
// final FileItem fileItem = XMLUtils.prepareFileItem(pipelineContext);
// if (fileItem instanceof DefaultFileItem) {
// final DefaultFileItem defaultFileItem = (DefaultFileItem) fileItem;
// outputFile = defaultFileItem.getStoreLocation();
// } else {
outputFile = null;
// }
}
if (outputFile != null)
execTask.setOutput(outputFile);
// Set output property
execTask.setOutputproperty(OUTPUT_PROPERTY);
// Handle result code
execTask.setResultProperty(RESULT_PROPERTY);
// Set error file is available
final File errorFile;
if (config.error != null) {
errorFile = new File(config.error);
} else {
errorFile = null;
}
if (errorFile != null)
execTask.setError(errorFile);
// Set error property
execTask.setErrorProperty(ERROR_PROPERTY);
// Set optional append
execTask.setAppend(config.append);
// TODO: Handle input
// execTask.setInput(config.input;
// execTask.setInputString(config.inputstring);
//
// Set command timeout
if (config.timeout != null)
execTask.setTimeout(config.timeout);
// Other boolean properties
execTask.setFailonerror(config.failonerror);
execTask.setFailIfExecutionFails(config.failifexecutionfails);
execTask.setNewenvironment(config.newenvironment);
execTask.setVMLauncher(config.vmlauncher);
execTask.setResolveExecutable(config.resolveexecutable);
execTask.setSearchPath(config.searchpath);
// logger.info("xxx before execute");
execTask.execute();
// logger.info("xxx after execute");
final State state = (State) getState(pipelineContext);
state.isRead = true;
if (xmlReceiver != null) {
// Output or remember stdout
state.outputString = project.getProperty(OUTPUT_PROPERTY);
if (outputName.equals(OUTPUT_STDOUT)) {
outputStdout(xmlReceiver, state);
}
// Output or remember stderr
state.errorString = project.getProperty(ERROR_PROPERTY);
if (outputName.equals(OUTPUT_STDERR)) {
outputStderr(xmlReceiver, state);
}
// Output or remember result
state.result = project.getProperty(RESULT_PROPERTY);
// logger.info("xxx: result " + state.result);
if (outputName.equals(OUTPUT_RESULT)) {
outputResult(xmlReceiver, state);
}
}
} catch (Exception e) {
throw new OXFException(e);
}
}
private void outputStdout(ContentHandler contentHandler, State state) {
BinaryTextSupport.readText(state.outputString, contentHandler, "text/plain", null);
state.outputString = null;
}
private void outputStderr(ContentHandler contentHandler, State state) {
BinaryTextSupport.readText(state.errorString, contentHandler, "text/plain", null);
state.errorString = null;
}
private void outputResult(XMLReceiver xmlReceiver, State state) {
final XMLReceiverHelper helper = new XMLReceiverHelper(xmlReceiver);
helper.startDocument();
helper.startElement("result");
helper.text(state.result);
helper.endElement();
helper.endDocument();
}
/**
* Case where an output must be generated.
*/
@Override
public ProcessorOutput createOutput(final String outputName) {
final ProcessorOutput output = new ProcessorOutputImpl(ExecuteProcessor.this, outputName) {
public void readImpl(final PipelineContext pipelineContext, XMLReceiver xmlReceiver) {
final State state = (State) getState(pipelineContext);
if (!state.isRead) {
// We haven't run the command yet
doIt(pipelineContext, outputName, xmlReceiver);
} else {
// We have already run the command
if (outputName.equals(OUTPUT_STDOUT)) {
outputStdout(xmlReceiver, state);
} else if (outputName.equals(OUTPUT_STDERR)) {
outputStderr(xmlReceiver, state);
} else if (outputName.equals(OUTPUT_RESULT)) {
outputResult(xmlReceiver, state);
}
}
}
};
addOutput(outputName, output);
return output;
}
/**
* Case where no output is generated.
*/
@Override
public void start(PipelineContext pipelineContext) {
doIt(pipelineContext, null, null);
}
@Override
public void reset(PipelineContext pipelineContext) {
setState(pipelineContext, new State());
}
private static class State {
public boolean isRead;
public String outputString;
public String errorString;
public String result;
}
}