org.exist.xquery.modules.process.Execute Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of exist-process Show documentation
Show all versions of exist-process Show documentation
eXist-db XQuery Process Module
The newest version!
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* [email protected]
* http://www.exist-db.org
*
* This library 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 library 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.xquery.modules.process;
import org.exist.dom.QName;
import org.exist.dom.memtree.ElementImpl;
import org.exist.dom.memtree.MemTreeBuilder;
import org.exist.numbering.NodeId;
import org.exist.stax.ExtendedXMLStreamReader;
import org.exist.util.FileUtils;
import org.exist.xquery.*;
import org.exist.xquery.value.*;
import org.xml.sax.helpers.AttributesImpl;
import javax.xml.XMLConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class Execute extends BasicFunction {
public final static FunctionSignature signature =
new FunctionSignature(
new QName("execute", ProcessModule.NAMESPACE, ProcessModule.PREFIX),
"",
new SequenceType[] {
new FunctionParameterSequenceType("args", Type.STRING, Cardinality.ONE_OR_MORE,
"a list of strings which signifies the external program file to be invoked and its arguments, if any"),
new FunctionParameterSequenceType("options", Type.ELEMENT, Cardinality.ZERO_OR_ONE,
"an XML fragment defining optional parameters like working directory or the lines to send to " +
"the process via stdin. Format: workingDir " +
"line ")
},
new FunctionReturnSequenceType(Type.ELEMENT, Cardinality.EXACTLY_ONE, "the sequence of code points"));
public final static QName RESULT_QNAME = new QName("execution", XMLConstants.NULL_NS_URI);
public final static QName COMMAND_LINE_QNAME = new QName("commandline", XMLConstants.NULL_NS_URI);
public final static QName STDOUT_QNAME = new QName("stdout", XMLConstants.NULL_NS_URI);
public final static QName LINE_QNAME = new QName("line", XMLConstants.NULL_NS_URI);
public Execute(final XQueryContext context) {
super(context, signature);
}
@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
if (!context.getSubject().hasDbaRole())
throw new XPathException(this, "process:execute is only available to users with dba role");
// create list of parameters to pass to shell
List cmdArgs = new ArrayList<>(args[0].getItemCount());
for (SequenceIterator i = args[0].iterate(); i.hasNext(); ) {
cmdArgs.add(i.nextItem().getStringValue());
}
// parse options
List stdin = null;
Path workingDir = null;
Map environment = null;
if (!args[1].isEmpty()) {
try {
final NodeValue options = (NodeValue) args[1].itemAt(0);
final int thisLevel = options.getNodeId().getTreeLevel();
final XMLStreamReader reader = context.getXMLStreamReader(options);
reader.next();
while (reader.hasNext()) {
int status = reader.next();
if (status == XMLStreamReader.START_ELEMENT) {
String name = reader.getLocalName();
if ("workingDir".equals(name)) {
workingDir = getWorkingDir(reader.getElementText());
} else if ("line".equals(name)) {
if (stdin == null)
stdin = new ArrayList<>(21);
stdin.add(reader.getElementText() + "\n");
} else if ("env".equals(name)) {
if (environment == null)
environment = new HashMap<>();
String key = reader.getAttributeValue(null, "name");
String value = reader.getAttributeValue(null, "value");
if (key != null && value != null)
environment.put(key, value);
}
} else if (status == XMLStreamReader.END_ELEMENT) {
final NodeId otherId = (NodeId) reader.getProperty(ExtendedXMLStreamReader.PROPERTY_NODE_ID);
final int otherLevel = otherId.getTreeLevel();
if (otherLevel == thisLevel) {
// finished `optRoot` element...
break; // exit-while
}
}
}
} catch (XMLStreamException | IOException e) {
throw new XPathException(this, "Invalid XML fragment for options: " + e.getMessage(), e);
}
}
if (LOG.isDebugEnabled())
LOG.debug("Creating process {}", cmdArgs.get(0));
ProcessBuilder pb = new ProcessBuilder(cmdArgs);
pb.redirectErrorStream(true);
if (workingDir != null)
pb.directory(workingDir.toFile());
if (environment != null) {
Map env = pb.environment();
env.putAll(environment);
}
try {
Process process = pb.start();
if (stdin != null) {
try(final Writer writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8))) {
for (final String line : stdin) {
writer.write(line);
}
}
}
List output = readOutput(process);
int exitValue = process.waitFor();
return createReport(exitValue, output, cmdArgs);
} catch (IOException e) {
throw new XPathException(this, "An IO error occurred while executing the process " + cmdArgs.get(0) + ": " + e.getMessage(), e);
} catch (InterruptedException e) {
throw new XPathException(this, "process:execute was interrupted while waiting for process " + cmdArgs.get(0));
}
}
private Path getWorkingDir(String arg) {
final Path file = Paths.get(arg);
if (file.isAbsolute()) {
return file;
}
final Optional home = context.getBroker().getConfiguration().getExistHome();
return FileUtils.resolve(home, arg);
}
private ElementImpl createReport(int exitValue, List output, List cmdArgs) {
context.pushDocumentContext();
try {
MemTreeBuilder builder = context.getDocumentBuilder();
AttributesImpl attribs = new AttributesImpl();
attribs.addAttribute("", "exitCode", "exitCode", "CDATA", Integer.toString(exitValue));
builder.startDocument();
int nodeNr = builder.startElement(RESULT_QNAME, attribs);
// print command line
StringBuilder cmdLine = new StringBuilder();
for (String param : cmdArgs) {
cmdLine.append(param).append(' ');
}
builder.startElement(COMMAND_LINE_QNAME, null);
builder.characters(cmdLine.toString());
builder.endElement();
// print received output to
builder.startElement(STDOUT_QNAME, null);
for (String line : output) {
builder.startElement(LINE_QNAME, null);
builder.characters(line);
builder.endElement();
}
builder.endElement();
builder.endElement();
return (ElementImpl) builder.getDocument().getNode(nodeNr);
} finally {
context.popDocumentContext();
}
}
private List readOutput(Process process) throws XPathException {
try(final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
List output = new ArrayList<>(31);
String line;
while ((line = reader.readLine()) != null) {
output.add(line);
}
return output;
} catch (IOException e) {
throw new XPathException(this, "An IO error occurred while reading output from the process: " + e.getMessage(), e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy