blob: f778e3ab0cb1a54bd513867355c0d2fa40888755 [file] [log] [blame]
/*
* Copyright (C) 2010-2011 The University of Manchester
*
* See the file "LICENSE" for license terms.
*/
package org.taverna.server.master;
import static eu.medsea.util.MimeUtil.getMimeType;
import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
import static javax.ws.rs.core.UriBuilder.fromUri;
import static javax.xml.xpath.XPathConstants.NODE;
import static javax.xml.xpath.XPathConstants.NODESET;
import static org.taverna.server.master.TavernaServerSupport.log;
import static org.taverna.server.master.common.Uri.secure;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.util.xml.SimpleNamespaceContext;
import org.taverna.server.master.exceptions.FilesystemAccessException;
import org.taverna.server.master.exceptions.NoDirectoryEntryException;
import org.taverna.server.master.interfaces.Directory;
import org.taverna.server.master.interfaces.DirectoryEntry;
import org.taverna.server.master.interfaces.File;
import org.taverna.server.master.interfaces.TavernaRun;
import org.taverna.server.master.interfaces.UriBuilderFactory;
import org.taverna.server.master.utils.FilenameUtils;
import org.taverna.server.port_description.AbsentValue;
import org.taverna.server.port_description.AbstractPortDescription;
import org.taverna.server.port_description.AbstractValue;
import org.taverna.server.port_description.ErrorValue;
import org.taverna.server.port_description.InputDescription;
import org.taverna.server.port_description.InputDescription.InputPort;
import org.taverna.server.port_description.LeafValue;
import org.taverna.server.port_description.ListValue;
import org.taverna.server.port_description.OutputDescription;
import org.taverna.server.port_description.OutputDescription.OutputPort;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* A class that is used to build descriptions of the contents of a workflow
* run's filesystem.
*
* @author Donal Fellows
*/
public class ContentsDescriptorBuilder {
/** Namespace for use when pulling apart a .t2flow document. */
private static final String T2FLOW_NS = "http://taverna.sf.net/2008/xml/t2flow";
/** Prefix for t2flow namespace. */
private static final String T2FLOW_PFX = "t2";
private FilenameUtils fileUtils;
private UriBuilderFactory uriBuilderFactory;
private XPathExpression inputPorts;
private XPathExpression outputPorts;
private XPathExpression portName;
private XPathExpression portDepth;
private XPathExpression dataflow;
public ContentsDescriptorBuilder() throws XPathExpressionException {
XPath xp = XPathFactory.newInstance().newXPath();
SimpleNamespaceContext ctxt = new SimpleNamespaceContext();
ctxt.bindNamespaceUri(T2FLOW_PFX, T2FLOW_NS);
xp.setNamespaceContext(ctxt);
dataflow = xp.compile("//t2:dataflow[1]");
inputPorts = xp.compile("./t2:inputPorts/t2:port");
outputPorts = xp.compile("./t2:outputPorts/t2:port");
portName = xp.compile("./t2:name/text()");
portDepth = xp.compile("./t2:depth/text()");
}
private Element dataflow(Element root) throws XPathExpressionException {
return (Element) dataflow.evaluate(root, NODE);
}
private List<Element> inputPorts(Element dataflow)
throws XPathExpressionException {
List<Element> result = new ArrayList<>();
if (dataflow == null)
return result;
NodeList nl = (NodeList) inputPorts.evaluate(dataflow, NODESET);
// Wrap as a list so we can iterate over it <sigh>
for (int i = 0; i < nl.getLength(); i++)
result.add((Element) nl.item(i));
return result;
}
private List<Element> outputPorts(Element dataflow)
throws XPathExpressionException {
List<Element> result = new ArrayList<>();
if (dataflow == null)
return result;
NodeList nl = (NodeList) outputPorts.evaluate(dataflow, NODESET);
// Wrap as a list so we can iterate over it <sigh>
for (int i = 0; i < nl.getLength(); i++)
result.add((Element) nl.item(i));
return result;
}
private String portName(Element port) throws XPathExpressionException {
return portName.evaluate(port);
}
private String portDepth(Element port) throws XPathExpressionException {
return portDepth.evaluate(port);
}
@Required
public void setUriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
this.uriBuilderFactory = uriBuilderFactory;
}
@Required
public void setFileUtils(FilenameUtils fileUtils) {
this.fileUtils = fileUtils;
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
private Element fillInFromWorkflow(TavernaRun run, UriBuilder ub,
AbstractPortDescription portDesc) throws XPathExpressionException {
Element elem = run.getWorkflow().getWorkflowRoot();
portDesc.fillInBaseData(elem.getAttribute("id"), run.getId(),
ub.build());
return dataflow(elem);
}
/**
* Build the contents description.
*
* @param run
* The workflow run this is talking about.
* @param dataflow
* The dataflow element of the T2flow document.
* @param ub
* How to build URIs.
* @param descriptor
* The descriptor to modify.
* @param expected
* The list of outputs that are <i>expected</i> to be produced;
* they might not actually produce anything though.
* @throws NoDirectoryEntryException
* @throws FilesystemAccessException
* @throws XPathExpressionException
*/
private void constructPorts(TavernaRun run, Element dataflow,
UriBuilder ub, OutputDescription descriptor)
throws FilesystemAccessException, NoDirectoryEntryException,
XPathExpressionException {
Collection<DirectoryEntry> outs = null;
try {
outs = fileUtils.getDirectory(run, "out").getContents();
} catch (FilesystemAccessException | NoDirectoryEntryException e) {
log.warn("unexpected failure in construction of output descriptor",
e);
}
for (Element output : outputPorts(dataflow)) {
OutputPort p = descriptor.addPort(portName(output));
if (outs != null) {
p.output = constructValue(outs, ub, p.name);
p.depth = computeDepth(p.output);
}
}
}
/**
* Computes the depth of value in a descriptor.
*
* @param value
* The value description to characterise.
* @return Its depth (i.e., the depth of the port outputting the value) or
* <tt>null</tt> if that is impossible to determine.
*/
private Integer computeDepth(AbstractValue value) {
if (value instanceof ListValue) {
int mv = 1;
for (AbstractValue v : ((ListValue) value).contents) {
Integer d = computeDepth(v);
if (d != null && mv <= d)
mv = d + 1;
}
return mv;
} else if (value instanceof LeafValue || value instanceof ErrorValue)
return 0;
else
return null;
}
/**
* Build a description of a leaf value.
*
* @param file
* The file representing the value.
* @return A value descriptor.
* @throws FilesystemAccessException
* If anything goes wrong.
*/
private LeafValue constructLeafValue(File file)
throws FilesystemAccessException {
LeafValue v = new LeafValue();
v.fileName = file.getFullName();
v.byteLength = file.getSize();
try {
byte[] head = file.getContents(0, 1024);
v.contentType = getMimeType(new ByteArrayInputStream(head));
} catch (Exception e) {
v.contentType = APPLICATION_OCTET_STREAM_TYPE.toString();
}
return v;
}
/**
* Build a description of an error value.
*
* @param file
* The file representing the error.
* @return A value descriptor.
* @throws FilesystemAccessException
* If anything goes wrong.
*/
private ErrorValue constructErrorValue(File file)
throws FilesystemAccessException {
ErrorValue v = new ErrorValue();
v.fileName = file.getFullName();
v.byteLength = file.getSize();
return v;
}
/**
* Build a description of a list value.
*
* @param dir
* The directory representing the list.
* @param ub
* The factory for URIs.
* @return A value descriptor.
* @throws FilesystemAccessException
* If anything goes wrong.
*/
private ListValue constructListValue(Directory dir, UriBuilder ub)
throws FilesystemAccessException {
ListValue v = new ListValue();
v.length = 0;
Set<DirectoryEntry> contents = new HashSet<>(dir.getContents());
Iterator<DirectoryEntry> it = contents.iterator();
while (it.hasNext())
if (!it.next().getName().matches("^[0-9]+([.].*)?$"))
it.remove();
for (int i = 1; !contents.isEmpty(); i++) {
String exact = Integer.toString(i);
AbstractValue subval = constructValue(contents, ub, exact);
v.contents.add(subval);
if (!(subval instanceof AbsentValue)) {
v.length = i;
String pfx = i + ".";
for (DirectoryEntry de : contents)
if (de.getName().equals(exact)
|| de.getName().startsWith(pfx)) {
contents.remove(de);
break;
}
}
}
return v;
}
/**
* Build a value description.
*
* @param parentContents
* The contents of the parent directory.
* @param ub
* The factory for URIs.
* @param name
* The name of the value's file/directory representative.
* @return A value descriptor.
* @throws FilesystemAccessException
* If anything goes wrong.
*/
private AbstractValue constructValue(
Collection<DirectoryEntry> parentContents, UriBuilder ub,
String name) throws FilesystemAccessException {
String error = name + ".error";
String prefix = name + ".";
for (DirectoryEntry entry : parentContents) {
AbstractValue av;
if (entry.getName().equals(error) && entry instanceof File) {
av = constructErrorValue((File) entry);
} else if (!entry.getName().equals(name)
&& !entry.getName().startsWith(prefix))
continue;
else if (entry instanceof File)
av = constructLeafValue((File) entry);
else
av = constructListValue((Directory) entry, ub);
String fullPath = entry.getFullName().replaceFirst("^/", "");
av.href = ub.clone().path(fullPath).build();
return av;
}
return new AbsentValue();
}
/**
* Construct a description of the outputs of a workflow run.
*
* @param run
* The workflow run whose outputs are to be described.
* @param ui
* The origin for URIs.
* @return The description, which can be serialized to XML.
* @throws FilesystemAccessException
* If something goes wrong reading the directories.
* @throws NoDirectoryEntryException
* If something goes wrong reading the directories.
*/
public OutputDescription makeOutputDescriptor(TavernaRun run, UriInfo ui)
throws FilesystemAccessException, NoDirectoryEntryException {
OutputDescription descriptor = new OutputDescription();
try {
UriBuilder ub = getRunUriBuilder(run, ui);
Element dataflow = fillInFromWorkflow(run, ub, descriptor);
if (dataflow == null || run.getOutputBaclavaFile() != null)
return descriptor;
constructPorts(run, dataflow, ub.path("wd"), descriptor);
} catch (XPathExpressionException e) {
log.info("failure in XPath evaluation", e);
}
return descriptor;
}
private UriBuilder getRunUriBuilder(TavernaRun run, UriInfo ui) {
if (ui == null)
return secure(uriBuilderFactory.getRunUriBuilder(run));
else
return secure(fromUri(ui.getAbsolutePath().toString()
.replaceAll("/(out|in)put/?$", "")));
}
/**
* Constructs input descriptions.
*
* @param run
* The run to build for.
* @param ui
* The mechanism for building URIs.
* @return The description of the <i>expected</i> inputs of the run.
*/
public InputDescription makeInputDescriptor(TavernaRun run, UriInfo ui) {
InputDescription desc = new InputDescription();
try {
UriBuilder ub = getRunUriBuilder(run, ui);
Element dataflow = fillInFromWorkflow(run, ub, desc);
ub = ub.path("input/{name}");
for (Element port : inputPorts(dataflow)) {
InputPort in = desc.addPort(portName(port));
in.href = ub.build(in.name);
try {
in.depth = Integer.valueOf(portDepth(port));
} catch (NumberFormatException ex) {
in.depth = null;
}
}
} catch (XPathExpressionException e) {
log.info("failure in XPath evaluation", e);
}
return desc;
}
}