Skip to content

Commit 15f21d0

Browse files
Improve license visualization (#491)
This commit introduces 3 main improvements: - It moves the license dictionary from the frontend (Thymeleaf template) to the backend (Workflow class) - It groups licenses common links to the corresponding SPDX URIs - It translates SPDX URIs into licenses' common names for better visualization Co-authored-by: Bruno P. Kinoshita <[email protected]>
1 parent 608aadb commit 15f21d0

File tree

8 files changed

+224
-97
lines changed

8 files changed

+224
-97
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@
156156
<groupId>org.springframework.boot</groupId>
157157
<artifactId>spring-boot-starter-validation</artifactId>
158158
</dependency>
159+
<!-- For SPDX license identifiers -->
160+
<dependency>
161+
<groupId>com.github.tbouron</groupId>
162+
<artifactId>spdx-license-checker</artifactId>
163+
<version>1.0.0</version>
164+
</dependency>
159165
<!-- Test dependencies -->
160166
<dependency>
161167
<groupId>org.springframework.boot</groupId>

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.util.Set;
3838
import org.apache.commons.io.FileUtils;
3939
import org.apache.commons.io.FilenameUtils;
40+
import org.apache.commons.lang3.StringUtils;
4041
import org.apache.jena.iri.IRI;
4142
import org.apache.jena.iri.IRIFactory;
4243
import org.apache.jena.ontology.OntModelSpec;
@@ -71,6 +72,7 @@ public class CWLService {
7172
// Autowired properties/services
7273
private final RDFService rdfService;
7374
private final CWLTool cwlTool;
75+
private final Map<String, String> licenseVocab;
7476
private final int singleFileSizeLimit;
7577

7678
// CWL specific strings
@@ -108,9 +110,11 @@ public class CWLService {
108110
public CWLService(
109111
RDFService rdfService,
110112
CWLTool cwlTool,
113+
Map<String, String> licenseVocab,
111114
@Value("${singleFileSizeLimit}") int singleFileSizeLimit) {
112115
this.rdfService = rdfService;
113116
this.cwlTool = cwlTool;
117+
this.licenseVocab = licenseVocab;
114118
this.singleFileSizeLimit = singleFileSizeLimit;
115119
}
116120

@@ -448,9 +452,9 @@ public Workflow parseWorkflowWithCwltool(Workflow basicModel, Path workflowFile,
448452
}
449453
// Try to determine license
450454
ResultSet licenseResult = rdfService.getLicense(url);
451-
String licenseLink = null;
455+
String licenseLink;
452456
if (licenseResult.hasNext()) {
453-
licenseLink = licenseResult.next().get("license").toString();
457+
licenseLink = normaliseLicenseLink(licenseResult.next().get("license").toString());
454458
} else {
455459
// Check for "LICENSE"-like files in root of git repo
456460
licenseLink = basicModel.getRetrievedFrom().getLicense(workTree);
@@ -1056,4 +1060,12 @@ private Object extractRun(Map<String, Object> step) {
10561060
}
10571061
return null;
10581062
}
1063+
1064+
public String normaliseLicenseLink(String licenseLink) {
1065+
if (licenseLink == null) {
1066+
return null;
1067+
}
1068+
String httpsLicenseLink = StringUtils.stripEnd(licenseLink.replace("http://", "https://"), "/");
1069+
return licenseVocab.getOrDefault(httpsLicenseLink, licenseLink);
1070+
}
10591071
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.commonwl.view.git;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
import org.apache.commons.lang3.StringUtils;
9+
import org.commonwl.view.util.LicenseUtils;
10+
import org.springframework.context.annotation.Bean;
11+
import org.springframework.context.annotation.Configuration;
12+
import org.springframework.context.annotation.Scope;
13+
import org.springframework.web.client.RestTemplate;
14+
import org.springframework.web.context.WebApplicationContext;
15+
16+
@Configuration
17+
public class GitConfig {
18+
19+
@Bean
20+
@Scope(WebApplicationContext.SCOPE_APPLICATION)
21+
public Map<String, String> licenseVocab() {
22+
RestTemplate restTemplate = new RestTemplate();
23+
ObjectNode jsonLicenses =
24+
restTemplate.getForObject(LicenseUtils.SPDX_LICENSES_JSON_URL, ObjectNode.class);
25+
if (jsonLicenses == null) {
26+
throw new GitLicenseException(
27+
"Failed to load SPDX licenses from " + LicenseUtils.SPDX_LICENSES_JSON_URL);
28+
}
29+
Map<String, String> licenseMap = new HashMap<>();
30+
for (JsonNode license : jsonLicenses.withArray("licenses")) {
31+
String spdxURL = LicenseUtils.SPDX_LICENSES_PREFIX + license.get("licenseId").asText();
32+
for (JsonNode alias : license.withArray("seeAlso")) {
33+
licenseMap.put(
34+
StringUtils.stripEnd(alias.asText().replace("http://", "https://"), "/"), spdxURL);
35+
}
36+
}
37+
return licenseMap;
38+
}
39+
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.net.URISyntaxException;
2929
import java.nio.file.Path;
3030
import java.util.Objects;
31+
import org.commonwl.view.util.LicenseUtils;
3132
import org.slf4j.Logger;
3233
import org.slf4j.LoggerFactory;
3334

@@ -39,8 +40,6 @@ public class GitDetails implements Serializable {
3940

4041
private final Logger logger = LoggerFactory.getLogger(this.getClass());
4142

42-
private static final String SPDX_PREFIX = "https://spdx.org/licenses/";
43-
4443
private String repoUrl;
4544
private String branch;
4645
private String path;
@@ -323,7 +322,8 @@ public String getLicense(Path workTree) throws GitLicenseException {
323322
}
324323
String key = jsonLicenses.withArray("licenses").get(0).get("key").asText();
325324
if (!"other".equals(key)) {
326-
return SPDX_PREFIX + jsonLicenses.withArray("licenses").get(0).get("spdx_id").asText();
325+
return LicenseUtils.SPDX_LICENSES_PREFIX
326+
+ jsonLicenses.withArray("licenses").get(0).get("spdx_id").asText();
327327
} else {
328328
return licenseLink;
329329
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.commonwl.view.util;
2+
3+
public class LicenseUtils {
4+
public static final String SPDX_LICENSES_PREFIX = "https://spdx.org/licenses/";
5+
public static final String SPDX_LICENSES_JSON_URL = SPDX_LICENSES_PREFIX + "licenses.json";
6+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
2323
import com.fasterxml.jackson.annotation.JsonInclude;
2424
import com.fasterxml.jackson.annotation.JsonProperty;
25+
import com.github.tbouron.SpdxLicense;
2526
import java.io.Serializable;
2627
import java.util.Date;
2728
import java.util.Map;
@@ -40,6 +41,7 @@
4041
import org.commonwl.view.cwl.CWLStep;
4142
import org.commonwl.view.git.GitDetails;
4243
import org.commonwl.view.util.BaseEntity;
44+
import org.commonwl.view.util.LicenseUtils;
4345
import org.hibernate.annotations.GenericGenerator;
4446
import org.hibernate.annotations.Type;
4547
import org.springframework.format.annotation.DateTimeFormat;
@@ -353,6 +355,16 @@ public void setLicenseLink(String licenseLink) {
353355
this.licenseLink = licenseLink;
354356
}
355357

358+
public String getLicenseName() {
359+
if (licenseLink == null) {
360+
return null;
361+
}
362+
if (licenseLink.startsWith(LicenseUtils.SPDX_LICENSES_PREFIX)) {
363+
return SpdxLicense.fromId(licenseLink.replace(LicenseUtils.SPDX_LICENSES_PREFIX, "")).name;
364+
}
365+
return licenseLink;
366+
}
367+
356368
@Override
357369
public boolean equals(Object o) {
358370
if (this == o) return true;

src/main/resources/templates/workflow.html

Lines changed: 39 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -49,29 +49,29 @@ <h4 id="modalTitle">Dot File Source:</h4>
4949
digraph G {
5050
graph [
5151
bgcolor="#eeeeee"
52-
color="black"
53-
fontsize="10"
54-
labeljust="left"
55-
clusterrank="local"
56-
ranksep="0.22"
57-
nodesep="0.05"
58-
];
52+
color="black"
53+
fontsize="10"
54+
labeljust="left"
55+
clusterrank="local"
56+
ranksep="0.22"
57+
nodesep="0.05"
58+
];
5959
node [
60-
fontname="Helvetica"
61-
fontsize="10"
62-
fontcolor="black"
63-
shape="record"
64-
height="0"
65-
width="0"
66-
color="black"
67-
fillcolor="lightgoldenrodyellow"
68-
style="filled"
60+
fontname="Helvetica"
61+
fontsize="10"
62+
fontcolor="black"
63+
shape="record"
64+
height="0"
65+
width="0"
66+
color="black"
67+
fillcolor="lightgoldenrodyellow"
68+
style="filled"
6969
];
7070
edge [
71-
fontname="Helvetica"
72-
fontsize="8"
73-
fontcolor="black"
74-
color="black"
71+
fontname="Helvetica"
72+
fontsize="8"
73+
fontcolor="black"
74+
color="black"
7575
arrowsize="0.7"
7676
];
7777
subgraph cluster_inputs {
@@ -199,43 +199,18 @@ <h2 style="float:left;">Requires: </h2>
199199
<img th:if="${workflow.dockerLink == 'true'}" id="dockerLogo" th:src="@{/img/Docker-logo.png}" src="../static/img/Docker-logo.png" alt="docker logo" />
200200
</div>
201201
<div th:if="${workflow.licenseLink != null}" class="alert alert-success" role="alert">
202-
<span class="hidden-xs">This workflow is Open Source and may be reused according to the terms of:</span>
203-
<a href="http://example.com/" th:href="@{${workflow.licenseLink}}" class="alert-link">
204-
<span th:remove="tag" th:switch="${workflow.licenseLink}">
205-
<!-- TODO: Move license 'registry' to controller? -->
206-
<span th:remove="tag" th:case="'https://www.apache.org/licenses/LICENSE-2.0'">
207-
Apache License, version 2.0
208-
</span>
209-
<span th:remove="tag" th:case="'http://www.apache.org/licenses/LICENSE-2.0'">
210-
Apache License, version 2.0
211-
</span>
212-
<span th:remove="tag" th:case="'https://spdx.org/licenses/Apache-2.0'">
213-
Apache License, version 2.0
214-
</span>
215-
<span th:remove="tag" th:case="'https://mit-license.org/'">
216-
MIT License
217-
</span>
218-
<span th:remove="tag" th:case="'https://spdx.org/licenses/AGPL-3.0'">
219-
GNU Affero General Public License v3.0
220-
</span>
221-
<span th:remove="tag" th:case="'http://www.gnu.org/licenses/agpl.txt'">
222-
GNU Affero General Public License
223-
</span>
224-
<span th:remove="tag" th:case="'http://www.opensource.org/licenses/AGPL-3.0'">
225-
GNU Affero General Public License v3.0
226-
</span>
227-
<div th:remove="tag" th:case="*" th:text="${workflow.licenseLink}">
228-
<!-- FIXME: This may not be an open source license! -->
229-
http://example.com/LICENSE
230-
</div>
231-
</span>
232-
</a>
233-
<div class="hidden-xs"><small>Note that the <em>tools</em> invoked by the workflow may have separate licenses.</small></div>
234-
</div>
235-
<div th:unless="${workflow.licenseLink}" class="alert alert-warning" role="alert">
236-
Unknown workflow license, check
237-
<a th:href="@{${workflow.retrievedFrom.getUrl()}}" href="#" rel="noopener" target="_blank">source repository</a>.
238-
</div>
202+
<span class="hidden-xs">This workflow is Open Source and may be reused according to the terms of:</span>
203+
<a href="http://example.com/" th:href="@{${workflow.licenseLink}}" class="alert-link">
204+
<div th:remove="tag" th:text="${workflow.getLicenseName()}">
205+
http://example.com/LICENSE
206+
</div>
207+
</a>
208+
<div class="hidden-xs"><small>Note that the <em>tools</em> invoked by the workflow may have separate licenses.</small></div>
209+
</div>
210+
<div th:unless="${workflow.licenseLink}" class="alert alert-warning" role="alert">
211+
Unknown workflow license, check
212+
<a th:href="@{${workflow.retrievedFrom.getUrl()}}" href="#" rel="noopener" target="_blank">source repository</a>.
213+
</div>
239214
<h2>Inputs</h2>
240215
<div th:if="${workflow.inputs.isEmpty()}" class="alert alert-info">
241216
<p>There are no inputs in this workflow</p>
@@ -273,10 +248,10 @@ <h2>Steps</h2>
273248
<div class="table-responsive">
274249
<table class="table table-striped table-hover steps">
275250
<thead>
276-
<th>ID</th>
277-
<th>Runs</th>
278-
<th>Label</th>
279-
<th>Doc</th>
251+
<th>ID</th>
252+
<th>Runs</th>
253+
<th>Label</th>
254+
<th>Doc</th>
280255
</thead>
281256
<tbody>
282257
<tr th:each="step : ${workflow.steps}" th:with="workflowURL=@{${workflow.retrievedFrom.getUrl()}}">
@@ -332,16 +307,16 @@ <h2>Outputs</h2>
332307
</div>
333308
</div>
334309
<div class="row hidden-print">
335-
<div class="col-md-12 text-center" id="formats">
336-
<span th:each="format : ${formats}">
310+
<div class="col-md-12 text-center" id="formats">
311+
<span th:each="format : ${formats}">
337312
<a th:id="|format-${format}|" role="button" class="btn btn-default btn-sm" th:href="${workflow.getPermalink(format.name())}" th:text="${format}"
338313
href="#">html</a>
339314
</span>
340315
</div>
341316
</div>
342317
<div class="visible-print-block">
343318
<address>Permalink:
344-
<code th:text="${workflow.permalink}">https://w3id.org/cwl/view/git/933bf2a1a1cce32d88f88f136275535da9df0954/workflows/larger/test-hello.cwl</code>
319+
<code th:text="${workflow.permalink}">https://w3id.org/cwl/view/git/933bf2a1a1cce32d88f88f136275535da9df0954/workflows/larger/test-hello.cwl</code>
345320
</address>
346321
</div>
347322
</div>

0 commit comments

Comments
 (0)