Skip to content

Commit fa3ccef

Browse files
authored
Merge pull request #151 from common-workflow-language/packed-workflows
Packed Workflow Support
2 parents 5532be0 + d65c213 commit fa3ccef

30 files changed

+644
-270
lines changed

src/main/java/org/commonwl/view/cwl/CWLProcess.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,18 @@
2323
* Enum for possible CWL processes
2424
*/
2525
public enum CWLProcess {
26-
WORKFLOW,
27-
COMMANDLINETOOL,
28-
EXPRESSIONTOOL;
26+
WORKFLOW("Workflow"),
27+
COMMANDLINETOOL("CommandLineTool"),
28+
EXPRESSIONTOOL("ExpressionTool");
29+
30+
private final String name;
31+
32+
CWLProcess(String name) {
33+
this.name = name;
34+
}
2935

3036
@Override
3137
public String toString() {
32-
switch (this) {
33-
case WORKFLOW:
34-
return "Workflow";
35-
case COMMANDLINETOOL:
36-
return "CommandLineTool";
37-
case EXPRESSIONTOOL:
38-
return "ExpressionTool";
39-
default:
40-
return super.toString();
41-
}
38+
return name;
4239
}
4340
}

src/main/java/org/commonwl/view/cwl/CWLService.java

Lines changed: 90 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,11 @@
1919

2020
package org.commonwl.view.cwl;
2121

22-
import static org.apache.commons.io.FileUtils.readFileToString;
23-
24-
import java.io.ByteArrayInputStream;
25-
import java.io.File;
26-
import java.io.IOException;
27-
import java.io.StringWriter;
28-
import java.util.ArrayList;
29-
import java.util.HashMap;
30-
import java.util.Iterator;
31-
import java.util.List;
32-
import java.util.Map;
33-
22+
import com.fasterxml.jackson.databind.JsonNode;
23+
import com.fasterxml.jackson.databind.ObjectMapper;
24+
import com.fasterxml.jackson.databind.node.ArrayNode;
25+
import com.fasterxml.jackson.databind.node.ObjectNode;
26+
import com.fasterxml.jackson.databind.node.TextNode;
3427
import org.apache.commons.io.FileUtils;
3528
import org.apache.commons.io.FilenameUtils;
3629
import org.apache.jena.iri.IRI;
@@ -46,6 +39,7 @@
4639
import org.commonwl.view.graphviz.ModelDotWriter;
4740
import org.commonwl.view.graphviz.RDFDotWriter;
4841
import org.commonwl.view.workflow.Workflow;
42+
import org.commonwl.view.workflow.WorkflowNotFoundException;
4943
import org.commonwl.view.workflow.WorkflowOverview;
5044
import org.slf4j.Logger;
5145
import org.slf4j.LoggerFactory;
@@ -54,11 +48,13 @@
5448
import org.springframework.stereotype.Service;
5549
import org.yaml.snakeyaml.Yaml;
5650

57-
import com.fasterxml.jackson.databind.JsonNode;
58-
import com.fasterxml.jackson.databind.ObjectMapper;
59-
import com.fasterxml.jackson.databind.node.ArrayNode;
60-
import com.fasterxml.jackson.databind.node.ObjectNode;
61-
import com.fasterxml.jackson.databind.node.TextNode;
51+
import java.io.ByteArrayInputStream;
52+
import java.io.File;
53+
import java.io.IOException;
54+
import java.io.StringWriter;
55+
import java.util.*;
56+
57+
import static org.apache.commons.io.FileUtils.readFileToString;
6258

6359
/**
6460
* Provides CWL parsing for workflows to gather an overview
@@ -114,12 +110,58 @@ public CWLService(RDFService rdfService,
114110
this.singleFileSizeLimit = singleFileSizeLimit;
115111
}
116112

113+
/**
114+
* Gets whether a file is packed using schema salad
115+
* @param workflowFile The file to be parsed
116+
* @return Whether the file is packed
117+
*/
118+
public boolean isPacked(File workflowFile) throws IOException {
119+
if (workflowFile.length() > singleFileSizeLimit) {
120+
return false;
121+
}
122+
String fileContent = readFileToString(workflowFile);
123+
return fileContent.contains("$graph");
124+
}
125+
126+
/**
127+
* Gets a list of workflows from a packed CWL file
128+
* @param packedFile The packed CWL file
129+
* @return The list of workflow overviews
130+
*/
131+
public List<WorkflowOverview> getWorkflowOverviewsFromPacked(File packedFile) throws IOException {
132+
if (packedFile.length() <= singleFileSizeLimit) {
133+
List<WorkflowOverview> overviews = new ArrayList<>();
134+
135+
JsonNode packedJson = yamlStringToJson(readFileToString(packedFile));
136+
137+
if (packedJson.has(DOC_GRAPH)) {
138+
for (JsonNode jsonNode : packedJson.get(DOC_GRAPH)) {
139+
if (extractProcess(jsonNode) == CWLProcess.WORKFLOW) {
140+
WorkflowOverview overview = new WorkflowOverview(jsonNode.get(ID).asText(),
141+
extractLabel(jsonNode), extractDoc(jsonNode));
142+
overviews.add(overview);
143+
}
144+
}
145+
} else {
146+
throw new IOException("The file given was not recognised as a packed CWL file");
147+
}
148+
149+
return overviews;
150+
151+
} else {
152+
throw new IOException("File '" + packedFile.getName() + "' is over singleFileSizeLimit - " +
153+
FileUtils.byteCountToDisplaySize(packedFile.length()) + "/" +
154+
FileUtils.byteCountToDisplaySize(singleFileSizeLimit));
155+
}
156+
}
157+
117158
/**
118159
* Gets the Workflow object from internal parsing
119160
* @param workflowFile The workflow file to be parsed
161+
* @param packedWorkflowId The ID of the workflow object if the file is packed
120162
* @return The constructed workflow object
121163
*/
122-
public Workflow parseWorkflowNative(File workflowFile) throws IOException {
164+
public Workflow parseWorkflowNative(File workflowFile, String packedWorkflowId) throws IOException {
123165

124166
// Check file size limit before parsing
125167
long fileSizeBytes = workflowFile.length();
@@ -128,16 +170,30 @@ public Workflow parseWorkflowNative(File workflowFile) throws IOException {
128170
// Parse file as yaml
129171
JsonNode cwlFile = yamlStringToJson(readFileToString(workflowFile));
130172

131-
// If the CWL file is packed there can be multiple workflows in a file
132-
Map<String, JsonNode> packedFiles = new HashMap<>();
133-
if (cwlFile.has(DOC_GRAPH)) {
134-
// Packed CWL, find the first subelement which is a workflow and take it
135-
for (JsonNode jsonNode : cwlFile.get(DOC_GRAPH)) {
136-
packedFiles.put(jsonNode.get(ID).asText(), jsonNode);
137-
if (extractProcess(jsonNode) == CWLProcess.WORKFLOW) {
138-
cwlFile = jsonNode;
173+
// Check packed workflow occurs
174+
if (packedWorkflowId != null) {
175+
boolean found = false;
176+
if (cwlFile.has(DOC_GRAPH)) {
177+
for (JsonNode jsonNode : cwlFile.get(DOC_GRAPH)) {
178+
if (extractProcess(jsonNode) == CWLProcess.WORKFLOW) {
179+
String currentId = jsonNode.get(ID).asText();
180+
if (currentId.startsWith("#")) {
181+
currentId = currentId.substring(1);
182+
}
183+
if (currentId.equals(packedWorkflowId)) {
184+
cwlFile = jsonNode;
185+
found = true;
186+
break;
187+
}
188+
}
139189
}
140190
}
191+
if (!found) throw new WorkflowNotFoundException();
192+
} else {
193+
// Check the current json node is a workflow
194+
if (extractProcess(cwlFile) != CWLProcess.WORKFLOW) {
195+
throw new WorkflowNotFoundException();
196+
}
141197
}
142198

143199
// Use filename for label if there is no defined one
@@ -150,10 +206,6 @@ public Workflow parseWorkflowNative(File workflowFile) throws IOException {
150206
Workflow workflowModel = new Workflow(label, extractDoc(cwlFile), getInputs(cwlFile),
151207
getOutputs(cwlFile), getSteps(cwlFile), null);
152208

153-
if (packedFiles.size() > 0) {
154-
workflowModel.setPackedWorkflowID(cwlFile.get(ID).asText());
155-
}
156-
157209
workflowModel.setCwltoolVersion(cwlTool.getVersion());
158210

159211
// Generate DOT graph
@@ -186,7 +238,7 @@ public Workflow parseWorkflowWithCwltool(Workflow basicModel,
186238
File workflowFile) throws CWLValidationException {
187239
GitDetails gitDetails = basicModel.getRetrievedFrom();
188240
String latestCommit = basicModel.getLastCommit();
189-
String packedWorkflowID = basicModel.getPackedWorkflowID();
241+
String packedWorkflowID = gitDetails.getPackedId();
190242

191243
// Get paths to workflow
192244
String url = gitDetails.getUrl(latestCommit).replace("https://", "");
@@ -405,13 +457,19 @@ public WorkflowOverview getWorkflowOverview(File file) throws IOException {
405457
JsonNode cwlFile = yamlStringToJson(readFileToString(file));
406458

407459
// If the CWL file is packed there can be multiple workflows in a file
460+
int packedCount = 0;
408461
if (cwlFile.has(DOC_GRAPH)) {
409462
// Packed CWL, find the first subelement which is a workflow and take it
410463
for (JsonNode jsonNode : cwlFile.get(DOC_GRAPH)) {
411464
if (extractProcess(jsonNode) == CWLProcess.WORKFLOW) {
412465
cwlFile = jsonNode;
466+
packedCount++;
413467
}
414468
}
469+
if (packedCount > 1) {
470+
return new WorkflowOverview("/" + file.getName(), "Packed file",
471+
"contains " + packedCount + " workflows");
472+
}
415473
}
416474

417475
// Can only make an overview if this is a workflow
@@ -423,7 +481,7 @@ public WorkflowOverview getWorkflowOverview(File file) throws IOException {
423481
}
424482

425483
// Return the constructed overview
426-
return new WorkflowOverview(file.getName(), label, extractDoc(cwlFile));
484+
return new WorkflowOverview("/" + file.getName(), label, extractDoc(cwlFile));
427485
} else {
428486
// Return null if not a workflow file
429487
return null;

src/main/java/org/commonwl/view/cwl/CWLToolRunner.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.commonwl.view.cwl;
2121

22+
import org.apache.jena.query.QueryException;
2223
import org.commonwl.view.researchobject.ROBundleFactory;
2324
import org.commonwl.view.workflow.QueuedWorkflow;
2425
import org.commonwl.view.workflow.QueuedWorkflowRepository;
@@ -79,7 +80,6 @@ public void createWorkflowFromQueued(QueuedWorkflow queuedWorkflow, File workflo
7980
newWorkflow.setRetrievedFrom(tempWorkflow.getRetrievedFrom());
8081
newWorkflow.setRetrievedOn(new Date());
8182
newWorkflow.setLastCommit(tempWorkflow.getLastCommit());
82-
newWorkflow.setPackedWorkflowID(tempWorkflow.getPackedWorkflowID());
8383
newWorkflow.setCwltoolVersion(cwlToolVersion);
8484

8585
workflowRepository.save(newWorkflow);
@@ -89,19 +89,22 @@ public void createWorkflowFromQueued(QueuedWorkflow queuedWorkflow, File workflo
8989

9090
// Mark success on queue
9191
queuedWorkflow.setCwltoolStatus(CWLToolStatus.SUCCESS);
92-
queuedWorkflowRepository.save(queuedWorkflow);
9392

93+
} catch (QueryException ex) {
94+
logger.error("Jena query exception ", ex);
95+
queuedWorkflow.setCwltoolStatus(CWLToolStatus.ERROR);
96+
queuedWorkflow.setMessage("An error occurred when executing a query on the SPARQL store");
9497
} catch (CWLValidationException ex) {
9598
logger.error(ex.getMessage(), ex);
9699
queuedWorkflow.setCwltoolStatus(CWLToolStatus.ERROR);
97100
queuedWorkflow.setMessage(ex.getMessage());
98-
queuedWorkflowRepository.save(queuedWorkflow);
99101
} catch (Exception ex) {
100102
logger.error("Unexpected error", ex);
101103
queuedWorkflow.setCwltoolStatus(CWLToolStatus.ERROR);
102104
queuedWorkflow.setMessage("Whoops! Cwltool ran successfully, but an unexpected " +
103105
"error occurred in CWLViewer!\n" +
104106
"Help us by reporting it on Gitter or a Github issue\n");
107+
} finally {
105108
queuedWorkflowRepository.save(queuedWorkflow);
106109
}
107110

src/main/java/org/commonwl/view/git/GitDetails.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class GitDetails implements Serializable {
3434
private String repoUrl;
3535
private String branch;
3636
private String path;
37+
private String packedId;
3738

3839
public GitDetails(String repoUrl, String branch, String path) {
3940
this.repoUrl = repoUrl;
@@ -67,6 +68,14 @@ public void setBranch(String branch) {
6768
this.branch = branch;
6869
}
6970

71+
public String getPackedId() {
72+
return packedId;
73+
}
74+
75+
public void setPackedId(String packedId) {
76+
this.packedId = packedId;
77+
}
78+
7079
public String getPath() {
7180
return path;
7281
}
@@ -139,13 +148,14 @@ public String getUrl() {
139148
* @return The URL
140149
*/
141150
public String getInternalUrl() {
151+
String packedPart = packedId == null ? "" : "%23" + packedId;
142152
String pathPart = path.equals("/") ? "" : "/" + path;
143153
switch (getType()) {
144154
case GITHUB:
145155
case GITLAB:
146-
return "/workflows/" + normaliseUrl(repoUrl).replace(".git", "") + "/blob/" + branch + pathPart;
156+
return "/workflows/" + normaliseUrl(repoUrl).replace(".git", "") + "/blob/" + branch + pathPart + packedPart;
147157
default:
148-
return "/workflows/" + normaliseUrl(repoUrl) + "/" + branch + pathPart;
158+
return "/workflows/" + normaliseUrl(repoUrl) + "/" + branch + pathPart + packedPart;
149159
}
150160
}
151161

0 commit comments

Comments
 (0)