blob: f5259bd2143105b3df49214365358d1ff8be3854 [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 org.apache.commons.logging.LogFactory.getLog;
import static org.taverna.server.master.common.Uri.secure;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.logging.Log;
import org.springframework.beans.factory.annotation.Required;
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 uk.org.taverna.scufl2.api.container.WorkflowBundle;
import uk.org.taverna.scufl2.api.core.Workflow;
import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
/**
* A class that is used to build descriptions of the contents of a workflow
* run's filesystem.
*
* @author Donal Fellows
*/
public class ContentsDescriptorBuilder {
private Log log = getLog("Taverna.Server.Webapp");
private FilenameUtils fileUtils;
private UriBuilderFactory uriBuilderFactory;
@Required
public void setUriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
this.uriBuilderFactory = uriBuilderFactory;
}
@Required
public void setFileUtils(FilenameUtils fileUtils) {
this.fileUtils = fileUtils;
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
private Workflow fillInFromWorkflow(TavernaRun run, UriBuilder ub,
AbstractPortDescription portDesc) throws IOException {
WorkflowBundle bundle = run.getWorkflow().getScufl2Workflow();
bundle.getMainWorkflow().getInputPorts();
portDesc.fillInBaseData(bundle.getMainWorkflow()
.getWorkflowIdentifier().toString(), run.getId(), ub.build());
return bundle.getMainWorkflow();
}
/**
* 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);
Workflow dataflow = fillInFromWorkflow(run, ub, descriptor);
Collection<DirectoryEntry> outs = null;
ub = ub.path("wd/{path}");
for (OutputWorkflowPort output : dataflow.getOutputPorts()) {
OutputPort p = descriptor.addPort(output.getName());
if (run.getOutputBaclavaFile() == null) {
if (outs == null)
outs = fileUtils.getDirectory(run, "out").getContents();
p.output = constructValue(outs, ub, p.name);
p.depth = computeDepth(p.output);
}
}
} catch (IOException e) {
log.info("failure in conversion to .scufl2", 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);
Workflow workflow = fillInFromWorkflow(run, ub, desc);
ub = ub.path("input/{name}");
for (InputWorkflowPort port : workflow.getInputPorts()) {
InputPort in = desc.addPort(port.getName());
in.href = ub.build(in.name);
try {
in.depth = port.getDepth();
} catch (NumberFormatException ex) {
in.depth = null;
}
}
} catch (IOException e) {
log.info("failure in conversion to .scufl2", e);
}
return desc;
}
}