Skip to content

Commit c31d7f9

Browse files
authored
Merge branch 'master' into ui-tweaks
2 parents f6cd541 + f89f37a commit c31d7f9

File tree

12 files changed

+437
-127
lines changed

12 files changed

+437
-127
lines changed

src/main/java/org/commonwl/view/GlobalControllerErrorHandling.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919

2020
package org.commonwl.view;
2121

22+
import java.util.Collections;
23+
24+
import org.commonwl.view.workflow.MultipleWorkflowsException;
2225
import org.commonwl.view.workflow.RepresentationNotFoundException;
2326
import org.commonwl.view.workflow.WorkflowNotFoundException;
2427
import org.springframework.http.HttpHeaders;
@@ -29,7 +32,12 @@
2932
import org.springframework.web.bind.annotation.ExceptionHandler;
3033

3134
/**
32-
* Handles exception handling across the application
35+
* Handles exception handling across the application.
36+
* <p>
37+
* Because of Spring Boot's content negotiation these handlers are needed when
38+
* the error is returned with an otherwise "non acceptable" content type (e.g.
39+
* Accept: image/svg+xml but we have to say 404 Not Found)
40+
*
3341
*/
3442
@ControllerAdvice
3543
public class GlobalControllerErrorHandling {
@@ -42,20 +50,34 @@ public class GlobalControllerErrorHandling {
4250
public ResponseEntity<?> handleNotFound() {
4351
final HttpHeaders headers = new HttpHeaders();
4452
headers.setContentType(MediaType.TEXT_PLAIN);
45-
return new ResponseEntity<>("Workflow could not be found", headers, HttpStatus.NOT_FOUND);
53+
return new ResponseEntity<>("Workflow or git commit could not be found", headers, HttpStatus.NOT_FOUND);
54+
}
55+
56+
/**
57+
* More than one workflow (or workflow parts) found
58+
*
59+
* @return A text/uri-list of potential representations
60+
*/
61+
@ExceptionHandler(MultipleWorkflowsException.class)
62+
public ResponseEntity<?> handleMultipleWorkflows(MultipleWorkflowsException ex) {
63+
final HttpHeaders headers = new HttpHeaders();
64+
headers.setContentType(MediaType.parseMediaType("text/uri-list"));
65+
return new ResponseEntity<>(ex.toString(), headers, HttpStatus.MULTIPLE_CHOICES);
4666
}
4767

4868
/**
49-
* Workflow exists but representation is not found
50-
* eg Generic git workflow asking for raw workflow URL
69+
* Workflow exists but representation is not found eg Generic git workflow
70+
* asking for raw workflow URL
71+
*
5172
* @return A plain text error message
5273
*/
5374
@ExceptionHandler(RepresentationNotFoundException.class)
5475
public ResponseEntity<?> handleNoRepresentation() {
5576
final HttpHeaders headers = new HttpHeaders();
77+
headers.setVary(Collections.singletonList("Accept"));
5678
headers.setContentType(MediaType.TEXT_PLAIN);
5779
return new ResponseEntity<>("The workflow exists but the requested representation could not be found",
58-
headers, HttpStatus.NOT_FOUND);
80+
headers, HttpStatus.NOT_ACCEPTABLE);
5981
}
6082

6183
}

src/main/java/org/commonwl/view/WebConfig.java

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import static org.springframework.http.MediaType.parseMediaType;
2323

24+
import org.commonwl.view.workflow.Workflow;
25+
import org.commonwl.view.workflow.WorkflowPermalinkController;
2426
import org.springframework.context.annotation.Configuration;
2527
import org.springframework.http.MediaType;
2628
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
@@ -31,39 +33,52 @@
3133
public class WebConfig extends WebMvcConfigurerAdapter {
3234

3335
/**
34-
* Ordered list of formats as presented on Workflow page - must match the
35-
* .mediaType() strings below.
36+
* Ordered list of formats as presented on Workflow page and supported for
37+
* content negotiation.
38+
*
39+
* @see Workflow#getPermalink(Format)
40+
* @see WorkflowPermalinkController
3641
*
3742
*/
38-
public static enum formats {
39-
html, json, turtle, jsonld, rdfxml, svg, png, dot, zip, ro, yaml, raw
43+
public static enum Format {
44+
// Browser
45+
html(MediaType.TEXT_HTML),
46+
// API
47+
json(MediaType.APPLICATION_JSON),
48+
// RDF
49+
turtle("text/turtle"), jsonld("application/ld+json"), rdfxml("application/rdf+xml"),
50+
// Images
51+
svg("image/svg+xml"), png(MediaType.IMAGE_PNG), dot("text/vnd+graphviz"),
52+
// Archives
53+
zip("application/zip"), ro("application/vnd.wf4ever.robundle+zip"),
54+
// raw redirects
55+
yaml("text/x-yaml"), raw(MediaType.APPLICATION_OCTET_STREAM);
56+
57+
private final MediaType mediaType;
58+
59+
Format(MediaType mediaType) {
60+
this.mediaType = mediaType;
61+
}
62+
63+
Format(String mediaType) {
64+
this.mediaType = parseMediaType(mediaType);
65+
}
66+
67+
public MediaType mediaType() {
68+
return mediaType;
69+
}
4070
}
4171

4272
/**
43-
* Allows the use of the format query parameter to be used
44-
* instead of the Accept HTTP header
73+
* Allows the use of the format query parameter to be used instead of the Accept
74+
* HTTP header
4575
*/
4676
@Override
4777
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
48-
configurer.favorParameter(true)
49-
// Browser
50-
.mediaType("html", MediaType.TEXT_HTML)
51-
// API
52-
.mediaType("json", MediaType.APPLICATION_JSON)
53-
// RDF
54-
.mediaType("turtle", parseMediaType("text/turtle"))
55-
.mediaType("jsonld", parseMediaType("application/ld+json"))
56-
.mediaType("rdfxml", parseMediaType("application/rdf+xml"))
57-
// Images
58-
.mediaType("svg", parseMediaType("image/svg+xml"))
59-
.mediaType("png", MediaType.IMAGE_PNG)
60-
.mediaType("dot", parseMediaType("text/vnd+graphviz"))
61-
// Archives
62-
.mediaType("zip", parseMediaType("application/zip"))
63-
.mediaType("ro", parseMediaType("application/vnd.wf4ever.robundle+zip"))
64-
// raw redirects
65-
.mediaType("yaml", parseMediaType("text/x-yaml"))
66-
.mediaType("raw", MediaType.APPLICATION_OCTET_STREAM);
78+
ContentNegotiationConfigurer c = configurer.favorParameter(true);
79+
for (Format f : Format.values()) {
80+
c = c.mediaType(f.name(), f.mediaType());
81+
}
6782
}
6883

6984
@Override

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

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

2020
package org.commonwl.view.cwl;
2121

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;
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.nio.file.Files;
29+
import java.nio.file.Path;
30+
import java.util.ArrayList;
31+
import java.util.HashMap;
32+
import java.util.Iterator;
33+
import java.util.List;
34+
import java.util.Map;
35+
2736
import org.apache.commons.io.FileUtils;
2837
import org.apache.commons.io.FilenameUtils;
2938
import org.apache.jena.iri.IRI;
@@ -48,15 +57,11 @@
4857
import org.springframework.stereotype.Service;
4958
import org.yaml.snakeyaml.Yaml;
5059

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

6166
/**
6267
* Provides CWL parsing for workflows to gather an overview
@@ -244,8 +249,10 @@ public Workflow parseWorkflowWithCwltool(Workflow basicModel,
244249
String packedWorkflowID = gitDetails.getPackedId();
245250

246251
// Get paths to workflow
247-
String url = basicModel.getPermalink();
248-
String localPath = workflowFile.toAbsolutePath().toString();
252+
String url = basicModel.getIdentifier();
253+
String workflowFileURI = workflowFile.toAbsolutePath().toUri().toString();
254+
String workTreeUri = workTree.toAbsolutePath().toUri().toString();
255+
String localPath = workflowFileURI;
249256
String gitPath = gitDetails.getPath();
250257
if (packedWorkflowID != null) {
251258
if (packedWorkflowID.charAt(0) != '#') {
@@ -259,8 +266,10 @@ public Workflow parseWorkflowWithCwltool(Workflow basicModel,
259266
// Get RDF representation from cwltool
260267
if (!rdfService.graphExists(url)) {
261268
String rdf = cwlTool.getRDF(localPath);
262-
rdf = rdf.replace("file://" + workTree.toAbsolutePath().toString(),
263-
"https://w3id.org/cwl/view/git/" + latestCommit);
269+
// Replace /tmp/123123 with permalink base
270+
// NOTE: We do not just replace workflowFileURI, all referenced files will also get rewritten
271+
rdf = rdf.replace(workTreeUri,
272+
"https://w3id.org/cwl/view/git/" + latestCommit + "/");
264273
// Workaround for common-workflow-language/cwltool#427
265274
rdf = rdf.replace("<rdfs:>", "<http://www.w3.org/2000/01/rdf-schema#>");
266275

src/main/java/org/commonwl/view/researchobject/ROBundleService.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.apache.taverna.robundle.manifest.PathAnnotation;
4747
import org.apache.taverna.robundle.manifest.PathMetadata;
4848
import org.apache.taverna.robundle.manifest.Proxy;
49+
import org.commonwl.view.WebConfig.Format;
4950
import org.commonwl.view.cwl.CWLTool;
5051
import org.commonwl.view.cwl.CWLValidationException;
5152
import org.commonwl.view.cwl.RDFService;
@@ -137,7 +138,7 @@ public Bundle createBundle(Workflow workflow, GitDetails gitInfo) throws IOExcep
137138
// TODO: Make this importedBy/On/From
138139
manifest.setRetrievedBy(appAgent);
139140
manifest.setRetrievedOn(manifest.getCreatedOn());
140-
manifest.setRetrievedFrom(new URI(workflow.getPermalink("ro")));
141+
manifest.setRetrievedFrom(new URI(workflow.getPermalink(Format.ro)));
141142

142143
// Make a directory in the RO bundle to store the files
143144
Path bundleRoot = bundle.getRoot();
@@ -164,12 +165,12 @@ public Bundle createBundle(Workflow workflow, GitDetails gitInfo) throws IOExcep
164165
File png = graphVizService.getGraph(workflow.getID() + ".png", workflow.getVisualisationDot(), "png");
165166
Files.copy(png.toPath(), bundleRoot.resolve("visualisation.png"));
166167
PathMetadata pngAggr = bundle.getManifest().getAggregation(bundleRoot.resolve("visualisation.png"));
167-
pngAggr.setRetrievedFrom(new URI(workflow.getPermalink("png")));
168+
pngAggr.setRetrievedFrom(new URI(workflow.getPermalink(Format.png)));
168169

169170
File svg = graphVizService.getGraph(workflow.getID() + ".svg", workflow.getVisualisationDot(), "svg");
170171
Files.copy(svg.toPath(), bundleRoot.resolve("visualisation.svg"));
171172
PathMetadata svgAggr = bundle.getManifest().getAggregation(bundleRoot.resolve("visualisation.svg"));
172-
svgAggr.setRetrievedFrom(new URI(workflow.getPermalink("svg")));
173+
svgAggr.setRetrievedFrom(new URI(workflow.getPermalink(Format.svg)));
173174

174175
// Add annotation files
175176
GitDetails wfDetails = workflow.getRetrievedFrom();
@@ -192,7 +193,7 @@ public Bundle createBundle(Workflow workflow, GitDetails gitInfo) throws IOExcep
192193
} catch (CWLValidationException ex) {
193194
logger.error("Could not pack workflow when creating Research Object", ex.getMessage());
194195
}
195-
String rdfUrl = workflow.getPermalink();
196+
String rdfUrl = workflow.getIdentifier();
196197
if (rdfService.graphExists(rdfUrl)) {
197198
addAggregation(bundle, manifestAnnotations, "workflow.ttl",
198199
new String(rdfService.getModel(rdfUrl, "TURTLE")));
@@ -314,7 +315,7 @@ private void addFilesToBundle(GitDetails gitDetails, Bundle bundle, Path bundleP
314315
// Attempt to get authors from cwl description - takes priority
315316
ResultSet descAuthors = rdfService.getAuthors(bundlePath
316317
.resolve(file.getName()).toString().substring(10),
317-
workflow.getPermalink());
318+
workflow.getIdentifier());
318319
if (descAuthors.hasNext()) {
319320
QuerySolution authorSoln = descAuthors.nextSolution();
320321
HashableAgent newAuthor = new HashableAgent();
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.commonwl.view.workflow;
21+
22+
import java.util.Collection;
23+
import java.util.Collections;
24+
import java.util.HashSet;
25+
import java.util.Set;
26+
27+
import org.commonwl.view.WebConfig.Format;
28+
import org.springframework.http.HttpStatus;
29+
import org.springframework.web.bind.annotation.ResponseStatus;
30+
31+
/**
32+
* Exception thrown when multiple workflows exist for the request
33+
*/
34+
@ResponseStatus(value = HttpStatus.MULTIPLE_CHOICES)
35+
public class MultipleWorkflowsException extends RuntimeException {
36+
37+
private final Collection<Workflow> matches;
38+
39+
public MultipleWorkflowsException(Workflow match) {
40+
this(Collections.singleton(match));
41+
}
42+
43+
public MultipleWorkflowsException(Collection<Workflow> matches) {
44+
if (matches.isEmpty()) {
45+
throw new IllegalArgumentException("MultipleWorkflowsException, but empty list of workflows");
46+
}
47+
this.matches = matches;
48+
}
49+
50+
// Always CRLF in text/uri-list
51+
private final String CRLF = "\r\n";
52+
53+
public String getRawPermalink() {
54+
// all raw URIs should be the same without ?part=
55+
return matches.stream().findAny().get().getPermalink(Format.raw);
56+
}
57+
58+
/**
59+
* Generate a text/uri-list of potential representations/redirects
60+
*
61+
* @see https://www.iana.org/assignments/media-types/text/uri-list
62+
*/
63+
@Override
64+
public String toString() {
65+
StringBuffer sb = new StringBuffer();
66+
sb.append("## Multiple workflow representations found");
67+
sb.append(CRLF);
68+
sb.append("# ");
69+
sb.append(CRLF);
70+
71+
sb.append("# ");
72+
sb.append(Format.raw.mediaType());
73+
sb.append(CRLF);
74+
sb.append(getRawPermalink());
75+
sb.append(CRLF);
76+
77+
Set<String> seen = new HashSet<>();
78+
// For each workflow, link to each remaining format
79+
for (Workflow w : matches) {
80+
if (!seen.add(w.getIdentifier())) {
81+
// Skip permalink duplicates
82+
continue;
83+
}
84+
sb.append("#");
85+
sb.append(CRLF);
86+
sb.append("# ");
87+
sb.append(w.getIdentifier());
88+
sb.append(CRLF);
89+
for (Format f : Format.values()) {
90+
if (f == Format.raw) {
91+
// Already did that one above
92+
continue;
93+
}
94+
sb.append("# ");
95+
sb.append(f.mediaType());
96+
sb.append(CRLF);
97+
sb.append(w.getPermalink(f));
98+
sb.append(CRLF);
99+
}
100+
}
101+
return sb.toString();
102+
}
103+
104+
}

0 commit comments

Comments
 (0)