Skip to content

Commit 7b8eb67

Browse files
stainfbacallkinow
authored
CWL to graph endpoint (#241)
Co-authored-by: Finn Bacall <[email protected]> Co-authored-by: Bruno P. Kinoshita <[email protected]>
1 parent 2502986 commit 7b8eb67

File tree

5 files changed

+262
-23
lines changed

5 files changed

+262
-23
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM maven:3.5-jdk-8-alpine
1+
FROM maven:3.6-jdk-8-alpine
22
MAINTAINER Stian Soiland-Reyes <[email protected]>
33

44
# Build-time metadata as defined at http://label-schema.org

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public List<WorkflowOverview> getWorkflowOverviewsFromPacked(File packedFile) th
169169
* Note, the length of the stream is not checked.
170170
*
171171
* @param workflowStream The workflow stream to be parsed
172-
* @param packedWorkflowId The ID of the workflow object if the file is packed
172+
* @param packedWorkflowId The ID of the workflow object if the file is packed. <code>null</code> means the workflow is not expected to be packed, while "" means the first workflow found is used, packed or non-packed.
173173
* @param defaultLabel Label to give workflow if not set
174174
* @return The constructed workflow object
175175
*/
@@ -178,29 +178,31 @@ public Workflow parseWorkflowNative(InputStream workflowStream, String packedWor
178178
JsonNode cwlFile = yamlStreamToJson(workflowStream);
179179

180180
// Check packed workflow occurs
181+
boolean found = false;
181182
if (packedWorkflowId != null) {
182-
boolean found = false;
183183
if (cwlFile.has(DOC_GRAPH)) {
184184
for (JsonNode jsonNode : cwlFile.get(DOC_GRAPH)) {
185185
if (extractProcess(jsonNode) == CWLProcess.WORKFLOW) {
186186
String currentId = jsonNode.get(ID).asText();
187187
if (currentId.startsWith("#")) {
188188
currentId = currentId.substring(1);
189189
}
190-
if (currentId.equals(packedWorkflowId)) {
190+
if (packedWorkflowId.isEmpty() || currentId.equals(packedWorkflowId)) {
191191
cwlFile = jsonNode;
192192
found = true;
193193
break;
194194
}
195195
}
196196
}
197197
}
198-
if (!found) throw new WorkflowNotFoundException();
199-
} else {
200-
// Check the current json node is a workflow
201-
if (extractProcess(cwlFile) != CWLProcess.WORKFLOW) {
202-
throw new WorkflowNotFoundException();
203-
}
198+
if (!found && ! packedWorkflowId.isEmpty()) throw new WorkflowNotFoundException();
199+
}
200+
if (! found && extractProcess(cwlFile) == CWLProcess.WORKFLOW) {
201+
// Check the current json node is a workflow
202+
found = true;
203+
}
204+
if (! found) {
205+
throw new WorkflowNotFoundException();
204206
}
205207

206208
// Use filename for label if there is no defined one

src/main/java/org/commonwl/view/workflow/WorkflowController.java

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,24 @@
2020
package org.commonwl.view.workflow;
2121

2222
import java.io.File;
23+
import java.io.FileOutputStream;
2324
import java.io.IOException;
2425
import java.nio.file.Path;
26+
import java.io.InputStream;
27+
import java.security.DigestInputStream;
28+
import java.security.MessageDigest;
29+
import java.security.NoSuchAlgorithmException;
2530
import java.util.List;
2631

2732
import javax.servlet.http.HttpServletRequest;
2833
import javax.servlet.http.HttpServletResponse;
2934
import javax.validation.Valid;
3035

36+
import org.apache.commons.codec.binary.Hex;
37+
import org.apache.commons.io.IOUtils;
3138
import org.apache.commons.lang.StringUtils;
3239
import org.commonwl.view.WebConfig;
40+
import org.commonwl.view.cwl.CWLService;
3341
import org.commonwl.view.cwl.CWLToolStatus;
3442
import org.commonwl.view.git.GitDetails;
3543
import org.commonwl.view.graphviz.GraphVizService;
@@ -40,6 +48,7 @@
4048
import org.springframework.beans.factory.annotation.Autowired;
4149
import org.springframework.beans.factory.annotation.Value;
4250
import org.springframework.core.io.FileSystemResource;
51+
import org.springframework.core.io.InputStreamResource;
4352
import org.springframework.core.io.PathResource;
4453
import org.springframework.core.io.Resource;
4554
import org.springframework.data.domain.Pageable;
@@ -48,11 +57,7 @@
4857
import org.springframework.ui.Model;
4958
import org.springframework.validation.BeanPropertyBindingResult;
5059
import org.springframework.validation.BindingResult;
51-
import org.springframework.web.bind.annotation.GetMapping;
52-
import org.springframework.web.bind.annotation.PathVariable;
53-
import org.springframework.web.bind.annotation.PostMapping;
54-
import org.springframework.web.bind.annotation.RequestParam;
55-
import org.springframework.web.bind.annotation.ResponseBody;
60+
import org.springframework.web.bind.annotation.*;
5661
import org.springframework.web.servlet.HandlerMapping;
5762
import org.springframework.web.servlet.ModelAndView;
5863
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@@ -64,6 +69,7 @@ public class WorkflowController {
6469

6570
private final WorkflowFormValidator workflowFormValidator;
6671
private final WorkflowService workflowService;
72+
private final CWLService cwlService;
6773
private final GraphVizService graphVizService;
6874

6975
/**
@@ -79,10 +85,11 @@ public class WorkflowController {
7985
@Autowired
8086
public WorkflowController(WorkflowFormValidator workflowFormValidator,
8187
WorkflowService workflowService,
82-
GraphVizService graphVizService) {
88+
GraphVizService graphVizService, CWLService cwlService) {
8389
this.workflowFormValidator = workflowFormValidator;
8490
this.workflowService = workflowService;
8591
this.graphVizService = graphVizService;
92+
this.cwlService = cwlService;
8693
}
8794

8895
/**
@@ -396,6 +403,32 @@ public PathResource getTempGraphAsPng(@PathVariable("queueID") String queueID,
396403
return new PathResource(out);
397404
}
398405

406+
/**
407+
* Take a CWL workflow from the POST body and generate a PNG graph for it.
408+
* @param in The workflow CWL
409+
*/
410+
@PostMapping(value="/graph/png", produces="image/png",
411+
consumes={"text/yaml", "text/x-yaml", "text/plain", "application/octet-stream"})
412+
@ResponseBody
413+
public Resource downloadGraphPngFromFile(InputStream in, HttpServletResponse response)
414+
throws IOException, NoSuchAlgorithmException {
415+
response.setHeader("Content-Disposition", "inline; filename=\"graph.png\"");
416+
return getGraphFromInputStream(in, "png");
417+
}
418+
419+
/**
420+
* Take a CWL workflow from the POST body and generate a SVG graph for it.
421+
* @param in The workflow CWL as YAML text
422+
*/
423+
@PostMapping(value="/graph/svg", produces="image/svg+xml",
424+
consumes={"text/yaml", "text/x-yaml", "text/plain", "application/octet-stream"})
425+
@ResponseBody
426+
public Resource downloadGraphSvgFromFile(InputStream in, HttpServletResponse response)
427+
throws IOException, NoSuchAlgorithmException {
428+
response.setHeader("Content-Disposition", "inline; filename=\"graph.svg\"");
429+
return getGraphFromInputStream(in, "svg");
430+
}
431+
399432

400433
/**
401434
* Extract the path from the end of a full request string
@@ -549,4 +582,11 @@ private ModelAndView getWorkflow(GitDetails gitDetails, RedirectAttributes redir
549582
WebConfig.Format.values());
550583
}
551584
}
585+
586+
private Resource getGraphFromInputStream(InputStream in, String format)
587+
throws IOException, NoSuchAlgorithmException {
588+
Workflow workflow = cwlService.parseWorkflowNative(in, null, "workflow"); // first workflow will do
589+
InputStream out = graphVizService.getGraphStream(workflow.getVisualisationDot(), format);
590+
return new InputStreamResource(out);
591+
}
552592
}

src/main/resources/templates/apidocs.html

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ <h1>JSON API Documentation</h1>
3939

4040
<p>If you make anything utilizing our API, please <a href="https://gitter.im/common-workflow-language/cwlviewer" alt="Gitter Chatroom" target="_blank" rel="noopener">let us know about it</a> - we would love to see!</p>
4141

42-
<p>All queries require the following header to receive a JSON response:
42+
<p>Except where noted otherwise, all queries require the following header to receive a JSON response:
4343
<pre class="highlight http">accept: application/json</pre>
4444
</p>
4545

@@ -652,6 +652,130 @@ <h4>cwlviewer.py</h4>
652652
else:
653653
print('Error adding workflow')
654654
</pre>
655+
656+
657+
<h3 id="graphSvg">SVG sketch of CWL workflow</h3>
658+
<div class="alert alert-info"><strong>Note:</strong> This method uses a brief YAML
659+
parsing of the standalone CWL file that does not support annotations
660+
like <kbd>label</kbd> or <kbd>doc</kbd>, and that does not check
661+
for validity or completeness.
662+
</div>
663+
<span class="method post">POST</span>
664+
<pre>/graph/svg</pre>
665+
<h4>Input</h4>
666+
<pre class="highlight http">
667+
Content-Type: text/yaml
668+
</pre>
669+
<pre class="highlight yaml">
670+
#!/usr/bin/env cwl-runner
671+
672+
cwlVersion: v1.0
673+
class: Workflow
674+
675+
inputs:
676+
usermessage: string
677+
678+
outputs:
679+
response:
680+
outputSource: step0/response
681+
type: File
682+
683+
steps:
684+
step0:
685+
run:
686+
class: CommandLineTool
687+
inputs:
688+
message:
689+
type: string
690+
inputBinding:
691+
position: 1
692+
baseCommand: echo
693+
outputs:
694+
response:
695+
type: stdout
696+
in:
697+
message: usermessage
698+
out: [response]
699+
</pre>
700+
<h4>Output</h4>
701+
<p><samp>Content-Type: image/svg+xml</samp></p>
702+
<pre class="highlight xml">
703+
&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;
704+
&lt;!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"&gt;
705+
&lt;svg width="450pt" height="270pt"
706+
viewBox="0.00 0.00 463.00 278.00" xmlns="http://www.w3.org/2000/svg"&gt;
707+
&lt;!-- ... --&gt;
708+
&lt;/svg&gt;
709+
</pre>
710+
<h4>Success</h4>
711+
<pre class="highlight http">
712+
HTTP/1.1 200 OK
713+
Content-Type: image/svg+xml
714+
</pre>
715+
<h4>Error</h4>
716+
<pre class="highlight http">HTTP/1.1 400 Bad Request</pre>
717+
718+
719+
<h3 id="graphPng">PNG sketch of CWL workflow</h3>
720+
<div class="alert alert-info"><strong>Note:</strong> This method uses a brief YAML
721+
parsing of the standalone CWL file that does not support annotations
722+
like <kbd>label</kbd> or <kbd>doc</kbd>, and that does not check
723+
for validity or completeness.
724+
</div>
725+
<span class="method post">POST</span>
726+
<pre>/graph/png</pre>
727+
<h4>Input</h4>
728+
729+
<pre class="highlight http">
730+
Content-Type: text/yaml
731+
</pre>
732+
<pre class="highlight yaml">
733+
#!/usr/bin/env cwl-runner
734+
735+
cwlVersion: v1.0
736+
class: Workflow
737+
738+
inputs:
739+
usermessage: string
740+
741+
outputs:
742+
response:
743+
outputSource: step0/response
744+
type: File
745+
746+
steps:
747+
step0:
748+
run:
749+
class: CommandLineTool
750+
inputs:
751+
message:
752+
type: string
753+
inputBinding:
754+
position: 1
755+
baseCommand: echo
756+
outputs:
757+
response:
758+
type: stdout
759+
in:
760+
message: usermessage
761+
out: [response]
762+
</pre>
763+
764+
<h4>Output</h4>
765+
766+
<pre class="highlight">
767+
\x89PNG\n\x1a\n\x00\x00 (binary)
768+
</pre>
769+
<h4>Success</h4>
770+
<pre class="highlight http">
771+
HTTP/1.1 200 OK
772+
Content-Type: image/png
773+
</pre>
774+
<h4>Error</h4>
775+
<pre class="highlight http">HTTP/1.1 400 Bad Request</pre>
776+
777+
778+
655779
</div>
656780
</div>
657781
</div>

0 commit comments

Comments
 (0)