diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 15a26d28..9cb6f1ad 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -30,7 +30,7 @@ jobs: uses: actions/setup-java@v5 with: distribution: 'adopt' - java-version: '17' + java-version: '25' - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fc8f1c30..46f94571 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -21,7 +21,7 @@ jobs: uses: actions/setup-java@v5 with: distribution: 'adopt' - java-version: '17' + java-version: '25' # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/Dockerfile b/Dockerfile index 173e1959..4407f41c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/library/maven:3-eclipse-temurin-17-alpine AS build-licensee +FROM docker.io/library/maven:3-eclipse-temurin-25-alpine AS build-licensee RUN apk add --update \ alpine-sdk \ @@ -10,7 +10,7 @@ RUN apk add --update \ RUN gem install licensee -FROM docker.io/library/maven:3-eclipse-temurin-17-alpine +FROM docker.io/library/maven:3-eclipse-temurin-25-alpine MAINTAINER Stian Soiland-Reyes # Build-time metadata as defined at https://github.com/opencontainers/image-spec/blob/main/annotations.md diff --git a/README.md b/README.md index 189d8f9f..93623f4f 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,21 @@ # CWL Viewer -This is a [Spring Boot](http://projects.spring.io/spring-boot/) MVC application which fetches [Common Workflow Language](http://www.commonwl.org/) files from a Github repository and creates a page for it detailing the main workflow and its inputs, outputs and steps. +This is a [Spring Boot](http://projects.spring.io/spring-boot/) MVC application which fetches [Common Workflow Language](http://www.commonwl.org/) files +from a GitHub repository and creates a page for it detailing the main workflow and its inputs, outputs and steps. [![Build Status](https://github.com/common-workflow-language/cwlviewer/workflows/CWL%20Viewer%20Build/badge.svg?branch=main)](https://github.com/common-workflow-language/cwlviewer/actions?query=workflow%3A%22CWL%20Viewer%20Build%22) [![Coverage Status](https://coveralls.io/repos/github/common-workflow-language/cwlviewer/badge.svg)](https://coveralls.io/github/common-workflow-language/cwlviewer) [![Gitter](https://img.shields.io/gitter/room/gitterHQ/gitter.svg)](https://gitter.im/common-workflow-language/cwlviewer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![](https://images.microbadger.com/badges/image/commonworkflowlanguage/cwlviewer.svg)](https://microbadger.com/images/commonworkflowlanguage/cwlviewer "MicroBadger commonworkflowlanguage/cwlviewer") [![Docker image commonworkflowlanguage/cwlviewer](https://images.microbadger.com/badges/version/commonworkflowlanguage/cwlviewer.svg)](https://hub.docker.com/r/commonworkflowlanguage/cwlviewer/ "Docker Hub commonworkflowlanguage/cwlviewer") [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.823534.svg)](https://doi.org/10.5281/zenodo.823534) # Using CWL Viewer -You are recommended to use the **production instance** of CWL Viewer at https://view.commonwl.org/ which runs the latest [release](https://github.com/common-workflow-language/cwlviewer/releases). Any downtime should be reported on the [gitter chat for cwlviewer](https://gitter.im/common-workflow-language/cwlviewer). +You are recommended to use the **production instance** of CWL Viewer at https://view.commonwl.org/ which runs +the latest [release](https://github.com/common-workflow-language/cwlviewer/releases). Any downtime should be reported on the [gitter chat for cwlviewer](https://gitter.im/common-workflow-language/cwlviewer). - # Running @@ -19,7 +24,8 @@ If you are a developer, or you want to use the CWL Viewer in a closed environmen ## Recommended - Running with Docker -This application can be started with [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/install/) and Docker Compose is the recommended method of running or developing this codebase. +This application can be started with [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/install/) and Docker Compose is the recommended +method of running or developing this codebase. Then run the following commands to clone the project in your local system. @@ -134,7 +140,8 @@ To compile you will need [Java 17](https://www.oracle.com/java/technologies/down (e.g. [Eclipse Adoptium](https://projects.eclipse.org/projects/adoptium)) and version, as well as [Apache Maven 3](https://maven.apache.org/download.cgi) (`apt install maven`). -Spring Boot uses an embedded HTTP server. The Spring Boot Maven plugin includes a run goal which can be used to quickly compile and run it: +Spring Boot uses an embedded HTTP server. The Spring Boot Maven plugin includes a run goal which can be used to +quickly compile and run it: ``` $ mvn spring-boot:run @@ -166,9 +173,11 @@ Now check out http://localhost:8080/ to access CWL Viewer. ## Configuration -There are a variety of configuration options detailed in the [application configuration file](https://github.com/common-workflow-language/cwlviewer/blob/master/src/main/resources/application.properties) which can be adjusted. +There are a variety of configuration options detailed in the [application configuration file](https://github.com/common-workflow-language/cwlviewer/blob/master/src/main/resources/application.properties) which can +be adjusted. -When deploying with docker, these can be overridden externally by creating/modifying `docker-compose.override.yml` as follows: +When deploying with docker, these can be overridden externally by creating/modifying `docker-compose.override.yml` +as follows: ```yaml version: '3.9' diff --git a/pom.xml b/pom.xml index f4b3691a..4b644f27 100644 --- a/pom.xml +++ b/pom.xml @@ -15,18 +15,17 @@ org.springframework.boot spring-boot-starter-parent - 3.1.4 + 4.1.0-RC1 UTF-8 UTF-8 - 17 + 25 6.1.0 0.13.6 - 2.21.3 2.17.0 @@ -65,24 +64,6 @@ org.springframework.boot spring-boot-starter-data-jpa - - org.springframework.data - spring-data-commons - - - org.springframework.data - spring-data-jpa - - - io.hypersistence - hypersistence-utils-hibernate-62 - 3.9.4 - - - com.fasterxml.jackson.module - jackson-module-jakarta-xmlbind-annotations - 2.21.3 - - - com.fasterxml.jackson.core - jackson-core - ${ver.jackson} - org.apache.jena @@ -184,14 +159,13 @@ org.testcontainers - junit-jupiter + postgresql 1.21.4 test - org.testcontainers - postgresql - 1.21.4 + org.springframework.boot + spring-boot-starter-data-jpa-test test diff --git a/src/main/java/org/commonwl/view/CwlViewerApplication.java b/src/main/java/org/commonwl/view/CwlViewerApplication.java index 91e9c5c8..db12106f 100644 --- a/src/main/java/org/commonwl/view/CwlViewerApplication.java +++ b/src/main/java/org/commonwl/view/CwlViewerApplication.java @@ -33,7 +33,7 @@ @EnableTransactionManagement public class CwlViewerApplication { - public static void main(String[] args) { + static void main(String[] args) { SpringApplication.run(CwlViewerApplication.class, args); } } diff --git a/src/main/java/org/commonwl/view/Scheduler.java b/src/main/java/org/commonwl/view/Scheduler.java index 17cb3043..8bd910a2 100644 --- a/src/main/java/org/commonwl/view/Scheduler.java +++ b/src/main/java/org/commonwl/view/Scheduler.java @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.commonwl.view; import java.io.File; @@ -63,14 +82,13 @@ public void removeOldQueuedWorkflowEntries() { calendar.add(Calendar.HOUR, -QUEUED_WORKFLOW_AGE_LIMIT_HOURS); Date removeTime = calendar.getTime(); - logger.info("The time is " + now); - logger.info( - "Delete time interval is : OLDER THAN " + QUEUED_WORKFLOW_AGE_LIMIT_HOURS + " HOURS"); - logger.info("Deleting queued workflows older than or equal to " + removeTime); + logger.info("The time is {}", now); + logger.info("Delete time interval is : OLDER THAN {} HOURS", QUEUED_WORKFLOW_AGE_LIMIT_HOURS); + logger.info("Deleting queued workflows older than or equal to {}", removeTime); logger.info( - queuedWorkflowRepository.deleteByTempRepresentation_RetrievedOnLessThanEqual(removeTime) - + " Old queued workflows removed"); + "{} Old queued workflows removed", + queuedWorkflowRepository.deleteByTempRepresentation_RetrievedOnLessThanEqual(removeTime)); } /** @@ -79,9 +97,9 @@ public void removeOldQueuedWorkflowEntries() { *

Will scan each temporary directory (graphviz, RO, git), searching for files exceeding a * specified threshold. * - *

It scans the first level directories, i.e. it does not recursively scans directories. So it + *

It scans the first level directories, i.e. it does not recursively scan directories. So it * will delete any RO or Git repository directories that exceed the threshold. Similarly, it will - * delete any graph (svg, png, etc) that also exceed it. + * delete any graph (svg, png, etc.) that also exceeds it. * *

Errors logged through Logger. Settings in Spring application properties file. * @@ -110,7 +128,7 @@ private void clearDirectory(String temporaryDirectory) { File temporaryDirectoryFile = new File(temporaryDirectory); String[] files = temporaryDirectoryFile.list(new AgeFileFilter(Date.from(cutoff))); - if (files != null && files.length > 0) { + if (files != null) { for (String fileName : files) { File fileToDelete = new File(temporaryDirectoryFile, fileName); try { @@ -122,9 +140,9 @@ private void clearDirectory(String temporaryDirectory) { // expected and // must be treated as errors. logger.error( - String.format( - "Failed to delete old temporary file or directory [%s]: %s", - fileToDelete.getAbsolutePath(), e.getMessage()), + "Failed to delete old temporary file or directory [{}]: {}", + fileToDelete.getAbsolutePath(), + e.getMessage(), e); } } diff --git a/src/main/java/org/commonwl/view/WebConfig.java b/src/main/java/org/commonwl/view/WebConfig.java index 7101ab22..fca6c2c8 100644 --- a/src/main/java/org/commonwl/view/WebConfig.java +++ b/src/main/java/org/commonwl/view/WebConfig.java @@ -38,7 +38,7 @@ public class WebConfig implements WebMvcConfigurer { * @see Workflow#getPermalink(Format) * @see WorkflowPermalinkController */ - public static enum Format { + public enum Format { // Browser html(MediaType.TEXT_HTML), // API diff --git a/src/main/java/org/commonwl/view/cwl/CWLElement.java b/src/main/java/org/commonwl/view/cwl/CWLElement.java index 7f496403..17527653 100644 --- a/src/main/java/org/commonwl/view/cwl/CWLElement.java +++ b/src/main/java/org/commonwl/view/cwl/CWLElement.java @@ -20,23 +20,22 @@ package org.commonwl.view.cwl; import com.fasterxml.jackson.annotation.JsonInclude; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** Represents the input/output of a workflow/tool */ @JsonInclude(JsonInclude.Include.NON_EMPTY) -public class CWLElement { +public class CWLElement implements Serializable { private String label; private String doc; private String type; private String format; - private List sourceID; + private final List sourceID = new ArrayList<>(); private String defaultVal; - public CWLElement() { - this.sourceID = new ArrayList<>(); - } + public CWLElement() {} public String getLabel() { return label; diff --git a/src/main/java/org/commonwl/view/cwl/CWLNotAWorkflowException.java b/src/main/java/org/commonwl/view/cwl/CWLNotAWorkflowException.java index db0efa03..63a5b48c 100644 --- a/src/main/java/org/commonwl/view/cwl/CWLNotAWorkflowException.java +++ b/src/main/java/org/commonwl/view/cwl/CWLNotAWorkflowException.java @@ -20,6 +20,7 @@ package org.commonwl.view.cwl; /** Exception thrown when the provided CWL document is not a Workflow. */ +@SuppressWarnings("unused") public class CWLNotAWorkflowException extends CWLValidationException { public CWLNotAWorkflowException(String message) { diff --git a/src/main/java/org/commonwl/view/cwl/CWLService.java b/src/main/java/org/commonwl/view/cwl/CWLService.java index 0c440d2b..b8aa8a35 100644 --- a/src/main/java/org/commonwl/view/cwl/CWLService.java +++ b/src/main/java/org/commonwl/view/cwl/CWLService.java @@ -177,12 +177,12 @@ public List getWorkflowOverviewsFromPacked(File packedFile) th * @param packedWorkflowId The ID of the workflow object if the file is packed. null * means the workflow is not expected to be packed, while "" means the first workflow found is * used, packed or non-packed. - * @param defaultLabel Label to give workflow if not set + * @param defaultLabel Label to give a workflow if not set * @return The constructed workflow object */ public Workflow parseWorkflowNative( InputStream workflowStream, String packedWorkflowId, String defaultLabel) - throws IOException, WorkflowNotFoundException, CWLValidationException { + throws WorkflowNotFoundException, CWLValidationException { // Parse file as yaml Map cwlFile = yamlStreamToJson(workflowStream); @@ -236,7 +236,7 @@ public Workflow parseWorkflowNative( dotWriter.writeGraph(workflowModel); workflowModel.setVisualisationDot(graphWriter.toString()); } catch (IOException ex) { - logger.error("Failed to create DOT graph for workflow: " + ex.getMessage()); + logger.error("Failed to create DOT graph for workflow: {}", ex.getMessage()); } return workflowModel; @@ -425,12 +425,9 @@ public Workflow parseWorkflowWithCwltool(Workflow basicModel, Path workflowFile, CWLStep wfStep = new CWLStep(); IRI workflowPath = iriFactory.construct(url).resolve("./"); - Object runValue = step.get("run").asResource().toString(); - if (String.class.isAssignableFrom(runValue.getClass())) { - String runPath = (String) runValue; - wfStep.setRun(workflowPath.relativize(runPath).toString()); - wfStep.setRunType(rdfService.strToRuntype(step.get("runtype").toString())); - } + String runValue = step.get("run").asResource().toString(); + wfStep.setRun(workflowPath.relativize(runValue).toString()); + wfStep.setRunType(rdfService.strToRuntype(step.get("runtype").toString())); if (step.contains("src")) { CWLElement src = new CWLElement(); @@ -482,12 +479,12 @@ public Workflow parseWorkflowWithCwltool(Workflow basicModel, Path workflowFile, // Generate DOT graph StringWriter graphWriter = new StringWriter(); - RDFDotWriter RDFDotWriter = new RDFDotWriter(graphWriter, rdfService, gitPath); + RDFDotWriter rdfDotWriter = new RDFDotWriter(graphWriter, rdfService, gitPath); try { - RDFDotWriter.writeGraph(url); + rdfDotWriter.writeGraph(url); workflowModel.setVisualisationDot(graphWriter.toString()); } catch (IOException ex) { - logger.error("Failed to create DOT graph for workflow: " + ex.getMessage()); + logger.error("Failed to create DOT graph for workflow: {}", ex.getMessage()); } return workflowModel; @@ -502,7 +499,7 @@ public Workflow parseWorkflowWithCwltool(Workflow basicModel, Path workflowFile, */ public WorkflowOverview getWorkflowOverview(File file) throws IOException { - // Get the content of this file from Github + // Get the content of this file from GitHub long fileSizeBytes = file.length(); // Check file size limit before parsing @@ -577,37 +574,28 @@ private void setFormat(CWLElement inputOutput, String format) { * Convert RDF URI for a type to a name * * @param uri The URI for the type - * @return The human readable name for that type + * @return The human-readable name for that type */ private String typeURIToString(String uri) { - switch (uri) { - case "http://www.w3.org/2001/XMLSchema#string": - return "String"; - case "https://w3id.org/cwl/cwl#File": - return "File"; - case "http://www.w3.org/2001/XMLSchema#boolean": - return "Boolean"; - case "http://www.w3.org/2001/XMLSchema#int": - return "Integer"; - case "http://www.w3.org/2001/XMLSchema#double": - return "Double"; - case "http://www.w3.org/2001/XMLSchema#float": - return "Float"; - case "http://www.w3.org/2001/XMLSchema#long": - return "Long"; - case "https://w3id.org/cwl/cwl#Directory": - return "Directory"; - default: - return uri; - } + return switch (uri) { + case "http://www.w3.org/2001/XMLSchema#string" -> "String"; + case "https://w3id.org/cwl/cwl#File" -> "File"; + case "http://www.w3.org/2001/XMLSchema#boolean" -> "Boolean"; + case "http://www.w3.org/2001/XMLSchema#int" -> "Integer"; + case "http://www.w3.org/2001/XMLSchema#double" -> "Double"; + case "http://www.w3.org/2001/XMLSchema#float" -> "Float"; + case "http://www.w3.org/2001/XMLSchema#long" -> "Long"; + case "https://w3id.org/cwl/cwl#Directory" -> "Directory"; + default -> uri; + }; } /** * Converts a yaml String to JsonNode * - * @param path A Path to a file containing the yaml content + * @param path A Path to a file containing the YAML content * @return A JsonNode with the content of the document - * @throws IOException + * @throws IOException If it fails to read open a stream to the given path */ private Map yamlPathToJson(Path path) throws IOException { LoadSettings settings = LoadSettings.builder().build(); @@ -618,9 +606,9 @@ private Map yamlPathToJson(Path path) throws IOException { } /** - * Converts a yaml String to JsonNode + * Converts a YAML String to JsonNode * - * @param yamlStream An InputStream containing the yaml content + * @param yamlStream An InputStream containing the YAML content * @return A JsonNode with the content of the document */ private Map yamlStreamToJson(InputStream yamlStream) { @@ -699,7 +687,7 @@ private Map getSteps(Map cwlDoc) { } /** - * Get a the inputs for a particular document + * Get the inputs for a particular document * * @param cwlDoc The document to get inputs for * @return A map of input IDs and details related to them @@ -748,7 +736,7 @@ private Map getStepInputsOutputs(Object inOut) { for (Map inOutNode : (List>) inOut) { CWLElement inputOutput = new CWLElement(); List sources = extractSource(inOutNode); - if (sources.size() > 0) { + if (!sources.isEmpty()) { for (String source : sources) { inputOutput.addSourceID(stepIDFromSource(source)); } @@ -878,7 +866,7 @@ private String extractID(Map step) { */ private String extractDefault(Map inputOutput) { if (inputOutput != null && inputOutput.containsKey(DEFAULT)) { - Object default_value = ((Map) inputOutput).get(DEFAULT); + Object default_value = inputOutput.get(DEFAULT); if (default_value == null) { return null; } @@ -900,7 +888,7 @@ private String extractDefault(Map inputOutput) { */ private List extractSource(Map inputOutput) { if (inputOutput != null) { - List sources = new ArrayList(); + List sources = new ArrayList<>(); Object sourceNode = null; // outputSource and source treated the same @@ -935,7 +923,7 @@ private List extractSource(Map inputOutput) { * @return The step ID */ private String stepIDFromSource(String source) { - if (source != null && source.length() > 0) { + if (source != null && !source.isEmpty()) { // Strip leading # if it exists if (source.charAt(0) == '#') { source = source.substring(1); @@ -1004,7 +992,7 @@ private String extractTypes(Object typeNode) { for (Object type : (List) typeNode) { if (type.getClass() == String.class) { // This is a simple type - if (((String) type).equals("null")) { + if (type.equals("null")) { // null as a type means this field is optional optional = true; } else { @@ -1021,7 +1009,7 @@ private String extractTypes(Object typeNode) { typeDetails.append(items); typeDetails.append("[], "); } else { - typeDetails.append(type.toString() + ", "); + typeDetails.append(type).append(", "); } } else { typeDetails.append((String) ((Map) type).get(TYPE)); diff --git a/src/main/java/org/commonwl/view/cwl/CWLStep.java b/src/main/java/org/commonwl/view/cwl/CWLStep.java index 5c3940a9..34dfac2e 100644 --- a/src/main/java/org/commonwl/view/cwl/CWLStep.java +++ b/src/main/java/org/commonwl/view/cwl/CWLStep.java @@ -20,11 +20,12 @@ package org.commonwl.view.cwl; import com.fasterxml.jackson.annotation.JsonInclude; +import java.io.Serializable; import java.util.Map; /** Represents a step of a workflow */ @JsonInclude(JsonInclude.Include.NON_EMPTY) -public class CWLStep { +public class CWLStep implements Serializable { private String label; private String doc; diff --git a/src/main/java/org/commonwl/view/cwl/CWLToolRunner.java b/src/main/java/org/commonwl/view/cwl/CWLToolRunner.java index ec61d6e9..f2c91534 100644 --- a/src/main/java/org/commonwl/view/cwl/CWLToolRunner.java +++ b/src/main/java/org/commonwl/view/cwl/CWLToolRunner.java @@ -34,8 +34,8 @@ import org.commonwl.view.workflow.Workflow; import org.commonwl.view.workflow.WorkflowRepository; import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.TransportException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -43,7 +43,7 @@ import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Component; -/** Replace existing workflow with the one given by cwltool */ +/** Replace an existing workflow with the one given by cwltool */ @Component @EnableAsync public class CWLToolRunner { @@ -77,8 +77,7 @@ public CWLToolRunner( } @Async - public void createWorkflowFromQueued(QueuedWorkflow queuedWorkflow) - throws IOException, InterruptedException { + public void createWorkflowFromQueued(QueuedWorkflow queuedWorkflow) throws IOException { Workflow tempWorkflow = queuedWorkflow.getTempRepresentation(); GitDetails gitInfo = tempWorkflow.getRetrievedFrom(); @@ -108,27 +107,24 @@ public void createWorkflowFromQueued(QueuedWorkflow queuedWorkflow) queuedWorkflow.setCwltoolStatus(CWLToolStatus.SUCCESS); } catch (QueryException ex) { - logger.error("Jena query exception for workflow " + queuedWorkflow.getId(), ex); + logger.error("Jena query exception for workflow {}", queuedWorkflow.getId(), ex); queuedWorkflow.setCwltoolStatus(CWLToolStatus.ERROR); queuedWorkflow.setMessage("An error occurred when executing a query on the SPARQL store"); FileUtils.deleteGitRepository(repo); } catch (CWLValidationException | GitLicenseException ex) { String message = ex.getMessage(); logger.error( - "Workflow " + queuedWorkflow.getId() + " from " + gitInfo.toSummary() + " : " + message, - ex); + "Workflow {} from {} : {}", queuedWorkflow.getId(), gitInfo.toSummary(), message, ex); queuedWorkflow.setCwltoolStatus(CWLToolStatus.ERROR); queuedWorkflow.setMessage(message); FileUtils.deleteGitRepository(repo); } catch (TransportException ex) { String message = ex.getMessage(); logger.error( - "Workflow retrieval error while processing " - + queuedWorkflow.getId() - + " from " - + gitInfo.toSummary() - + " : " - + message, + "Workflow retrieval error while processing {} from {} : {}", + queuedWorkflow.getId(), + gitInfo.toSummary(), + message, ex); queuedWorkflow.setCwltoolStatus(CWLToolStatus.ERROR); if (message.contains( @@ -143,24 +139,20 @@ public void createWorkflowFromQueued(QueuedWorkflow queuedWorkflow) } catch (MissingObjectException ex) { String message = ex.getMessage(); logger.error( - "Workflow retrieval error while processing " - + queuedWorkflow.getId() - + " from " - + gitInfo.toSummary() - + " : " - + message, + "Workflow retrieval error while processing {} from {} : {}", + queuedWorkflow.getId(), + gitInfo.toSummary(), + message, ex); queuedWorkflow.setCwltoolStatus(CWLToolStatus.ERROR); queuedWorkflow.setMessage("Unable to retrieve a needed Git object: " + message); FileUtils.deleteGitRepository(repo); } catch (Exception ex) { logger.error( - "Unexpected error processing workflow " - + queuedWorkflow.getId() - + " from " - + gitInfo.toSummary() - + " : " - + ex.getMessage(), + "Unexpected error processing workflow {} from {} : {}", + queuedWorkflow.getId(), + gitInfo.toSummary(), + ex.getMessage(), ex); queuedWorkflow.setCwltoolStatus(CWLToolStatus.ERROR); queuedWorkflow.setMessage( diff --git a/src/main/java/org/commonwl/view/cwl/CWLToolStatus.java b/src/main/java/org/commonwl/view/cwl/CWLToolStatus.java index 6eaff38d..10baf706 100644 --- a/src/main/java/org/commonwl/view/cwl/CWLToolStatus.java +++ b/src/main/java/org/commonwl/view/cwl/CWLToolStatus.java @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.commonwl.view.cwl; /** Cwltool run status */ diff --git a/src/main/java/org/commonwl/view/cwl/RDFService.java b/src/main/java/org/commonwl/view/cwl/RDFService.java index 05b320c8..43272e6d 100644 --- a/src/main/java/org/commonwl/view/cwl/RDFService.java +++ b/src/main/java/org/commonwl/view/cwl/RDFService.java @@ -44,17 +44,18 @@ public class RDFService { // Context for SPARQL queries private final String queryCtx = - "PREFIX cwl: \n" - + "PREFIX rdf: \n" - + "PREFIX sld: \n" - + "PREFIX dct: \n" - + "PREFIX doap: \n" - + "PREFIX Workflow: \n" - + "PREFIX DockerRequirement: \n" - + "PREFIX rdfs: \n" - + "PREFIX s: "; + """ + PREFIX cwl: + PREFIX rdf: + PREFIX sld: + PREFIX dct: + PREFIX doap: + PREFIX Workflow: + PREFIX DockerRequirement: + PREFIX rdfs: + PREFIX s: """; - private String rdfService; + private final String rdfService; /** * Create the RDFService with configuration @@ -129,8 +130,10 @@ public boolean graphExists(String graphName) { public boolean ontPropertyExists(String ontUri) { ParameterizedSparqlString graphQuery = new ParameterizedSparqlString(); graphQuery.setCommandText( - "PREFIX rdfs: " - + "ASK WHERE { GRAPH ?graphName { ?ont rdfs:label ?label } }"); + """ + PREFIX rdfs: + ASK WHERE { GRAPH ?graphName { ?ont rdfs:label ?label } } + """); graphQuery.setIri("ont", ontUri); graphQuery.setIri("graphName", rdfService + "ontologies"); Query query = QueryFactory.create(graphQuery.toString()); @@ -149,14 +152,25 @@ public ResultSet getLabelAndDoc(String workflowURI) { ParameterizedSparqlString labelQuery = new ParameterizedSparqlString(); labelQuery.setCommandText( queryCtx - + "SELECT ?label ?doc\n" - + "WHERE {\n" - + " GRAPH ?wf {" - + " ?wf rdf:type ?type .\n" - + " OPTIONAL { ?wf sld:label|rdfs:label ?label }\n" - + " OPTIONAL { ?wf sld:doc|rdfs:comment ?doc }\n" - + " }" - + "}"); + + """ + PREFIX cwl: + PREFIX rdf: + PREFIX sld: + PREFIX dct: + PREFIX doap: + PREFIX Workflow: + PREFIX DockerRequirement: + PREFIX rdfs: + PREFIX s: + SELECT ?label ?doc + WHERE { + GRAPH ?wf { + ?wf rdf:type ?type . + OPTIONAL { ?wf sld:label|rdfs:label ?label } + OPTIONAL { ?wf sld:doc|rdfs:comment ?doc } + } + } + """); labelQuery.setIri("wf", workflowURI); return runQuery(labelQuery); } @@ -171,13 +185,15 @@ public ResultSet getLabelAndDoc(String workflowURI) { public String getOntLabel(String ontologyURI) { ParameterizedSparqlString labelQuery = new ParameterizedSparqlString(); labelQuery.setCommandText( - "PREFIX rdfs: " - + "SELECT ?label\n" - + "WHERE {\n" - + " GRAPH ?graphName {\n" - + " ?ont rdfs:label ?label\n" - + " }\n" - + "}\n"); + """ + PREFIX rdfs: \ + SELECT ?label + WHERE { + GRAPH ?graphName { + ?ont rdfs:label ?label + } + } + """); labelQuery.setIri("ont", ontologyURI); labelQuery.setIri("graphName", rdfService + "ontologies"); ResultSet result = runQuery(labelQuery); @@ -197,31 +213,42 @@ public ResultSet getInputs(String workflowURI) { ParameterizedSparqlString inputsQuery = new ParameterizedSparqlString(); inputsQuery.setCommandText( queryCtx - + "SELECT ?name ?type ?items ?null ?format ?label ?doc\n" - + "WHERE {\n" - + " GRAPH ?wf {" - + " ?wf rdf:type cwl:Workflow .\n" - + " ?wf cwl:inputs ?name .\n" - + " OPTIONAL {\n" - + " { \n" - + " ?name sld:type ?type\n" - + " FILTER(?type != sld:null) \n" - + " FILTER (!isBlank(?type))\n" - + " } UNION { \n" - + " ?name sld:type ?arraytype .\n" - + " ?arraytype sld:type ?type .\n" - + " ?arraytype sld:items ?items \n" - + " }\n" - + " }\n" - + " OPTIONAL { \n" - + " ?name sld:type ?null\n" - + " FILTER(?null = sld:null)\n" - + " }\n" - + " OPTIONAL { ?name cwl:format ?format }\n" - + " OPTIONAL { ?name sld:label|rdfs:label ?label }\n" - + " OPTIONAL { ?name sld:doc|rdfs:comment ?doc }" - + " }" - + "}"); + + """ + PREFIX cwl: + PREFIX rdf: + PREFIX sld: + PREFIX dct: + PREFIX doap: + PREFIX Workflow: + PREFIX DockerRequirement: + PREFIX rdfs: + PREFIX s: + SELECT ?name ?type ?items ?null ?format ?label ?doc + WHERE { + GRAPH ?wf { + ?wf rdf:type cwl:Workflow . + ?wf cwl:inputs ?name . + OPTIONAL { + {\s + ?name sld:type ?type + FILTER(?type != sld:null)\s + FILTER (!isBlank(?type)) + } UNION {\s + ?name sld:type ?arraytype . + ?arraytype sld:type ?type . + ?arraytype sld:items ?items\s + } + } + OPTIONAL {\s + ?name sld:type ?null + FILTER(?null = sld:null) + } + OPTIONAL { ?name cwl:format ?format } + OPTIONAL { ?name sld:label|rdfs:label ?label } + OPTIONAL { ?name sld:doc|rdfs:comment ?doc } + } + } + """); inputsQuery.setIri("wf", workflowURI); return runQuery(inputsQuery); } @@ -236,31 +263,42 @@ public ResultSet getOutputs(String workflowURI) { ParameterizedSparqlString outputsQuery = new ParameterizedSparqlString(); outputsQuery.setCommandText( queryCtx - + "SELECT ?name ?type ?items ?null ?format ?label ?doc\n" - + "WHERE {\n" - + " GRAPH ?wf {" - + " ?wf rdf:type cwl:Workflow .\n" - + " ?wf cwl:outputs ?name .\n" - + " OPTIONAL {\n" - + " { \n" - + " ?name sld:type ?type\n" - + " FILTER(?type != sld:null) \n" - + " FILTER (!isBlank(?type))\n" - + " } UNION { \n" - + " ?name sld:type ?arraytype .\n" - + " ?arraytype sld:type ?type .\n" - + " ?arraytype sld:items ?items \n" - + " }\n" - + " }\n" - + " OPTIONAL { \n" - + " ?name sld:type ?null\n" - + " FILTER(?null = sld:null)\n" - + " }\n" - + " OPTIONAL { ?name cwl:format ?format }\n" - + " OPTIONAL { ?name sld:label|rdfs:label ?label }\n" - + " OPTIONAL { ?name sld:doc|rdfs:comment ?doc }" - + " }" - + "}"); + + """ + PREFIX cwl: + PREFIX rdf: + PREFIX sld: + PREFIX dct: + PREFIX doap: + PREFIX Workflow: + PREFIX DockerRequirement: + PREFIX rdfs: + PREFIX s: + SELECT ?name ?type ?items ?null ?format ?label ?doc + WHERE { + GRAPH ?wf { + ?wf rdf:type cwl:Workflow . + ?wf cwl:outputs ?name . + OPTIONAL { + {\s + ?name sld:type ?type + FILTER(?type != sld:null)\s + FILTER (!isBlank(?type)) + } UNION {\s + ?name sld:type ?arraytype . + ?arraytype sld:type ?type . + ?arraytype sld:items ?items\s + } + } + OPTIONAL {\s + ?name sld:type ?null + FILTER(?null = sld:null) + } + OPTIONAL { ?name cwl:format ?format } + OPTIONAL { ?name sld:label|rdfs:label ?label } + OPTIONAL { ?name sld:doc|rdfs:comment ?doc } + } + } + """); outputsQuery.setIri("wf", workflowURI); return runQuery(outputsQuery); } @@ -275,20 +313,31 @@ public ResultSet getSteps(String workflowURI) { ParameterizedSparqlString stepQuery = new ParameterizedSparqlString(); stepQuery.setCommandText( queryCtx - + "SELECT ?step ?run ?runtype ?label ?doc ?stepinput ?default ?src\n" - + "WHERE {\n" - + " GRAPH ?wf {" - + " ?wf Workflow:steps ?step .\n" - + " ?step cwl:run ?run .\n" - + " ?run rdf:type ?runtype .\n" - + " OPTIONAL { \n" - + " ?step cwl:in ?stepinput .\n" - + " { ?stepinput cwl:source ?src } UNION { ?stepinput cwl:default ?default }\n" - + " }\n" - + " OPTIONAL { ?run sld:label|rdfs:label ?label }\n" - + " OPTIONAL { ?run sld:doc|rdfs:comment ?doc }\n" - + " }" - + "}"); + + """ + PREFIX cwl: + PREFIX rdf: + PREFIX sld: + PREFIX dct: + PREFIX doap: + PREFIX Workflow: + PREFIX DockerRequirement: + PREFIX rdfs: + PREFIX s: + SELECT ?step ?run ?runtype ?label ?doc ?stepinput ?default ?src + WHERE { + GRAPH ?wf { + ?wf Workflow:steps ?step . + ?step cwl:run ?run . + ?run rdf:type ?runtype . + OPTIONAL {\s + ?step cwl:in ?stepinput . + { ?stepinput cwl:source ?src } UNION { ?stepinput cwl:default ?default } + } + OPTIONAL { ?run sld:label|rdfs:label ?label } + OPTIONAL { ?run sld:doc|rdfs:comment ?doc } + } + } + """); stepQuery.setIri("wf", workflowURI); return runQuery(stepQuery); } @@ -303,14 +352,25 @@ public ResultSet getStepLinks(String workflowURI) { ParameterizedSparqlString linkQuery = new ParameterizedSparqlString(); linkQuery.setCommandText( queryCtx - + "SELECT ?src ?dest ?default\n" - + "WHERE {\n" - + " GRAPH ?wf {" - + " ?wf Workflow:steps ?step .\n" - + " ?step cwl:in ?dest .\n" - + " { ?dest cwl:source ?src } UNION { ?dest cwl:default ?default }\n" - + " }" - + "}"); + + """ + PREFIX cwl: + PREFIX rdf: + PREFIX sld: + PREFIX dct: + PREFIX doap: + PREFIX Workflow: + PREFIX DockerRequirement: + PREFIX rdfs: + PREFIX s: + SELECT ?src ?dest ?default + WHERE { + GRAPH ?wf { + ?wf Workflow:steps ?step . + ?step cwl:in ?dest . + { ?dest cwl:source ?src } UNION { ?dest cwl:default ?default } + } + } + """); linkQuery.setIri("wf", workflowURI); return runQuery(linkQuery); } @@ -325,14 +385,25 @@ public ResultSet getOutputLinks(String workflowURI) { ParameterizedSparqlString linkQuery = new ParameterizedSparqlString(); linkQuery.setCommandText( queryCtx - + "SELECT ?src ?dest\n" - + "WHERE {\n" - + " GRAPH ?wf {" - + " ?wf rdf:type cwl:Workflow .\n" - + " ?wf cwl:outputs ?dest .\n" - + " ?dest cwl:outputSource ?src\n" - + " }" - + "}"); + + """ + PREFIX cwl: + PREFIX rdf: + PREFIX sld: + PREFIX dct: + PREFIX doap: + PREFIX Workflow: + PREFIX DockerRequirement: + PREFIX rdfs: + PREFIX s: + SELECT ?src ?dest + WHERE { + GRAPH ?wf { + ?wf rdf:type cwl:Workflow . + ?wf cwl:outputs ?dest . + ?dest cwl:outputSource ?src + } + } + """); linkQuery.setIri("wf", workflowURI); return runQuery(linkQuery); } @@ -347,15 +418,26 @@ public ResultSet getDockerLink(String workflowURI) { ParameterizedSparqlString dockerQuery = new ParameterizedSparqlString(); dockerQuery.setCommandText( queryCtx - + "SELECT ?docker ?pull\n" - + "WHERE {\n" - + " GRAPH ?wf {" - + " ?wf rdf:type cwl:Workflow .\n" - + " { ?wf cwl:requirements ?docker } UNION { ?wf cwl:hints ?docker} .\n" - + " ?docker rdf:type cwl:DockerRequirement\n" - + " OPTIONAL { ?docker DockerRequirement:dockerPull ?pull }\n" - + " }" - + "}"); + + """ + PREFIX cwl: + PREFIX rdf: + PREFIX sld: + PREFIX dct: + PREFIX doap: + PREFIX Workflow: + PREFIX DockerRequirement: + PREFIX rdfs: + PREFIX s: + SELECT ?docker ?pull + WHERE { + GRAPH ?wf { + ?wf rdf:type cwl:Workflow . + { ?wf cwl:requirements ?docker } UNION { ?wf cwl:hints ?docker} . + ?docker rdf:type cwl:DockerRequirement + OPTIONAL { ?docker DockerRequirement:dockerPull ?pull } + } + } + """); dockerQuery.setIri("wf", workflowURI); return runQuery(dockerQuery); } @@ -371,27 +453,38 @@ public ResultSet getAuthors(String path, String fileUri) { ParameterizedSparqlString linkQuery = new ParameterizedSparqlString(); linkQuery.setCommandText( queryCtx - + "SELECT ?email ?name ?orcid\n" - + "WHERE {\n" - + " GRAPH ?graphName {" - + " ?file s:author|s:contributor|s:creator ?author .\n" - + " {\n" - + " ?creator rdf:type s:Person .\n" - + " OPTIONAL { ?author s:email ?email }\n" - + " OPTIONAL { ?author s:name ?name }\n" - + " OPTIONAL { ?author s:id|s:sameAs ?orcid }\n" - + " } UNION {\n" - + " ?author rdf:type s:Organization .\n" - + " ?author s:department* ?dept .\n" - + " ?dept s:member ?member\n" - + " OPTIONAL { ?member s:email ?email }\n" - + " OPTIONAL { ?member s:name ?name }\n" - + " OPTIONAL { ?member s:id|s:sameAs ?orcid }\n" - + " }\n" - + " FILTER(regex(str(?orcid), \"^https?://orcid.org/\" ))\n" - + " FILTER(regex(str(?file), ?wfFilter, \"i\" ))\n" - + " }" - + "}"); + + """ + PREFIX cwl: + PREFIX rdf: + PREFIX sld: + PREFIX dct: + PREFIX doap: + PREFIX Workflow: + PREFIX DockerRequirement: + PREFIX rdfs: + PREFIX s: + SELECT ?email ?name ?orcid + WHERE { + GRAPH ?graphName { + ?file s:author|s:contributor|s:creator ?author . + { + ?creator rdf:type s:Person . + OPTIONAL { ?author s:email ?email } + OPTIONAL { ?author s:name ?name } + OPTIONAL { ?author s:id|s:sameAs ?orcid } + } UNION { + ?author rdf:type s:Organization . + ?author s:department* ?dept . + ?dept s:member ?member + OPTIONAL { ?member s:email ?email } + OPTIONAL { ?member s:name ?name } + OPTIONAL { ?member s:id|s:sameAs ?orcid } + } + FILTER(regex(str(?orcid), "^https?://orcid.org/" )) + FILTER(regex(str(?file), ?wfFilter, "i" )) + } + } + """); linkQuery.setLiteral("wfFilter", path + "$"); linkQuery.setIri("graphName", fileUri); return runQuery(linkQuery); @@ -436,16 +529,12 @@ public String formatDefault(String defaultVal) { * @return CWL process the string refers to */ public CWLProcess strToRuntype(String runtype) { - switch (runtype) { - case "https://w3id.org/cwl/cwl#Workflow": - return CWLProcess.WORKFLOW; - case "https://w3id.org/cwl/cwl#CommandLineTool": - return CWLProcess.COMMANDLINETOOL; - case "https://w3id.org/cwl/cwl#ExpressionTool": - return CWLProcess.EXPRESSIONTOOL; - default: - return null; - } + return switch (runtype) { + case "https://w3id.org/cwl/cwl#Workflow" -> CWLProcess.WORKFLOW; + case "https://w3id.org/cwl/cwl#CommandLineTool" -> CWLProcess.COMMANDLINETOOL; + case "https://w3id.org/cwl/cwl#ExpressionTool" -> CWLProcess.EXPRESSIONTOOL; + default -> null; + }; } /** @@ -480,15 +569,26 @@ public ResultSet getLicense(String workflowURI) { ParameterizedSparqlString licenseQuery = new ParameterizedSparqlString(); licenseQuery.setCommandText( queryCtx - + "SELECT ?license \n" - + "WHERE {\n" - + " GRAPH ?wf {" - + " ?wf rdf:type cwl:Workflow .\n" - + " { ?wf s:license ?license } \n" - + "UNION { ?wf doap:license ?license } \n" - + "UNION { ?wf dct:license ?license } \n" - + " }" - + "}"); + + """ + PREFIX cwl: + PREFIX rdf: + PREFIX sld: + PREFIX dct: + PREFIX doap: + PREFIX Workflow: + PREFIX DockerRequirement: + PREFIX rdfs: + PREFIX s: + SELECT ?license\s + WHERE { + GRAPH ?wf { + ?wf rdf:type cwl:Workflow . + { ?wf s:license ?license }\s + UNION { ?wf doap:license ?license }\s + UNION { ?wf dct:license ?license }\s + } + } + """); licenseQuery.setIri("wf", workflowURI); return runQuery(licenseQuery); } diff --git a/src/main/java/org/commonwl/view/git/GitConfig.java b/src/main/java/org/commonwl/view/git/GitConfig.java index 71637c42..8fb71b1c 100644 --- a/src/main/java/org/commonwl/view/git/GitConfig.java +++ b/src/main/java/org/commonwl/view/git/GitConfig.java @@ -1,7 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.commonwl.view.git; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -11,6 +28,8 @@ import org.springframework.context.annotation.Scope; import org.springframework.web.client.RestTemplate; import org.springframework.web.context.WebApplicationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ObjectNode; @Configuration public class GitConfig { @@ -27,10 +46,10 @@ public Map licenseVocab() { } Map licenseMap = new HashMap<>(); for (JsonNode license : jsonLicenses.withArray("licenses")) { - String spdxURL = LicenseUtils.SPDX_LICENSES_PREFIX + license.get("licenseId").asText(); + String spdxURL = LicenseUtils.SPDX_LICENSES_PREFIX + license.get("licenseId").asString(); for (JsonNode alias : license.withArray("seeAlso")) { licenseMap.put( - StringUtils.stripEnd(alias.asText().replace("http://", "https://"), "/"), spdxURL); + StringUtils.stripEnd(alias.asString().replace("http://", "https://"), "/"), spdxURL); } } return licenseMap; diff --git a/src/main/java/org/commonwl/view/git/GitDetails.java b/src/main/java/org/commonwl/view/git/GitDetails.java index fe0cbd3e..fa8e9abe 100644 --- a/src/main/java/org/commonwl/view/git/GitDetails.java +++ b/src/main/java/org/commonwl/view/git/GitDetails.java @@ -21,8 +21,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.annotation.JsonProperty; import java.io.IOException; import java.io.Serializable; import java.net.URI; @@ -32,6 +31,8 @@ import org.commonwl.view.util.LicenseUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; /** Represents all the parameters necessary to access a file/directory with Git */ @JsonIgnoreProperties( @@ -46,8 +47,13 @@ public class GitDetails implements Serializable { private String path; private String packedId; - @JsonCreator - public GitDetails(String repoUrl, String branch, String path) { + public GitDetails() {} + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GitDetails( + @JsonProperty("repoUrl") String repoUrl, + @JsonProperty("branch") String branch, + @JsonProperty("path") String path) { this.repoUrl = repoUrl; // Default to the master branch @@ -66,6 +72,7 @@ public String getRepoUrl() { return repoUrl; } + @SuppressWarnings("unused") public void setRepoUrl(String repoUrl) { this.repoUrl = repoUrl; } @@ -75,7 +82,11 @@ public String getBranch() { } public void setBranch(String branch) { - this.branch = branch; + if (branch == null || branch.isEmpty()) { + this.branch = "master"; + } else { + this.branch = branch; + } } public String getPackedId() { @@ -101,7 +112,7 @@ public void setPath(String path) { } /** - * Get the type of a repository URL this object refers to + * Get the type of repository URL this object refers to * * @return The type for the URL */ @@ -169,7 +180,10 @@ public String getUrl() { */ public String getInternalUrl(String branchOverride) { String packedPart = packedId == null ? "" : "%23" + packedId; + + // TODO: Maybe this needs encoding to support commas? See issue #782 String pathPart = path.equals("/") ? "" : "/" + path; + return switch (getType()) { case GITHUB, GITLAB -> "/workflows/" @@ -178,6 +192,7 @@ public String getInternalUrl(String branchOverride) { + branchOverride + pathPart + packedPart; + default -> "/workflows/" + normaliseUrl(repoUrl) + "/" + branchOverride + pathPart + packedPart; }; @@ -289,34 +304,31 @@ public String getLicense(Path workTree) throws GitLicenseException { try { String[] command = {"licensee", "detect", "--json", workTree.toString()}; if (logger.isTraceEnabled()) { - logger.trace("Calling " + String.join(" ", command)); + logger.trace("Calling {}", String.join(" ", command)); } Process process = Runtime.getRuntime().exec(command, null); ObjectMapper mapper = new ObjectMapper(); JsonNode jsonLicenses = mapper.readTree(process.getInputStream()); if (logger.isTraceEnabled()) { logger.trace( - "Licensee retrieved the following licenses:\n" + jsonLicenses.toPrettyString()); + "Licensee retrieved the following licenses:\n{}", jsonLicenses.toPrettyString()); } int size = jsonLicenses.withArray("licenses").size(); if (size > 0) { String licenseCandidate = - jsonLicenses.withArray("matched_files").get(0).get("filename").asText(); + jsonLicenses.withArray("matched_files").get(0).get("filename").asString(); String licenseLink = getRawUrl(null, licenseCandidate); if (logger.isWarnEnabled() && size > 1) { logger.warn( - "There are " - + size - + " identified license files in the " - + repoUrl - + " repository. " - + "Taking the first one: " - + licenseLink); + "There are {} identified license files in the {} repository. Taking the first one: {}", + size, + repoUrl, + licenseLink); } - String key = jsonLicenses.withArray("licenses").get(0).get("key").asText(); + String key = jsonLicenses.withArray("licenses").get(0).get("key").asString(); if (!"other".equals(key)) { return LicenseUtils.SPDX_LICENSES_PREFIX - + jsonLicenses.withArray("licenses").get(0).get("spdx_id").asText(); + + jsonLicenses.withArray("licenses").get(0).get("spdx_id").asString(); } else { return licenseLink; } diff --git a/src/main/java/org/commonwl/view/git/GitLicenseException.java b/src/main/java/org/commonwl/view/git/GitLicenseException.java index 2fd7fd17..d00f9a2e 100644 --- a/src/main/java/org/commonwl/view/git/GitLicenseException.java +++ b/src/main/java/org/commonwl/view/git/GitLicenseException.java @@ -1,7 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.commonwl.view.git; import jakarta.validation.ValidationException; +@SuppressWarnings("unused") public class GitLicenseException extends ValidationException { public GitLicenseException(String message) { diff --git a/src/main/java/org/commonwl/view/git/GitSemaphore.java b/src/main/java/org/commonwl/view/git/GitSemaphore.java index ba2186e7..c6eb70dd 100644 --- a/src/main/java/org/commonwl/view/git/GitSemaphore.java +++ b/src/main/java/org/commonwl/view/git/GitSemaphore.java @@ -27,7 +27,7 @@ @Component public class GitSemaphore { - private static Map currentRepos = new HashMap<>(); + private static final Map currentRepos = new HashMap<>(); /** * Note that a thread will be accessing the repository diff --git a/src/main/java/org/commonwl/view/git/GitService.java b/src/main/java/org/commonwl/view/git/GitService.java index 3025168e..fdde6de0 100644 --- a/src/main/java/org/commonwl/view/git/GitService.java +++ b/src/main/java/org/commonwl/view/git/GitService.java @@ -85,7 +85,7 @@ public Git getRepository(GitDetails gitDetails, boolean reuseDir) } } else { // Create a folder and clone repository into it - Files.createDirectory(repoDir); + Files.createDirectories(repoDir); try { repo = cloneRepo(gitDetails.getRepoUrl(), repoDir.toFile()); } catch (CheckoutConflictException ex) { @@ -161,11 +161,11 @@ public Set getAuthors(Git repo, String path) if (author != null) { HashableAgent newAgent = new HashableAgent(); String name = author.getName(); - if (name != null && name.length() > 0) { + if (name != null && !name.isEmpty()) { newAgent.setName(author.getName()); } String email = author.getEmailAddress(); - if (email != null && email.length() > 0) { + if (email != null && !email.isEmpty()) { newAgent.setUri(new URI("mailto:" + author.getEmailAddress())); } fileAuthors.add(newAgent); @@ -216,7 +216,7 @@ protected Git cloneRepo(String repoUrl, File directory) throws GitAPIException { protected File createTempDir() throws IOException { Path repoDir = gitStorage.resolve(String.valueOf(UUID.randomUUID())); - Files.createDirectory(repoDir); + Files.createDirectories(repoDir); return repoDir.toFile(); } } diff --git a/src/main/java/org/commonwl/view/graphviz/DotWriter.java b/src/main/java/org/commonwl/view/graphviz/DotWriter.java index 25fdf705..4d2fd162 100644 --- a/src/main/java/org/commonwl/view/graphviz/DotWriter.java +++ b/src/main/java/org/commonwl/view/graphviz/DotWriter.java @@ -25,8 +25,8 @@ /** Takes an object and creates a DOT graph of it */ public abstract class DotWriter { - protected static final String EOL = System.getProperty("line.separator"); - private Writer writer; + protected static final String EOL = System.lineSeparator(); + private final Writer writer; public DotWriter(Writer writer) { this.writer = writer; diff --git a/src/main/java/org/commonwl/view/graphviz/GraphVizService.java b/src/main/java/org/commonwl/view/graphviz/GraphVizService.java index c434a09d..2e3bc1da 100644 --- a/src/main/java/org/commonwl/view/graphviz/GraphVizService.java +++ b/src/main/java/org/commonwl/view/graphviz/GraphVizService.java @@ -28,6 +28,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -94,7 +95,7 @@ public Path getGraphPath(String fileName, String dot, String format) throws IOEx * * @param workflowID The ID of the workflow used for assuming file locations */ - public void deleteCache(String workflowID) { + public void deleteCache(UUID workflowID) { File graphvizSvg = new File(graphvizStorage + "/" + workflowID + ".svg"); graphvizSvg.delete(); File graphvizPng = new File(graphvizStorage + "/" + workflowID + ".png"); diff --git a/src/main/java/org/commonwl/view/graphviz/ModelDotWriter.java b/src/main/java/org/commonwl/view/graphviz/ModelDotWriter.java index bb5bc480..e523580b 100644 --- a/src/main/java/org/commonwl/view/graphviz/ModelDotWriter.java +++ b/src/main/java/org/commonwl/view/graphviz/ModelDotWriter.java @@ -150,7 +150,7 @@ private void writeSteps(Workflow workflow) throws IOException { // Workaround to force outputs to lowest ranking, see #104 writeLine(""); writeLine(" // Invisible links to force outputs to be at lowest rank"); - if (workflow.getOutputs().size() > 0) { + if (!workflow.getOutputs().isEmpty()) { for (Map.Entry step : workflow.getSteps().entrySet()) { writeLine( " \"" diff --git a/src/main/java/org/commonwl/view/graphviz/RDFDotWriter.java b/src/main/java/org/commonwl/view/graphviz/RDFDotWriter.java index dbf96dc4..ca809d91 100644 --- a/src/main/java/org/commonwl/view/graphviz/RDFDotWriter.java +++ b/src/main/java/org/commonwl/view/graphviz/RDFDotWriter.java @@ -37,8 +37,8 @@ public class RDFDotWriter extends DotWriter { private final IRIFactory iriFactory = IRIFactory.iriImplementation(); - private RDFService rdfService; - private String gitPath; + private final RDFService rdfService; + private final String gitPath; public RDFDotWriter(Writer writer, RDFService rdfService, String gitPath) { super(writer); @@ -164,7 +164,7 @@ private void writeStepLinks(String workflowUri) throws IOException { String sourceID = nodeIDFromUri(stepLink.get("src").toString()); String dest = stepLink.get("dest").toString(); String destID = nodeIDFromUri(dest); - String destInput = dest.substring(dest.replaceAll("#", "/").lastIndexOf("/") + 1); + String destInput = dest.substring(dest.replace("#", "/").lastIndexOf("/") + 1); writeLine(" \"" + sourceID + "\" -> \"" + destID + "\" [label=\"" + destInput + "\"];"); } else if (stepLink.contains("default")) { // Collect default values diff --git a/src/main/java/org/commonwl/view/researchobject/HashableAgent.java b/src/main/java/org/commonwl/view/researchobject/HashableAgent.java index a45f9f81..e82b6669 100644 --- a/src/main/java/org/commonwl/view/researchobject/HashableAgent.java +++ b/src/main/java/org/commonwl/view/researchobject/HashableAgent.java @@ -20,6 +20,7 @@ package org.commonwl.view.researchobject; import java.net.URI; +import java.util.Objects; import org.apache.taverna.robundle.manifest.Agent; /** An implementation of Agent with added HashCode and Equals methods for use in sets */ @@ -70,7 +71,7 @@ public void setUri(URI uri) { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass() || super.getClass() != o.getClass()) return false; + if (o == null || getClass() != o.getClass()) return false; HashableAgent that = (HashableAgent) o; @@ -81,9 +82,9 @@ public boolean equals(Object o) { if (orcid == null && uri != null && uri.equals(that.uri)) return true; // Default to checking all parameters - if (orcid != null ? !orcid.equals(that.orcid) : that.orcid != null) return false; - if (name != null ? !name.equals(that.name) : that.name != null) return false; - return uri != null ? uri.equals(that.uri) : that.uri == null; + if (!Objects.equals(orcid, that.orcid)) return false; + if (!Objects.equals(name, that.name)) return false; + return Objects.equals(uri, that.uri); } /** diff --git a/src/main/java/org/commonwl/view/researchobject/ROBundleFactory.java b/src/main/java/org/commonwl/view/researchobject/ROBundleFactory.java index 83745626..a19a7f55 100644 --- a/src/main/java/org/commonwl/view/researchobject/ROBundleFactory.java +++ b/src/main/java/org/commonwl/view/researchobject/ROBundleFactory.java @@ -62,8 +62,8 @@ public ROBundleFactory(ROBundleService roBundleService, WorkflowRepository workf * @throws IOException Any API errors which may have occurred */ @Async - public void createWorkflowRO(Workflow workflow) throws IOException, InterruptedException { - logger.info("Creating Research Object Bundle for workflow " + workflow.getID()); + public void createWorkflowRO(Workflow workflow) throws IOException { + logger.info("Creating Research Object Bundle for workflow " + workflow.getId()); // Get the whole containing folder, not just the workflow itself GitDetails githubInfo = workflow.getRetrievedFrom(); @@ -91,6 +91,6 @@ public void createWorkflowRO(Workflow workflow) throws IOException, InterruptedE // Add RO Bundle to associated workflow model workflow.setRoBundlePath(bundleLocation.toString()); workflowRepository.save(workflow); - logger.info("Finished saving Research Object Bundle for workflow " + workflow.getID()); + logger.info("Finished saving Research Object Bundle for workflow " + workflow.getId()); } } diff --git a/src/main/java/org/commonwl/view/researchobject/ROBundleService.java b/src/main/java/org/commonwl/view/researchobject/ROBundleService.java index 236c0dd9..ef155b94 100644 --- a/src/main/java/org/commonwl/view/researchobject/ROBundleService.java +++ b/src/main/java/org/commonwl/view/researchobject/ROBundleService.java @@ -147,7 +147,7 @@ public Bundle createBundle(Workflow workflow, GitDetails gitInfo) throws IOExcep // Make a directory in the RO bundle to store the files Path bundleRoot = bundle.getRoot(); Path bundlePath = bundleRoot.resolve("workflow"); - Files.createDirectory(bundlePath); + Files.createDirectories(bundlePath); // Add the files from the repo to this workflow Set authors = new HashSet<>(); @@ -278,7 +278,7 @@ private void addFilesToBundle( // Create a new folder in the RO for this directory Path newBundlePath = bundlePath.resolve(file.getName()); - Files.createDirectory(newBundlePath); + Files.createDirectories(newBundlePath); // Create git details object for subfolder GitDetails subfolderGitDetails = diff --git a/src/main/java/org/commonwl/view/util/BaseEntity.java b/src/main/java/org/commonwl/view/util/BaseEntity.java index efce9e8f..a337b2de 100644 --- a/src/main/java/org/commonwl/view/util/BaseEntity.java +++ b/src/main/java/org/commonwl/view/util/BaseEntity.java @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.commonwl.view.util; import jakarta.persistence.MappedSuperclass; diff --git a/src/main/java/org/commonwl/view/util/FileUtils.java b/src/main/java/org/commonwl/view/util/FileUtils.java index e76bdfd0..1cf55ecc 100644 --- a/src/main/java/org/commonwl/view/util/FileUtils.java +++ b/src/main/java/org/commonwl/view/util/FileUtils.java @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.commonwl.view.util; import java.io.File; diff --git a/src/main/java/org/commonwl/view/util/LicenseUtils.java b/src/main/java/org/commonwl/view/util/LicenseUtils.java index b0baf787..e73f3aad 100644 --- a/src/main/java/org/commonwl/view/util/LicenseUtils.java +++ b/src/main/java/org/commonwl/view/util/LicenseUtils.java @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.commonwl.view.util; public class LicenseUtils { diff --git a/src/main/java/org/commonwl/view/util/StreamGobbler.java b/src/main/java/org/commonwl/view/util/StreamGobbler.java index 26616ad6..ee445df8 100644 --- a/src/main/java/org/commonwl/view/util/StreamGobbler.java +++ b/src/main/java/org/commonwl/view/util/StreamGobbler.java @@ -23,12 +23,15 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** Background thread to consume stream and collect contents */ public class StreamGobbler extends Thread { - private final String lineSeparator = System.getProperty("line.separator"); + private static final Logger logger = LoggerFactory.getLogger(StreamGobbler.class); + private final String lineSeparator = System.lineSeparator(); - private InputStream is; + private final InputStream is; private String content = ""; public StreamGobbler(InputStream is) { @@ -36,16 +39,18 @@ public StreamGobbler(InputStream is) { } public void run() { - try { - InputStreamReader isr = new InputStreamReader(is); - BufferedReader br = new BufferedReader(isr); + final StringBuilder sb = new StringBuilder(); + + try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { String line; while ((line = br.readLine()) != null) { - content += line + lineSeparator; + sb.append(line).append(lineSeparator); } } catch (IOException ex) { - ex.printStackTrace(); + logger.error(ex.getMessage(), ex); } + + content = sb.toString(); } public String getContent() { diff --git a/src/main/java/org/commonwl/view/validation/AbstractGitValidator.java b/src/main/java/org/commonwl/view/validation/AbstractGitValidator.java new file mode 100644 index 00000000..7d881154 --- /dev/null +++ b/src/main/java/org/commonwl/view/validation/AbstractGitValidator.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.commonwl.view.validation; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.commonwl.view.git.GitDetails; +import org.commonwl.view.workflow.WorkflowForm; +import org.springframework.validation.Errors; + +/** + * Abstract implementation of a Git validator. + * + *

Implementations must provide methods to retrieve the host and the base URL. + */ +public abstract class AbstractGitValidator implements GitUrlValidator { + + /** + * Git host (e.g., github.com). + * + * @return Git host + */ + protected abstract String host(); + + /** + * Git repository base URL (e.g., .../.git). + * + * @param owner owner or organisation + * @param repo repository name + * @return a string representation of the Git repository base URL + */ + protected abstract String repoBaseUrl(String owner, String repo); + + @Override + public boolean supports(String url) { + try { + URI uri = URI.create(url); + return host().equalsIgnoreCase(uri.getHost()); + } catch (Exception e) { + return false; + } + } + + @Override + public void validate(WorkflowForm form, Errors errors) {} + + @Override + public GitDetails parse(String url, WorkflowForm form) { + final URI uri = URI.create(url); + + List parts = Arrays.stream(uri.getPath().split("/")).filter(p -> !p.isBlank()).toList(); + + if (parts.size() < 2) return null; + + String owner = parts.get(0); + String repo = parts.get(1); + + String repoUrl = repoBaseUrl(owner, repo); + + String branch = null; + String path = null; + + // Detect branch/path from /tree/ or /blob/ + for (int i = 2; i < parts.size(); i++) { + String p = parts.get(i); + + if ("tree".equals(p) || "blob".equals(p)) { + if (i + 1 < parts.size()) { + branch = parts.get(i + 1); + } + if (i + 2 < parts.size()) { + path = String.join("/", parts.subList(i + 2, parts.size())); + } + break; + } + } + + // Optional GitHub-style fragment fallback (#branch/path) + String fragment = uri.getFragment(); + if (fragment != null && !fragment.isBlank()) { + String[] fragParts = fragment.split("/", 2); + if (branch == null) { + branch = fragParts[0]; + } + if (path == null && fragParts.length > 1) { + path = fragParts[1]; + } + } + + if (form != null) { + if (StringUtils.isNotBlank(form.getBranch())) { + branch = form.getBranch(); + } + if (StringUtils.isNotBlank(form.getPath())) { + path = form.getPath(); + } + } + + return new GitDetails(repoUrl, branch, path); + } +} diff --git a/src/main/java/org/commonwl/view/workflow/WorkflowRepositoryCustom.java b/src/main/java/org/commonwl/view/validation/BitbucketUrlValidator.java similarity index 68% rename from src/main/java/org/commonwl/view/workflow/WorkflowRepositoryCustom.java rename to src/main/java/org/commonwl/view/validation/BitbucketUrlValidator.java index 05fd3c85..4938395f 100644 --- a/src/main/java/org/commonwl/view/workflow/WorkflowRepositoryCustom.java +++ b/src/main/java/org/commonwl/view/validation/BitbucketUrlValidator.java @@ -16,17 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -package org.commonwl.view.workflow; -import org.commonwl.view.git.GitDetails; +package org.commonwl.view.validation; -public interface WorkflowRepositoryCustom { +/** Validate Bitbucket repository URLs. */ +@SuppressWarnings("unused") +public class BitbucketUrlValidator extends AbstractGitValidator { + protected String host() { + return "bitbucket.org"; + } - /** - * Finds a workflow model in the database based on where it was retrieved from - * - * @param retrievedFrom Details of where the workflow is from - * @return The workflow model - */ - Workflow findByRetrievedFrom(GitDetails retrievedFrom); + protected String repoBaseUrl(String owner, String repo) { + return "https://bitbucket.org/" + owner + "/" + repo + ".git"; + } } diff --git a/src/main/java/org/commonwl/view/validation/GenericGitUrlValidator.java b/src/main/java/org/commonwl/view/validation/GenericGitUrlValidator.java new file mode 100644 index 00000000..76b2fda7 --- /dev/null +++ b/src/main/java/org/commonwl/view/validation/GenericGitUrlValidator.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.commonwl.view.validation; + +import org.commonwl.view.git.GitDetails; +import org.commonwl.view.workflow.WorkflowForm; +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; + +/** Validate generic Git repository URLs. */ +public class GenericGitUrlValidator implements GitUrlValidator { + + @Override + public boolean supports(String url) { + return url != null && url.endsWith(".git"); + } + + @Override + public void validate(WorkflowForm form, Errors errors) { + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "branch", "branch.emptyOrWhitespace"); + } + + @Override + public GitDetails parse(String url, WorkflowForm form) { + String branch = "master"; + String path = "/"; + + if (form != null) { + if (form.getBranch() != null && !form.getBranch().isBlank()) { + branch = form.getBranch(); + } + + if (form.getPath() != null && !form.getPath().isBlank()) { + path = form.getPath(); + } + } + + return new GitDetails(url, branch, path); + } +} diff --git a/src/main/java/org/commonwl/view/workflow/QueuedWorkflowRepositoryCustom.java b/src/main/java/org/commonwl/view/validation/GitHubUrlValidator.java similarity index 54% rename from src/main/java/org/commonwl/view/workflow/QueuedWorkflowRepositoryCustom.java rename to src/main/java/org/commonwl/view/validation/GitHubUrlValidator.java index c18bed6c..e20623f1 100644 --- a/src/main/java/org/commonwl/view/workflow/QueuedWorkflowRepositoryCustom.java +++ b/src/main/java/org/commonwl/view/validation/GitHubUrlValidator.java @@ -16,24 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -package org.commonwl.view.workflow; -import org.commonwl.view.git.GitDetails; -import org.springframework.data.repository.query.Param; +package org.commonwl.view.validation; -public interface QueuedWorkflowRepositoryCustom { - /** - * Finds a queued workflow based on where it was retrieved from. - * - * @param retrievedFrom Details of where the queued workflow is from - * @return The queued workflow - */ - QueuedWorkflow findByRetrievedFrom(@Param("retrievedFrom") GitDetails retrievedFrom); +/** Validate GitHub repository URLs. */ +public class GitHubUrlValidator extends AbstractGitValidator { - /** - * Deletes a queued workflow based on where it was retrieved from. - * - * @param retrievedFrom Details of where the queued workflow is from - */ - void deleteByTempRepresentation_RetrievedFrom(GitDetails retrievedFrom); + @Override + protected String host() { + return "github.com"; + } + + @Override + protected String repoBaseUrl(String owner, String repo) { + return "https://github.com/" + owner + "/" + repo + ".git"; + } } diff --git a/src/main/java/org/commonwl/view/validation/GitLabUrlValidator.java b/src/main/java/org/commonwl/view/validation/GitLabUrlValidator.java new file mode 100644 index 00000000..1bf28dc6 --- /dev/null +++ b/src/main/java/org/commonwl/view/validation/GitLabUrlValidator.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.commonwl.view.validation; + +/** Validate GitLab repository URLs. */ +public class GitLabUrlValidator extends AbstractGitValidator { + + @Override + protected String host() { + return "gitlab.com"; + } + + @Override + protected String repoBaseUrl(String owner, String repo) { + return "https://gitlab.com/" + owner + "/" + repo + ".git"; + } +} diff --git a/src/main/java/org/commonwl/view/validation/GitUrlValidator.java b/src/main/java/org/commonwl/view/validation/GitUrlValidator.java new file mode 100644 index 00000000..e5f296e1 --- /dev/null +++ b/src/main/java/org/commonwl/view/validation/GitUrlValidator.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.commonwl.view.validation; + +import org.commonwl.view.git.GitDetails; +import org.commonwl.view.workflow.WorkflowForm; +import org.springframework.validation.Errors; + +/** Validate Git URLs. */ +public interface GitUrlValidator { + /** + * Checks if the URL is supported or not. + * + * @param url URL + * @return {@code true} if the URL is one of the supported Git flavours, {@code false} otherwise + */ + boolean supports(String url); + + /** + * Validate the form. + * + * @param form The web form + * @param errors The errors object + */ + void validate(WorkflowForm form, Errors errors); + + /** + * Parse the Git URL returning a {@code GitDetails} object. + * + * @param url Git URL + * @param form Workflow form + * @return {@code GitDetails} object + */ + GitDetails parse(String url, WorkflowForm form); +} diff --git a/src/main/java/org/commonwl/view/workflow/MultipleWorkflowsException.java b/src/main/java/org/commonwl/view/workflow/MultipleWorkflowsException.java index 5c2cdcc3..395de0a0 100644 --- a/src/main/java/org/commonwl/view/workflow/MultipleWorkflowsException.java +++ b/src/main/java/org/commonwl/view/workflow/MultipleWorkflowsException.java @@ -55,11 +55,11 @@ public String getRawPermalink() { /** * Generate a text/uri-list of potential representations/redirects * - * @see https://www.iana.org/assignments/media-types/text/uri-list + * @see ... */ @Override public String toString() { - StringBuffer sb = new StringBuffer(); + final StringBuilder sb = new StringBuilder(); sb.append("## Multiple workflow representations found"); sb.append(CRLF); sb.append("# "); diff --git a/src/main/java/org/commonwl/view/workflow/QueuedWorkflow.java b/src/main/java/org/commonwl/view/workflow/QueuedWorkflow.java index 3297c92c..46c2590b 100644 --- a/src/main/java/org/commonwl/view/workflow/QueuedWorkflow.java +++ b/src/main/java/org/commonwl/view/workflow/QueuedWorkflow.java @@ -1,10 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.commonwl.view.workflow; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; -import io.hypersistence.utils.hibernate.type.json.JsonType; import jakarta.persistence.Column; -import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -13,10 +30,11 @@ import java.io.Serializable; import java.util.List; import java.util.Objects; +import java.util.UUID; import org.commonwl.view.cwl.CWLToolStatus; import org.commonwl.view.util.BaseEntity; -import org.hibernate.annotations.GenericGenerator; -import org.hibernate.annotations.Type; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; /** A workflow pending completion of cwltool */ @JsonIgnoreProperties(value = {"id", "tempRepresentation", "workflowList"}) @@ -27,28 +45,25 @@ public class QueuedWorkflow extends BaseEntity implements Serializable { // ID for database @Id - @GenericGenerator(name = "uuid2", strategy = "uuid2") - @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "uuid2") - @Column(length = 36, nullable = false, updatable = false) - public String id; + @GeneratedValue(strategy = GenerationType.UUID) + @Column(nullable = false, updatable = false, columnDefinition = "varchar(36)") + @JdbcTypeCode(SqlTypes.VARCHAR) + private UUID id; // Very barebones workflow to build loading thumbnail and overview @Column(columnDefinition = "jsonb") - @Type(value = JsonType.class) - @Convert(disableConversion = true) + @JdbcTypeCode(SqlTypes.JSON) private Workflow tempRepresentation; // List of packed workflows for packed workflows // TODO: Refactor so this is not necessary @Column(columnDefinition = "jsonb") - @Type(value = JsonType.class) - @Convert(disableConversion = true) + @JdbcTypeCode(SqlTypes.JSON) private List workflowList; // Cwltool details @Column(columnDefinition = "jsonb") - @Type(value = JsonType.class) - @Convert(disableConversion = true) + @JdbcTypeCode(SqlTypes.JSON) private CWLToolStatus cwltoolStatus = CWLToolStatus.RUNNING; @Column(columnDefinition = "TEXT") @@ -57,7 +72,7 @@ public class QueuedWorkflow extends BaseEntity implements Serializable { @Column(columnDefinition = "TEXT") private String message; - public String getId() { + public UUID getId() { return id; } @@ -85,6 +100,7 @@ public void setCwltoolVersion(String cwltoolVersion) { this.cwltoolVersion = cwltoolVersion; } + @SuppressWarnings("unused") public String getMessage() { return message; } diff --git a/src/main/java/org/commonwl/view/workflow/QueuedWorkflowRepository.java b/src/main/java/org/commonwl/view/workflow/QueuedWorkflowRepository.java index 486bb810..cedc1769 100644 --- a/src/main/java/org/commonwl/view/workflow/QueuedWorkflowRepository.java +++ b/src/main/java/org/commonwl/view/workflow/QueuedWorkflowRepository.java @@ -2,9 +2,12 @@ import java.util.Date; import java.util.List; +import java.util.UUID; +import org.commonwl.view.git.GitDetails; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -15,8 +18,7 @@ * issues with serialization. */ @Repository -public interface QueuedWorkflowRepository - extends JpaRepository, QueuedWorkflowRepositoryCustom { +public interface QueuedWorkflowRepository extends JpaRepository { /** * Deletes all queued workflows with date retrieved on older or equal to the Date argument passed. @@ -27,7 +29,8 @@ public interface QueuedWorkflowRepository @Transactional @Modifying @Query( - value = "DELETE FROM queued_workflow q WHERE q.temp_representation ->> 'retrievedOn' <= ?1", + value = + "DELETE FROM queued_workflow q WHERE (q.temp_representation ->> 'retrievedOn')::timestamp <= ?1", nativeQuery = true) Integer deleteByTempRepresentation_RetrievedOnLessThanEqual(Date retrievedOn); @@ -40,7 +43,39 @@ public interface QueuedWorkflowRepository */ @Query( value = - "SELECT q.* FROM queued_workflow q WHERE q.temp_representation ->> 'retrievedOn' <= ?1", + "SELECT q.* FROM queued_workflow q WHERE (q.temp_representation ->> 'retrievedOn')::timestamp <= ?1", nativeQuery = true) List findByTempRepresentation_RetrievedOnLessThanEqual(Date retrievedOn); + + /** + * Finds a queued workflow based on where it was retrieved from. + * + * @param retrievedFrom Details of where the queued workflow is from + * @return The queued workflow + */ + @Query( + value = + """ + SELECT q.* + FROM queued_workflow q + WHERE q.temp_representation -> 'retrievedFrom' = :retrievedFrom + """, + nativeQuery = true) + QueuedWorkflow findByRetrievedFrom(@Param("retrievedFrom") GitDetails retrievedFrom); + + /** + * Deletes a queued workflow based on where it was retrieved from. + * + * @param retrievedFrom Details of where the queued workflow is from + */ + @Transactional + @Modifying + @Query( + value = + """ + DELETE FROM queued_workflow q + WHERE q.temp_representation -> 'retrievedFrom' = :retrievedFrom + """, + nativeQuery = true) + void deleteByRetrievedFrom(@Param("retrievedFrom") GitDetails retrievedFrom); } diff --git a/src/main/java/org/commonwl/view/workflow/QueuedWorkflowRepositoryImpl.java b/src/main/java/org/commonwl/view/workflow/QueuedWorkflowRepositoryImpl.java deleted file mode 100644 index aca49113..00000000 --- a/src/main/java/org/commonwl/view/workflow/QueuedWorkflowRepositoryImpl.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.commonwl.view.workflow; - -import io.hypersistence.utils.hibernate.type.json.JsonType; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.persistence.PersistenceContextType; -import org.commonwl.view.git.GitDetails; -import org.hibernate.query.Query; -import org.springframework.transaction.annotation.Transactional; - -public class QueuedWorkflowRepositoryImpl implements QueuedWorkflowRepositoryCustom { - - private static final String QUERY_FIND_BY_RETRIEVED_FROM = - "SELECT q.* FROM queued_workflow q WHERE q.temp_representation -> 'retrievedFrom' = :retrievedFrom"; - - private static final String QUERY_DELETE_BY_RETRIEVED_FROM = - "DELETE FROM queued_workflow q WHERE q.temp_representation -> 'retrievedFrom' = :retrievedFrom"; - - @PersistenceContext(type = PersistenceContextType.EXTENDED) - EntityManager entityManager; - - @Override - public QueuedWorkflow findByRetrievedFrom(GitDetails retrievedFrom) { - // N.B. The migration from Spring Boot 2 to 3 got blocked for a while as - // we couldn't figure out a way to get the mapping of types to work. - // We always ended up the error about the invalid operator for the - // jsonb = bytea types. Found this comment from the author of the - // mapping library we use, with what was the fix for our issue (ran - // out of ideas, so started testing everything found online): - // Use `new JsonType(GitDetails.class)`. That finally solved it. - // Ref: https://github.com/common-workflow-language/cwlviewer/pull/568 - final Query query = - entityManager - .createNativeQuery(QUERY_FIND_BY_RETRIEVED_FROM, QueuedWorkflow.class) - .unwrap(Query.class); - - if (query == null) { - return null; - } - - query.setParameter("retrievedFrom", retrievedFrom, new JsonType(GitDetails.class)); - return (QueuedWorkflow) query.uniqueResult(); - } - - @Transactional - @Override - public void deleteByTempRepresentation_RetrievedFrom(GitDetails retrievedFrom) { - final Query query = - entityManager - .createNativeQuery(QUERY_DELETE_BY_RETRIEVED_FROM, QueuedWorkflow.class) - .unwrap(Query.class); - - if (query != null) { - query.setParameter("retrievedFrom", retrievedFrom, new JsonType(GitDetails.class)); - query.executeUpdate(); - } - } -} diff --git a/src/main/java/org/commonwl/view/workflow/Workflow.java b/src/main/java/org/commonwl/view/workflow/Workflow.java index 29d4868d..445b4b1d 100644 --- a/src/main/java/org/commonwl/view/workflow/Workflow.java +++ b/src/main/java/org/commonwl/view/workflow/Workflow.java @@ -23,9 +23,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.github.tbouron.SpdxLicense; -import io.hypersistence.utils.hibernate.type.json.JsonType; import jakarta.persistence.Column; -import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -36,6 +34,7 @@ import java.util.Date; import java.util.Map; import java.util.Objects; +import java.util.UUID; import org.commonwl.view.WebConfig; import org.commonwl.view.WebConfig.Format; import org.commonwl.view.cwl.CWLElement; @@ -43,8 +42,8 @@ import org.commonwl.view.git.GitDetails; import org.commonwl.view.util.BaseEntity; import org.commonwl.view.util.LicenseUtils; -import org.hibernate.annotations.GenericGenerator; -import org.hibernate.annotations.Type; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; import org.springframework.format.annotation.DateTimeFormat; /** Representation of a workflow */ @@ -63,15 +62,14 @@ public class Workflow extends BaseEntity implements Serializable { // ID for database @Id - @GenericGenerator(name = "uuid2", strategy = "uuid2") - @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "uuid2") - @Column(length = 36, nullable = false, updatable = false) - public String id; + @GeneratedValue(strategy = GenerationType.UUID) + @Column(nullable = false, updatable = false, columnDefinition = "varchar(36)") + @JdbcTypeCode(SqlTypes.VARCHAR) + private UUID id; // Metadata @Column(columnDefinition = "jsonb") - @Type(value = JsonType.class) - @Convert(disableConversion = true) + @JdbcTypeCode(SqlTypes.JSON) private GitDetails retrievedFrom; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss z") @@ -95,18 +93,15 @@ public class Workflow extends BaseEntity implements Serializable { private String doc; @Column(columnDefinition = "jsonb") - @Type(value = JsonType.class) - @Convert(disableConversion = true) + @JdbcTypeCode(SqlTypes.JSON) private Map inputs; @Column(columnDefinition = "jsonb") - @Type(value = JsonType.class) - @Convert(disableConversion = true) + @JdbcTypeCode(SqlTypes.JSON) private Map outputs; @Column(columnDefinition = "jsonb") - @Type(value = JsonType.class) - @Convert(disableConversion = true) + @JdbcTypeCode(SqlTypes.JSON) private Map steps; // Currently, only DockerRequirement is parsed for this @@ -155,11 +150,11 @@ public Workflow() { this(null, null, null, null, null, null, null); } - public String getID() { + public UUID getId() { return id; } - public void setId(String id) { + public void setId(UUID id) { this.id = id; } @@ -167,6 +162,7 @@ public String getLabel() { return label; } + @SuppressWarnings("unused") public void setLabel(String label) { this.label = label; } @@ -175,6 +171,7 @@ public String getDoc() { return doc; } + @SuppressWarnings("unused") public void setDoc(String doc) { this.doc = doc; } @@ -183,6 +180,7 @@ public Map getInputs() { return inputs; } + @SuppressWarnings("unused") public void setInputs(Map inputs) { this.inputs = inputs; } @@ -191,6 +189,7 @@ public Map getOutputs() { return outputs; } + @SuppressWarnings("unused") public void setOutputs(Map outputs) { this.outputs = outputs; } @@ -199,6 +198,7 @@ public Map getSteps() { return steps; } + @SuppressWarnings("unused") public void setSteps(Map steps) { this.steps = steps; } @@ -239,6 +239,7 @@ public String getDockerLink() { return dockerLink; } + @SuppressWarnings("unused") public void setDockerLink(String dockerLink) { this.dockerLink = dockerLink; } @@ -274,6 +275,7 @@ public String getVisualisationSvg() { return retrievedFrom.getInternalUrl().replaceFirst("/workflows", "/graph/svg"); } + @SuppressWarnings("unused") public String getRoBundle() { if (roBundlePath != null) { return getRoBundleLink(); @@ -343,6 +345,7 @@ public String getIdentifier() { + packedPart; } + @SuppressWarnings("unused") public boolean isPacked() { return retrievedFrom.getPackedId() != null; } @@ -351,6 +354,7 @@ public String getLicenseLink() { return licenseLink; } + @SuppressWarnings("unused") public void setLicenseLink(String licenseLink) { this.licenseLink = licenseLink; } diff --git a/src/main/java/org/commonwl/view/workflow/WorkflowController.java b/src/main/java/org/commonwl/view/workflow/WorkflowController.java index 2aa9774b..47d4d476 100644 --- a/src/main/java/org/commonwl/view/workflow/WorkflowController.java +++ b/src/main/java/org/commonwl/view/workflow/WorkflowController.java @@ -26,7 +26,6 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; -import java.security.NoSuchAlgorithmException; import java.util.List; import org.apache.commons.lang.StringUtils; import org.commonwl.view.WebConfig; @@ -44,7 +43,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.InputStreamResource; -import org.springframework.core.io.PathResource; import org.springframework.core.io.Resource; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; @@ -52,7 +50,11 @@ import org.springframework.ui.Model; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.support.RedirectAttributes; @@ -146,7 +148,7 @@ public ModelAndView createWorkflow( workflow = result.getTempRepresentation(); } else { if (result.getWorkflowList().size() == 1) { - gitInfo.setPackedId(result.getWorkflowList().get(0).getFileName()); + gitInfo.setPackedId(result.getWorkflowList().getFirst().fileName()); } return new ModelAndView("redirect:" + gitInfo.getInternalUrl()); } @@ -196,10 +198,10 @@ public ModelAndView createWorkflow( "/workflows/{domain}.com/{owner}/{repoName}/blob/{branch}/**" }) public ModelAndView getWorkflow( - @PathVariable("domain") String domain, - @PathVariable("owner") String owner, - @PathVariable("repoName") String repoName, - @PathVariable("branch") String branch, + @PathVariable String domain, + @PathVariable String owner, + @PathVariable String repoName, + @PathVariable String branch, HttpServletRequest request, RedirectAttributes redirectAttrs) { // The wildcard end of the URL is the path @@ -223,7 +225,7 @@ public ModelAndView getWorkflow( @GetMapping(value = "/workflows/*/*.git/{branch}/**") public ModelAndView getWorkflowGeneric( @Value("${applicationURL}") String applicationURL, - @PathVariable("branch") String branch, + @PathVariable String branch, HttpServletRequest request, RedirectAttributes redirectAttrs) { String path = @@ -248,10 +250,10 @@ public ModelAndView getWorkflowGeneric( produces = {"application/vnd.wf4ever.robundle+zip", "application/zip"}) @ResponseBody public Resource getROBundle( - @PathVariable("domain") String domain, - @PathVariable("owner") String owner, - @PathVariable("repoName") String repoName, - @PathVariable("branch") String branch, + @PathVariable String domain, + @PathVariable String owner, + @PathVariable String repoName, + @PathVariable String branch, HttpServletRequest request, HttpServletResponse response) { String path = @@ -273,9 +275,7 @@ public Resource getROBundle( produces = "application/vnd.wf4ever.robundle+zip") @ResponseBody public Resource getROBundleGeneric( - @PathVariable("branch") String branch, - HttpServletRequest request, - HttpServletResponse response) { + @PathVariable String branch, HttpServletRequest request, HttpServletResponse response) { String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); GitDetails gitDetails = getGitDetails(10, path, branch); @@ -300,10 +300,10 @@ public Resource getROBundleGeneric( produces = "image/svg+xml") @ResponseBody public Resource downloadGraphSvg( - @PathVariable("domain") String domain, - @PathVariable("owner") String owner, - @PathVariable("repoName") String repoName, - @PathVariable("branch") String branch, + @PathVariable String domain, + @PathVariable String owner, + @PathVariable String repoName, + @PathVariable String branch, HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -323,9 +323,7 @@ public Resource downloadGraphSvg( @GetMapping(value = "/graph/svg/*/*/*.git/{branch}/**", produces = "image/svg+xml") @ResponseBody public Resource downloadGraphSvgGeneric( - @PathVariable("branch") String branch, - HttpServletRequest request, - HttpServletResponse response) + @PathVariable String branch, HttpServletRequest request, HttpServletResponse response) throws IOException { String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); @@ -350,10 +348,10 @@ public Resource downloadGraphSvgGeneric( produces = "image/png") @ResponseBody public Resource downloadGraphPng( - @PathVariable("domain") String domain, - @PathVariable("owner") String owner, - @PathVariable("repoName") String repoName, - @PathVariable("branch") String branch, + @PathVariable String domain, + @PathVariable String owner, + @PathVariable String repoName, + @PathVariable String branch, HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -373,9 +371,7 @@ public Resource downloadGraphPng( @GetMapping(value = "/graph/png/*/*/*.git/{branch}/**", produces = "image/png") @ResponseBody public Resource downloadGraphPngGeneric( - @PathVariable("branch") String branch, - HttpServletRequest request, - HttpServletResponse response) + @PathVariable String branch, HttpServletRequest request, HttpServletResponse response) throws IOException { String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); @@ -400,10 +396,10 @@ public Resource downloadGraphPngGeneric( produces = "text/vnd.graphviz") @ResponseBody public Resource downloadGraphDot( - @PathVariable("domain") String domain, - @PathVariable("owner") String owner, - @PathVariable("repoName") String repoName, - @PathVariable("branch") String branch, + @PathVariable String domain, + @PathVariable String owner, + @PathVariable String repoName, + @PathVariable String branch, HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -423,9 +419,7 @@ public Resource downloadGraphDot( @GetMapping(value = "/graph/xdot/*/*/*.git/{branch}/**", produces = "text/vnd.graphviz") @ResponseBody public Resource downloadGraphDotGeneric( - @PathVariable("branch") String branch, - HttpServletRequest request, - HttpServletResponse response) + @PathVariable String branch, HttpServletRequest request, HttpServletResponse response) throws IOException { String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); @@ -444,8 +438,8 @@ public Resource downloadGraphDotGeneric( value = {"/queue/{queueID}/tempgraph.png"}, produces = "image/png") @ResponseBody - public PathResource getTempGraphAsPng( - @PathVariable("queueID") String queueID, HttpServletResponse response) throws IOException { + public FileSystemResource getTempGraphAsPng( + @PathVariable String queueID, HttpServletResponse response) throws IOException { QueuedWorkflow queued = workflowService.getQueuedWorkflow(queueID); if (queued == null) { throw new WorkflowNotFoundException(); @@ -454,7 +448,7 @@ public PathResource getTempGraphAsPng( graphVizService.getGraphPath( queued.getId() + ".png", queued.getTempRepresentation().getVisualisationDot(), "png"); response.setHeader("Content-Disposition", "inline; filename=\"graph.png\""); - return new PathResource(out); + return new FileSystemResource(out.toString()); } /** @@ -468,7 +462,7 @@ public PathResource getTempGraphAsPng( consumes = {"text/yaml", "text/x-yaml", "text/plain", "application/octet-stream"}) @ResponseBody public Resource downloadGraphPngFromFile(InputStream in, HttpServletResponse response) - throws IOException, NoSuchAlgorithmException { + throws IOException { response.setHeader("Content-Disposition", "inline; filename=\"graph.png\""); return getGraphFromInputStream(in, "png"); } @@ -484,7 +478,7 @@ public Resource downloadGraphPngFromFile(InputStream in, HttpServletResponse res consumes = {"text/yaml", "text/x-yaml", "text/plain", "application/octet-stream"}) @ResponseBody public Resource downloadGraphSvgFromFile(InputStream in, HttpServletResponse response) - throws IOException, NoSuchAlgorithmException { + throws IOException { response.setHeader("Content-Disposition", "inline; filename=\"graph.svg\""); return getGraphFromInputStream(in, "svg"); } @@ -517,17 +511,12 @@ public static String extractPath(String path, int startSlashNum) { */ public static GitDetails getGitDetails( String domain, String owner, String repoName, String branch, String path) { - String repoUrl; - switch (domain) { - case "github": - repoUrl = "https://github.com/" + owner + "/" + repoName + ".git"; - break; - case "gitlab": - repoUrl = "https://gitlab.com/" + owner + "/" + repoName + ".git"; - break; - default: - throw new WorkflowNotFoundException(); - } + String repoUrl = + switch (domain) { + case "github" -> "https://github.com/" + owner + "/" + repoName + ".git"; + case "gitlab" -> "https://gitlab.com/" + owner + "/" + repoName + ".git"; + default -> throw new WorkflowNotFoundException(); + }; String[] pathSplit = path.split("#"); GitDetails details = new GitDetails(repoUrl, branch, path); if (pathSplit.length > 1) { @@ -595,7 +584,7 @@ private ModelAndView getWorkflow(GitDetails gitDetails, RedirectAttributes redir if (queued.getWorkflowList() != null) { // Packed workflow listing if (queued.getWorkflowList().size() == 1) { - gitDetails.setPackedId(queued.getWorkflowList().get(0).getFileName()); + gitDetails.setPackedId(queued.getWorkflowList().getFirst().fileName()); return new ModelAndView("redirect:" + gitDetails.getInternalUrl()); } return new ModelAndView( @@ -612,7 +601,7 @@ private ModelAndView getWorkflow(GitDetails gitDetails, RedirectAttributes redir return new ModelAndView( "redirect:" + gitDetails.getInternalUrl() - + workflowOverviews.get(0).getFileName()); + + workflowOverviews.getFirst().fileName()); } else { errors.rejectValue( "url", @@ -663,7 +652,7 @@ private ModelAndView getWorkflow(GitDetails gitDetails, RedirectAttributes redir return new ModelAndView("loading", "queued", queued); } else { return new ModelAndView("workflow", "workflow", workflowModel) - .addObject("lineSeparator", System.getProperty("line.separator")) + .addObject("lineSeparator", System.lineSeparator()) .addObject("formats", WebConfig.Format.values()); } } diff --git a/src/main/java/org/commonwl/view/workflow/WorkflowForm.java b/src/main/java/org/commonwl/view/workflow/WorkflowForm.java index cc05010f..1190cffd 100644 --- a/src/main/java/org/commonwl/view/workflow/WorkflowForm.java +++ b/src/main/java/org/commonwl/view/workflow/WorkflowForm.java @@ -79,7 +79,8 @@ public String getPackedId() { return packedId; } - public void setPackedId(String packedId) { + @SuppressWarnings("unused") + public void getGitDetailsSetPackedId(String packedId) { this.packedId = packedId; } @@ -90,7 +91,11 @@ public void setPackedId(String packedId) { * @return The same string without trailing slashes */ private String trimTrailingSlashes(String url) { - return url.replaceAll("\\/+$", ""); + int end = url.length(); + while (end > 0 && url.charAt(end - 1) == '/') { + end--; + } + return url.substring(0, end); } @Override diff --git a/src/main/java/org/commonwl/view/workflow/WorkflowFormValidator.java b/src/main/java/org/commonwl/view/workflow/WorkflowFormValidator.java index a822f64f..4d75f892 100644 --- a/src/main/java/org/commonwl/view/workflow/WorkflowFormValidator.java +++ b/src/main/java/org/commonwl/view/workflow/WorkflowFormValidator.java @@ -19,10 +19,14 @@ package org.commonwl.view.workflow; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.net.URI; +import java.util.List; import org.apache.commons.lang.StringUtils; import org.commonwl.view.git.GitDetails; +import org.commonwl.view.validation.GenericGitUrlValidator; +import org.commonwl.view.validation.GitHubUrlValidator; +import org.commonwl.view.validation.GitLabUrlValidator; +import org.commonwl.view.validation.GitUrlValidator; import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; @@ -31,31 +35,6 @@ @Component public class WorkflowFormValidator { - // URL validation for cwl files on github.com - private static final String GITHUB_CWL_REGEX = - "^https?:\\/\\/github\\.com\\/([A-Za-z0-9_.-]+)\\/([A-Za-z0-9_.-]+)\\/?(?:tree|blob)\\/([^/]+)(?:\\/(.+\\.cwl))$"; - private static final Pattern githubCwlPattern = Pattern.compile(GITHUB_CWL_REGEX); - - // URL validation for directories on github.com - private static final String GITHUB_DIR_REGEX = - "^https?:\\/\\/github\\.com\\/([A-Za-z0-9_.-]+)\\/([A-Za-z0-9_.-]+)\\/?(?:(?:tree|blob)\\/([^/]+)\\/?(.*)?)?$"; - private static final Pattern githubDirPattern = Pattern.compile(GITHUB_DIR_REGEX); - - // URL validation for cwl files on gitlab.com - private static final String GITLAB_CWL_REGEX = - "^https?:\\/\\/gitlab\\.com\\/([A-Za-z0-9_.-]+)\\/([A-Za-z0-9_.-]+)\\/?(?:tree|blob)\\/([^/]+)(?:\\/(.+\\.cwl))$"; - private static final Pattern gitlabCwlPattern = Pattern.compile(GITLAB_CWL_REGEX); - - // URL validation for directories on gitlab.com - private static final String GITLAB_DIR_REGEX = - "^https?:\\/\\/gitlab\\.com\\/([A-Za-z0-9_.-]+)\\/([A-Za-z0-9_.-]+)\\/?(?:(?:tree|blob)\\/([^/]+)\\/?(.*)?)?$"; - private static final Pattern gitlabDirPattern = Pattern.compile(GITLAB_DIR_REGEX); - - // Generic Git URL validation - private static final String GIT_REPO_REGEX = - "^((git|ssh|http(s)?)|(git@[\\w\\.]+))(:(//)?)([\\w\\.@\\:/\\-~]+)(\\.git)(/)?$"; - private static final Pattern gitRepoPattern = Pattern.compile(GIT_REPO_REGEX); - /** * Validates a WorkflowForm to ensure the URL is not empty and links to a cwl file * @@ -63,96 +42,63 @@ public class WorkflowFormValidator { * @param e Any errors from validation */ public GitDetails validateAndParse(WorkflowForm form, Errors e) { - ValidationUtils.rejectIfEmptyOrWhitespace(e, "url", "url.emptyOrWhitespace"); - - // If not null and isn't just whitespace - if (!e.hasErrors()) { - - // Override if specific branch or path is given in the form - String repoUrl = null; - String branch = null; - String path = null; - String packedId = null; - if (!isEmptyOrWhitespace(form.getBranch())) { - branch = form.getBranch(); - } - if (!isEmptyOrWhitespace(form.getPath())) { - path = form.getPath(); - } - if (!isEmptyOrWhitespace(form.getPackedId())) { - packedId = form.getPackedId(); - } - - // github.com URL - Matcher m = githubCwlPattern.matcher(form.getUrl()); - if (m.find()) { - repoUrl = "https://github.com/" + m.group(1) + "/" + m.group(2) + ".git"; - if (branch == null) branch = m.group(3); - if (path == null) path = m.group(4); - } - // gitlab.com URL - m = gitlabCwlPattern.matcher(form.getUrl()); - if (m.find()) { - repoUrl = "https://gitlab.com/" + m.group(1) + "/" + m.group(2) + ".git"; - if (branch == null) branch = m.group(3); - if (path == null) path = m.group(4); - } + ValidationUtils.rejectIfEmptyOrWhitespace(e, "url", "url.emptyOrWhitespace"); - // github.com Dir URL - m = githubDirPattern.matcher(form.getUrl()); - if (m.find() && !m.group(2).endsWith(".git")) { - repoUrl = "https://github.com/" + m.group(1) + "/" + m.group(2) + ".git"; - if (branch == null) branch = m.group(3); - if (path == null) path = m.group(4); - } + if (e.hasErrors()) { + return null; + } - // gitlab.com Dir URL - m = gitlabDirPattern.matcher(form.getUrl()); - if (m.find() && !m.group(2).endsWith(".git")) { - repoUrl = "https://gitlab.com/" + m.group(1) + "/" + m.group(2) + ".git"; - if (branch == null) branch = m.group(3); - if (path == null) path = m.group(4); - } + List handlers = + List.of(new GitHubUrlValidator(), new GitLabUrlValidator(), new GenericGitUrlValidator()); - // Split off packed ID if present - if (repoUrl != null) { - GitDetails details = new GitDetails(repoUrl, branch, path); - if (packedId != null) { - details.setPackedId(packedId); - } else { - String[] pathSplit = path.split("#"); - if (pathSplit.length > 1) { - details.setPath(pathSplit[pathSplit.length - 2]); - details.setPackedId(pathSplit[pathSplit.length - 1]); - } - } - return details; - } + for (GitUrlValidator handler : handlers) { + if (handler.supports(form.getUrl())) { + handler.validate(form, e); - // General Git details if didn't match the above - ValidationUtils.rejectIfEmptyOrWhitespace(e, "branch", "branch.emptyOrWhitespace"); - if (!e.hasErrors()) { - m = gitRepoPattern.matcher(form.getUrl()); - if (m.find()) { - GitDetails details = new GitDetails(form.getUrl(), form.getBranch(), form.getPath()); - details.setPackedId(form.getPackedId()); + GitDetails details = handler.parse(form.getUrl(), form); + if (details != null) { + attachPackedId(details, form); return details; } } } - // Errors will stop this being used anyway + ValidationUtils.rejectIfEmptyOrWhitespace(e, "branch", "branch.emptyOrWhitespace"); return null; } + /** + * Attaches a packed workflow ID into the Git details. + * + *

If no workflow is packed in the request, it searches for the information about the workflow + * in the URL. + * + * @param details Git details + * @param form Workflow form + */ + private void attachPackedId(GitDetails details, WorkflowForm form) { + if (isNotEmptyOrWhitespace(form.getPackedId())) { + details.setPackedId(form.getPackedId()); + return; + } + + URI uri = URI.create(form.getUrl()); + + String fragment = uri.getFragment(); + + if (isNotEmptyOrWhitespace(fragment)) { + details.setPackedId(fragment); + } + } + /** * Checks if a string is empty or whitespace * * @param str The string to be checked * @return Whether the string is empty or whitespace */ - private boolean isEmptyOrWhitespace(String str) { - return (str == null || str.length() == 0 || StringUtils.isWhitespace(str)); + private boolean isNotEmptyOrWhitespace(String str) { + return (str != null && !str.isEmpty() && !StringUtils.isWhitespace(str)); } } diff --git a/src/main/java/org/commonwl/view/workflow/WorkflowJSONController.java b/src/main/java/org/commonwl/view/workflow/WorkflowJSONController.java index e906f26f..aa7a92c6 100644 --- a/src/main/java/org/commonwl/view/workflow/WorkflowJSONController.java +++ b/src/main/java/org/commonwl/view/workflow/WorkflowJSONController.java @@ -23,7 +23,11 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.commonwl.view.cwl.CWLValidationException; import org.commonwl.view.git.GitDetails; import org.springframework.beans.factory.annotation.Autowired; @@ -35,7 +39,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.ui.Model; import org.springframework.validation.BeanPropertyBindingResult; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.HandlerMapping; /** JSON API Controller */ @@ -107,12 +115,12 @@ public ResponseEntity newWorkflowFromGitURLJson( if (errors.hasErrors() || gitInfo == null) { String error; if (errors.hasErrors()) { - error = errors.getAllErrors().get(0).getDefaultMessage(); + error = errors.getAllErrors().getFirst().getDefaultMessage(); } else { error = "Could not parse workflow details from URL"; } Map message = Collections.singletonMap("message", "Error: " + error); - return new ResponseEntity>(message, HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(message, HttpStatus.BAD_REQUEST); } else { // Get workflow or create if does not exist Workflow workflow = workflowService.getWorkflow(gitInfo); @@ -125,7 +133,7 @@ public ResponseEntity newWorkflowFromGitURLJson( if (queued.getWorkflowList() != null) { if (queued.getWorkflowList().size() == 1) { // Parse the packed workflow within automatically if there is only one - gitInfo.setPackedId(queued.getWorkflowList().get(0).getFileName()); + gitInfo.setPackedId(queued.getWorkflowList().getFirst().fileName()); queued = workflowService.getQueuedWorkflow(gitInfo); if (queued == null) { queued = workflowService.createQueuedWorkflow(gitInfo); @@ -134,7 +142,7 @@ public ResponseEntity newWorkflowFromGitURLJson( // Error with alternatives suggested List workflowUris = new ArrayList<>(); for (WorkflowOverview overview : queued.getWorkflowList()) { - workflowUris.add(overview.getFileName().substring(1)); + workflowUris.add(overview.fileName().substring(1)); } Map responseMap = new HashMap<>(); responseMap.put( @@ -142,19 +150,18 @@ public ResponseEntity newWorkflowFromGitURLJson( "This workflow file is packed and contains multiple workflow " + "descriptions. Please provide a packedId parameter with one of the following"); responseMap.put("packedId", workflowUris); - return new ResponseEntity>( - responseMap, HttpStatus.UNPROCESSABLE_ENTITY); + return new ResponseEntity<>(responseMap, HttpStatus.UNPROCESSABLE_CONTENT); } } } catch (CWLValidationException ex) { Map message = Collections.singletonMap("message", "Error:" + ex.getMessage()); - return new ResponseEntity>(message, HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(message, HttpStatus.BAD_REQUEST); } catch (Exception ex) { Map message = Collections.singletonMap( "message", "Error: Workflow could not be created from the provided cwl file"); - return new ResponseEntity>(message, HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(message, HttpStatus.BAD_REQUEST); } } response.setHeader("Location", "/queue/" + queued.getId()); @@ -185,10 +192,10 @@ public ResponseEntity newWorkflowFromGitURLJson( }, produces = MediaType.APPLICATION_JSON_VALUE) public Workflow getWorkflowJson( - @PathVariable("domain") String domain, - @PathVariable("owner") String owner, - @PathVariable("repoName") String repoName, - @PathVariable("branch") String branch, + @PathVariable String domain, + @PathVariable String owner, + @PathVariable String repoName, + @PathVariable String branch, HttpServletRequest request) { // The wildcard end of the URL is the path String path = @@ -214,8 +221,7 @@ public Workflow getWorkflowJson( * @return The JSON representation of the workflow */ @GetMapping(value = "/workflows/*/*.git/{branch}/**", produces = MediaType.APPLICATION_JSON_VALUE) - public Workflow getWorkflowJsonGeneric( - @PathVariable("branch") String branch, HttpServletRequest request) { + public Workflow getWorkflowJsonGeneric(@PathVariable String branch, HttpServletRequest request) { // The wildcard end of the URL is the path String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); @@ -240,8 +246,7 @@ public Workflow getWorkflowJsonGeneric( * object */ @GetMapping(value = "/queue/{queueID}", produces = MediaType.APPLICATION_JSON_VALUE) - public QueuedWorkflow checkQueueJson( - @PathVariable("queueID") String queueID, HttpServletResponse response) { + public QueuedWorkflow checkQueueJson(@PathVariable String queueID, HttpServletResponse response) { QueuedWorkflow queuedWorkflow = workflowService.getQueuedWorkflow(queueID); if (queuedWorkflow == null) { throw new WorkflowNotFoundException(); diff --git a/src/main/java/org/commonwl/view/workflow/WorkflowOverview.java b/src/main/java/org/commonwl/view/workflow/WorkflowOverview.java index 66af295d..f64cca25 100644 --- a/src/main/java/org/commonwl/view/workflow/WorkflowOverview.java +++ b/src/main/java/org/commonwl/view/workflow/WorkflowOverview.java @@ -19,45 +19,5 @@ package org.commonwl.view.workflow; -import java.util.Objects; - /** Gives an overview of a workflow */ -public class WorkflowOverview { - - private final String fileName; - private final String label; - private final String doc; - - public WorkflowOverview(String fileName, String label, String doc) { - this.fileName = fileName; - this.label = label; - this.doc = doc; - } - - public String getFileName() { - return fileName; - } - - public String getLabel() { - return label; - } - - public String getDoc() { - return doc; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - WorkflowOverview that = (WorkflowOverview) o; - return Objects.equals(fileName, that.fileName) - && Objects.equals(label, that.label) - && Objects.equals(doc, that.doc); - } - - @Override - public int hashCode() { - return Objects.hash(fileName, label, doc); - } -} +public record WorkflowOverview(String fileName, String label, String doc) {} diff --git a/src/main/java/org/commonwl/view/workflow/WorkflowPermalinkController.java b/src/main/java/org/commonwl/view/workflow/WorkflowPermalinkController.java index f041d612..efca04a3 100644 --- a/src/main/java/org/commonwl/view/workflow/WorkflowPermalinkController.java +++ b/src/main/java/org/commonwl/view/workflow/WorkflowPermalinkController.java @@ -77,10 +77,9 @@ public String uriList( } /** - * Redirect to the viewer for a web browser or API call + * Redirect (302) to the viewer for a web browser or API call, or 404 * * @param commitId The commit ID of the workflow - * @return A 302 redirect response to the viewer or 404 */ @GetMapping( value = "/git/{commitid}/**", @@ -105,10 +104,9 @@ public void goToViewer( } /** - * Redirect to the raw file if this exists + * Redirect (302) to the raw file if this exists, or 406 * * @param commitId The commit ID of the workflow - * @return A 302 redirect response to the raw URL or 406 */ @GetMapping( value = "/git/{commitid}/**", @@ -118,7 +116,7 @@ public void goToRawUrl( HttpServletRequest request, HttpServletResponse response) { Optional rawUrl = findRaw(commitId, request); - if (!rawUrl.isPresent()) { + if (rawUrl.isEmpty()) { throw new RepresentationNotFoundException(); } else { response.setHeader("Location", rawUrl.get()); @@ -204,8 +202,8 @@ public byte[] getRdfAsRdfXml( * * @param commitId The commit ID of the workflow * @return The SVG image - * @throws IOException - * @throws WorkflowNotFoundException + * @throws IOException If it fails to read or write the SVG file + * @throws WorkflowNotFoundException If it fails to find the workflow */ @GetMapping(value = "/git/{commitid}/**", produces = "image/svg+xml") public Resource getGraphAsSvg( @@ -224,8 +222,8 @@ public Resource getGraphAsSvg( * * @param commitId The commit ID of the workflow * @return The PNG image - * @throws IOException - * @throws WorkflowNotFoundException + * @throws IOException If it fails to read or write the PNG file + * @throws WorkflowNotFoundException If it cannot find the workflow */ @GetMapping(value = "/git/{commitid}/**", produces = "image/png") public Resource getGraphAsPng( @@ -244,8 +242,8 @@ public Resource getGraphAsPng( * * @param commitId The commit ID of the workflow * @return The XDOT source - * @throws IOException - * @throws WorkflowNotFoundException + * @throws IOException If it fails to read or write the dot file + * @throws WorkflowNotFoundException If the workflow cannot be found */ @GetMapping(value = "/git/{commitid}/**", produces = "text/vnd+graphviz") public Resource getGraphAsXDot( diff --git a/src/main/java/org/commonwl/view/workflow/WorkflowRepository.java b/src/main/java/org/commonwl/view/workflow/WorkflowRepository.java index 670edcf6..7bb31bd6 100644 --- a/src/main/java/org/commonwl/view/workflow/WorkflowRepository.java +++ b/src/main/java/org/commonwl/view/workflow/WorkflowRepository.java @@ -20,10 +20,13 @@ package org.commonwl.view.workflow; import java.util.List; +import java.util.UUID; +import org.commonwl.view.git.GitDetails; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; /** @@ -33,8 +36,7 @@ * href="https://docs.spring.io/spring-data/jpa/docs/current/reference/html/">... */ @Repository -public interface WorkflowRepository - extends JpaRepository, WorkflowRepositoryCustom { +public interface WorkflowRepository extends JpaRepository { /** * Finds a workflow model in the database based on a commit ID and path @@ -58,6 +60,15 @@ public interface WorkflowRepository @Query("SELECT w FROM Workflow w WHERE w.lastCommit = ?1") List findByCommit(String commitId); + /** + * Finds a workflow model in the database based on where it was retrieved from + * + * @param retrievedFrom Details of where the workflow is from + * @return The workflow model + */ + @Query("SELECT w FROM Workflow w WHERE w.retrievedFrom = :retrievedFrom") + Workflow findByRetrievedFrom(@Param("retrievedFrom") GitDetails retrievedFrom); + /** * Paged request to get workflows of a specific status * diff --git a/src/main/java/org/commonwl/view/workflow/WorkflowRepositoryImpl.java b/src/main/java/org/commonwl/view/workflow/WorkflowRepositoryImpl.java deleted file mode 100644 index 8d992ccf..00000000 --- a/src/main/java/org/commonwl/view/workflow/WorkflowRepositoryImpl.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.commonwl.view.workflow; - -import io.hypersistence.utils.hibernate.type.json.JsonType; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.persistence.PersistenceContextType; -import org.commonwl.view.git.GitDetails; -import org.hibernate.query.Query; - -public class WorkflowRepositoryImpl implements WorkflowRepositoryCustom { - - private static final String QUERY_FIND_BY_RETRIEVED_FROM = - "SELECT w.* FROM workflow w WHERE w.retrieved_from = :retrievedFrom"; - - @PersistenceContext(type = PersistenceContextType.EXTENDED) - EntityManager entityManager; - - @Override - public Workflow findByRetrievedFrom(GitDetails retrievedFrom) { - final Query query = - entityManager - .createNativeQuery(QUERY_FIND_BY_RETRIEVED_FROM, Workflow.class) - .unwrap(Query.class); - - if (query == null) { - return null; - } - - query.setParameter("retrievedFrom", retrievedFrom, new JsonType(GitDetails.class)); - return (Workflow) query.uniqueResult(); - } -} diff --git a/src/main/java/org/commonwl/view/workflow/WorkflowService.java b/src/main/java/org/commonwl/view/workflow/WorkflowService.java index cf97282c..990f2630 100644 --- a/src/main/java/org/commonwl/view/workflow/WorkflowService.java +++ b/src/main/java/org/commonwl/view/workflow/WorkflowService.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.UUID; import org.commonwl.view.cwl.CWLService; import org.commonwl.view.cwl.CWLToolRunner; import org.commonwl.view.cwl.CWLToolStatus; @@ -48,7 +49,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.PathResource; +import org.springframework.core.io.FileSystemResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -119,7 +120,7 @@ public Page searchPageOfWorkflows(String searchString, Pageable pageab * @return The model for the workflow */ public Workflow getWorkflow(String id) { - return workflowRepository.findById(id).orElse(null); + return workflowRepository.findById(UUID.fromString(id)).orElse(null); } /** @@ -129,13 +130,13 @@ public Workflow getWorkflow(String id) { * @return The model for the queued workflow */ public QueuedWorkflow getQueuedWorkflow(String id) { - return queuedWorkflowRepository.findById(id).orElse(null); + return queuedWorkflowRepository.findById(UUID.fromString(id)).orElse(null); } /** * Get a queued workflow from the database * - * @param githubInfo Github information for the workflow + * @param githubInfo GitHub information for the workflow * @return The queued workflow model */ public QueuedWorkflow getQueuedWorkflow(GitDetails githubInfo) { @@ -157,7 +158,7 @@ public QueuedWorkflow getQueuedWorkflow(GitDetails githubInfo) { } /** - * Get a workflow from the database, refreshing it if cache has expired + * Get a workflow from the database, refreshing it if the cache has expired * * @param gitInfo Git information for the workflow * @return The workflow model associated with gitInfo @@ -199,7 +200,7 @@ public Workflow getWorkflow(GitDetails gitInfo) { workflow = null; } catch (Exception e) { // Add back the old workflow if it is broken now - logger.error("Could not parse updated workflow " + workflow.getID()); + logger.error("Could not parse updated workflow {}", workflow.getId()); workflowRepository.save(workflow); } } @@ -246,7 +247,7 @@ public List getWorkflowsFromDirectory(GitDetails gitInfo) File directory = new File(pathToDirectory.toString()); if (directory.exists() && directory.isDirectory()) { - for (final File file : directory.listFiles()) { + for (final File file : Objects.requireNonNull(directory.listFiles())) { int eIndex = file.getName().lastIndexOf('.') + 1; if (eIndex > 0) { String extension = file.getName().substring(eIndex); @@ -257,7 +258,7 @@ public List getWorkflowsFromDirectory(GitDetails gitInfo) workflowsInDir.add(overview); } } catch (IOException err) { - logger.error("Skipping file due to IOException: " + file.toString(), err); + logger.error("Skipping file due to IOException: {}", file, err); } } } @@ -348,7 +349,7 @@ public QueuedWorkflow createQueuedWorkflow(GitDetails gitInfo) if (cwlService.isPacked(workflowFile.toFile())) { List overviews = cwlService.getWorkflowOverviewsFromPacked(workflowFile.toFile()); - if (overviews.size() == 0) { + if (overviews.isEmpty()) { throw new IOException( "No workflow was found within the packed CWL file. " + gitInfo.toSummary()); } else { @@ -382,7 +383,7 @@ public QueuedWorkflow createQueuedWorkflow(GitDetails gitInfo) try { cwlToolRunner.createWorkflowFromQueued(queuedWorkflow); } catch (Exception e) { - logger.error("Could not update workflow with cwltool: " + gitInfo.toSummary(), e); + logger.error("Could not update workflow with cwltool: {}", gitInfo.toSummary(), e); } } catch (GitAPIException | RuntimeException | IOException e) { @@ -414,7 +415,7 @@ public void retryCwltool(QueuedWorkflow queuedWorkflow) { try { cwlToolRunner.createWorkflowFromQueued(queuedWorkflow); } catch (Exception e) { - logger.error("Could not update workflow " + queuedWorkflow.getId() + " with cwltool.", e); + logger.error("Could not update workflow {} with cwltool.", queuedWorkflow.getId(), e); } } @@ -444,9 +445,9 @@ public Workflow findByCommitAndPath(String commitID, String path, Optional matches = workflowRepository.findByCommitAndPath(commitID, path); if (matches == null || matches.isEmpty()) { throw new WorkflowNotFoundException(); - } else if (part == null) { + } else if (part.isEmpty()) { // any part will do - so we'll pick the first one - return matches.get(0); + return matches.getFirst(); } else { // Multiple matches means either added by both branch and ID // Or packed workflow @@ -456,14 +457,8 @@ public Workflow findByCommitAndPath(String commitID, String path, Optional format; + case "xdot" -> "dot"; + default -> throw new WorkflowNotFoundException("Format " + format + " not recognized."); + }; // Get workflow Workflow workflow = getWorkflow(gitDetails); @@ -502,8 +491,8 @@ public PathResource getWorkflowGraph(String format, GitDetails gitDetails) // Generate graph and serve the file Path out = graphVizService.getGraphPath( - workflow.getID() + "." + extension, workflow.getVisualisationDot(), format); - return new PathResource(out); + workflow.getId() + "." + extension, workflow.getVisualisationDot(), format); + return new FileSystemResource(out.toString()); } /** @@ -516,7 +505,8 @@ private void generateROBundle(Workflow workflow) { ROBundleFactory.createWorkflowRO(workflow); } catch (Exception ex) { logger.error( - "Error creating RO Bundle for workflow from " + workflow.getRetrievedFrom().toSummary(), + "Error creating RO Bundle for workflow from {}", + workflow.getRetrievedFrom().toSummary(), ex); } } @@ -531,20 +521,20 @@ private void removeWorkflow(Workflow workflow) { if (workflow.getRoBundlePath() != null) { File roBundle = new File(workflow.getRoBundlePath()); if (roBundle.delete()) { - logger.debug("Deleted Research Object Bundle for workflow " + workflow.getID()); + logger.debug("Deleted Research Object Bundle for workflow {}", workflow.getId()); } else { - logger.info("Failed to delete Research Object Bundle for workflow " + workflow.getID()); + logger.info("Failed to delete Research Object Bundle for workflow {}", workflow.getId()); } } // Delete cached graphviz images if they exist - graphVizService.deleteCache(workflow.getID()); + graphVizService.deleteCache(workflow.getId()); // Remove the workflow from the database workflowRepository.delete(workflow); // Remove any queued repositories pointing to the workflow - queuedWorkflowRepository.deleteByTempRepresentation_RetrievedFrom(workflow.getRetrievedFrom()); + queuedWorkflowRepository.deleteByRetrievedFrom(workflow.getRetrievedFrom()); } /** @@ -568,7 +558,7 @@ private boolean cacheExpired(Workflow workflow) { // Cache expiry time has elapsed // Check current head of the branch with the cached head logger.info( - "Time has expired for caching, checking commits for workflow " + workflow.getID()); + "Time has expired for caching, checking commits for workflow {}", workflow.getId()); String currentHead; Git repo = null; boolean safeToAccess = gitSemaphore.acquire(workflow.getRetrievedFrom().getRepoUrl()); @@ -580,12 +570,10 @@ private boolean cacheExpired(Workflow workflow) { FileUtils.deleteTemporaryGitRepository(repo); } logger.info( - "Current: " - + workflow.getLastCommit() - + ", HEAD: " - + currentHead - + " for workflow " - + workflow.getID()); + "Current: {}, HEAD: {} for workflow {}", + workflow.getLastCommit(), + currentHead, + workflow.getId()); // Reset date in database if there are still no changes boolean expired = !workflow.getLastCommit().equals(currentHead); @@ -600,8 +588,8 @@ private boolean cacheExpired(Workflow workflow) { } catch (Exception ex) { // Default to no expiry if there was an API error logger.error( - "API Error when checking for latest commit ID for caching for workflow " - + workflow.getID(), + "API Error when checking for latest commit ID for caching for workflow {}", + workflow.getId(), ex); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 17f92699..e5ea9ad5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -53,9 +53,11 @@ spring.sql.init.platform=postgres spring.jpa.generate-ddl=false spring.jpa.open-in-view=false spring.jpa.show-sql=false -spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect spring.jpa.hibernate.ddl-auto=validate spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true +# To stop Hibernate from using serialization fallback, and control maxsize +spring.jpa.properties.hibernate.bytecode.allow_enhancement_as_proxy=false +spring.jpa.properties.hibernate.query.plan_cache_max_size=2048 #======================= # SPARQL endpoint diff --git a/src/test/java/org/commonwl/view/PageControllerTest.java b/src/test/java/org/commonwl/view/PageControllerTest.java index efa529e8..e460d747 100644 --- a/src/test/java/org/commonwl/view/PageControllerTest.java +++ b/src/test/java/org/commonwl/view/PageControllerTest.java @@ -22,7 +22,9 @@ import static org.hamcrest.beans.HasPropertyWithValue.hasProperty; import static org.hamcrest.core.Is.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/commonwl/view/cwl/CWLElementTest.java b/src/test/java/org/commonwl/view/cwl/CWLElementTest.java index 44a2088d..35000570 100644 --- a/src/test/java/org/commonwl/view/cwl/CWLElementTest.java +++ b/src/test/java/org/commonwl/view/cwl/CWLElementTest.java @@ -31,7 +31,7 @@ public class CWLElementTest { * list */ @Test - public void testSourceIDList() throws Exception { + public void testSourceIDList() { CWLElement element = new CWLElement(); diff --git a/src/test/java/org/commonwl/view/cwl/CWLProcessTest.java b/src/test/java/org/commonwl/view/cwl/CWLProcessTest.java index 5742ce18..d5480244 100644 --- a/src/test/java/org/commonwl/view/cwl/CWLProcessTest.java +++ b/src/test/java/org/commonwl/view/cwl/CWLProcessTest.java @@ -27,7 +27,7 @@ public class CWLProcessTest { /** Test toString method for enum */ @Test - public void testToString() throws Exception { + public void testToString() { assertEquals("Workflow", CWLProcess.WORKFLOW.toString()); assertEquals("CommandLineTool", CWLProcess.COMMANDLINETOOL.toString()); assertEquals("ExpressionTool", CWLProcess.EXPRESSIONTOOL.toString()); diff --git a/src/test/java/org/commonwl/view/cwl/CWLServiceTest.java b/src/test/java/org/commonwl/view/cwl/CWLServiceTest.java index 7e3eb2fd..842c8313 100644 --- a/src/test/java/org/commonwl/view/cwl/CWLServiceTest.java +++ b/src/test/java/org/commonwl/view/cwl/CWLServiceTest.java @@ -158,7 +158,7 @@ public void parseLobSTRDraft3WorkflowNative() throws Exception { Workflow lobSTRDraft3 = cwlService.parseWorkflowNative( Paths.get("src/test/resources/cwl/lobstr-draft3/lobSTR-workflow.cwl"), null); - testLobSTRWorkflow(lobSTRDraft3, true); + testLobSTRWorkflow(lobSTRDraft3); } /** Test native loading parsing of the LobSTR workflow CWL version 1.0 */ @@ -170,7 +170,7 @@ public void parseLobSTRv1WorkflowNative() throws Exception { Workflow lobSTRv1 = cwlService.parseWorkflowNative( Paths.get("src/test/resources/cwl/lobstr-v1/lobSTR-workflow.cwl"), null); - testLobSTRWorkflow(lobSTRv1, true); + testLobSTRWorkflow(lobSTRv1); } /** Test native loading parsing of optional inline types */ @@ -182,17 +182,17 @@ public void parseWorkflowInlineOptionalTypesNative() throws Exception { Workflow workflow = cwlService.parseWorkflowNative( Paths.get("src/test/resources/cwl/oneline_optional_types.cwl"), null); - assertEquals(workflow.getInputs().get("qualified_phred_quality").getType(), "int?"); - assertEquals(workflow.getInputs().get("ncrna_tab_file").getType(), "File?"); - assertEquals(workflow.getInputs().get("reverse_reads").getType(), "File?"); - assertEquals(workflow.getInputs().get("ssu_tax").getType(), "string, File"); + assertEquals("int?", workflow.getInputs().get("qualified_phred_quality").getType()); + assertEquals("File?", workflow.getInputs().get("ncrna_tab_file").getType()); + assertEquals("File?", workflow.getInputs().get("reverse_reads").getType()); + assertEquals("string, File", workflow.getInputs().get("ssu_tax").getType()); assertEquals( - workflow.getInputs().get("rfam_models").getType(), "{type=array, items=[string, File]}"); + "{type=array, items=[string, File]}", workflow.getInputs().get("rfam_models").getType()); } /** Test native loading parsing of MultipleInputFeatureRequirement using workflows */ @Test - public void parseWorkflowMultiInboundLins() throws Exception { + public void parseWorkflowMultiInboundLinks() throws Exception { CWLService cwlService = new CWLService( rdfService, new CWLTool(), Mockito.mock(GitConfig.class).licenseVocab(), 5242880); @@ -200,11 +200,11 @@ public void parseWorkflowMultiInboundLins() throws Exception { cwlService.parseWorkflowNative( Paths.get("src/test/resources/cwl/complex-workflow/complex-workflow-1.cwl"), null); assertEquals( - workflow.getSteps().get("re_tar_step").getSources().get("file_list").getSourceIDs().get(0), - "touch_step"); + "touch_step", + workflow.getSteps().get("re_tar_step").getSources().get("file_list").getSourceIDs().get(0)); assertEquals( - workflow.getSteps().get("re_tar_step").getSources().get("file_list").getSourceIDs().get(1), - "files"); + "files", + workflow.getSteps().get("re_tar_step").getSources().get("file_list").getSourceIDs().get(1)); } /** Test native loading parsing of nested array types */ @@ -215,8 +215,8 @@ public void parseWorkflowNestedArrayTypes() throws Exception { rdfService, new CWLTool(), Mockito.mock(GitConfig.class).licenseVocab(), 5242880); Workflow workflow = cwlService.parseWorkflowNative(Paths.get("src/test/resources/cwl/nested_array.cwl"), null); - assertEquals(workflow.getInputs().get("overlap_files").getType(), "File[][]"); - assertEquals(workflow.getOutputs().get("freq_files").getType(), "File[][]"); + assertEquals("File[][]", workflow.getInputs().get("overlap_files").getType()); + assertEquals("File[][]", workflow.getOutputs().get("freq_files").getType()); assertTrue(Map.class.isAssignableFrom(workflow.getSteps().get("dummy").getRun().getClass())); } @@ -316,9 +316,9 @@ public void getHelloWorkflowOverview() throws Exception { assertNotNull(hello); // No docs for this workflow - assertEquals("Hello World", hello.getLabel()); - assertEquals("Puts a message into a file using echo", hello.getDoc()); - assertEquals("/hello.cwl", hello.getFileName()); + assertEquals("Hello World", hello.label()); + assertEquals("Puts a message into a file using echo", hello.doc()); + assertEquals("/hello.cwl", hello.fileName()); } /** Test retrieval of a workflow overview with an array ``doc`` field */ @@ -338,7 +338,7 @@ public void getHelloWorkflowOverviewDocList() throws Exception { System.out.println(cwlService.normaliseLicenseLink("http://asdasdasda")); WorkflowOverview hello = cwlService.getWorkflowOverview(helloWorkflow); assertNotNull(hello); - assertEquals("Puts a message into a file using echo. Even more doc", hello.getDoc()); + assertEquals("Puts a message into a file using echo. Even more doc", hello.doc()); } /** Test IOException is thrown when files are over limit with getWorkflowOverview */ @@ -383,16 +383,16 @@ public void workflowOverviewsFromPackedFile() throws Exception { assertTrue(cwlService.isPacked(packedFile)); List overviews = cwlService.getWorkflowOverviewsFromPacked(packedFile); assertEquals(1, overviews.size()); - assertEquals("main", overviews.get(0).getFileName()); - assertNull(overviews.get(0).getLabel()); - assertNull(overviews.get(0).getDoc()); + assertEquals("main", overviews.getFirst().fileName()); + assertNull(overviews.getFirst().label()); + assertNull(overviews.getFirst().doc()); } /** * Validate a LobSTR workflow See: ... */ - private void testLobSTRWorkflow(Workflow lobSTR, boolean nativeParsed) { + private void testLobSTRWorkflow(Workflow lobSTR) { // Overall not null assertNotNull(lobSTR); @@ -426,7 +426,8 @@ private void testLobSTRWorkflow(Workflow lobSTR, boolean nativeParsed) { assertTrue(outputs.get("bam").getSourceIDs().contains("samindex")); // Extra tests if parsing is done with cwltool - if (!nativeParsed) { + CWLProcess runType = steps.get("lobSTR").getRunType(); + if (runType != null) { assertEquals(CWLProcess.COMMANDLINETOOL, steps.get("lobSTR").getRunType()); } } diff --git a/src/test/java/org/commonwl/view/cwl/RDFServiceTest.java b/src/test/java/org/commonwl/view/cwl/RDFServiceTest.java index 99352175..f0ad15cc 100644 --- a/src/test/java/org/commonwl/view/cwl/RDFServiceTest.java +++ b/src/test/java/org/commonwl/view/cwl/RDFServiceTest.java @@ -36,7 +36,7 @@ public class RDFServiceTest { /** Test extracting step names from full URIs */ @Test - public void stepNameFromURI() throws Exception { + public void stepNameFromURI() { // PACKED - with and without input String baseURL = @@ -57,7 +57,7 @@ public void stepNameFromURI() throws Exception { /** Test formatting default values */ @Test - public void formatDefault() throws Exception { + public void formatDefault() { assertEquals("\\\"-bg\\\"", rdfService.formatDefault("-bg")); assertEquals( "10000", rdfService.formatDefault("10000^^http://www.w3.org/2001/XMLSchema#integer")); @@ -67,7 +67,7 @@ public void formatDefault() throws Exception { /** Test extracting a label from name */ @Test - public void labelFromName() throws Exception { + public void labelFromName() { assertEquals("trim_method", rdfService.labelFromName("trim_method")); assertEquals( "outfile", diff --git a/src/test/java/org/commonwl/view/docker/DockerServiceTest.java b/src/test/java/org/commonwl/view/docker/DockerServiceTest.java index dfd05332..b5682d05 100644 --- a/src/test/java/org/commonwl/view/docker/DockerServiceTest.java +++ b/src/test/java/org/commonwl/view/docker/DockerServiceTest.java @@ -22,43 +22,34 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class DockerServiceTest { - private DockerService dockerService; - - /** New instance of DockerService */ - @BeforeEach - public void setUp() throws Exception { - dockerService = new DockerService(); - } - - /** Test conversion from docker pull tag to dockerhub URL */ + /** Test conversion from docker pull tag to DockerHub URL */ @Test - public void getStandardDockerHubURL() throws Exception { + public void getStandardDockerHubURL() { String test = DockerService.getDockerHubURL("stain/cwlviewer"); assertEquals("https://hub.docker.com/r/stain/cwlviewer", test); } /** Second valid example */ @Test - public void getStandardDockerHubURL2() throws Exception { + public void getStandardDockerHubURL2() { String test = DockerService.getDockerHubURL("rabix/lobSTR"); assertEquals("https://hub.docker.com/r/rabix/lobSTR", test); } /** Example from the official repository */ @Test - public void getOfficialRepoDockerHubURL() throws Exception { + public void getOfficialRepoDockerHubURL() { String test = DockerService.getDockerHubURL("ubuntu"); assertEquals("https://hub.docker.com/r/_/ubuntu", test); } /** Invalid tag should fail to get URL */ @Test - public void getInvalidDockerHubURL() throws Exception { + public void getInvalidDockerHubURL() { String test = DockerService.getDockerHubURL("clearly/not/a/valid/tag"); assertNull(test); } diff --git a/src/test/java/org/commonwl/view/git/GitDetailsTest.java b/src/test/java/org/commonwl/view/git/GitDetailsTest.java index 5d682e04..28b79f1c 100644 --- a/src/test/java/org/commonwl/view/git/GitDetailsTest.java +++ b/src/test/java/org/commonwl/view/git/GitDetailsTest.java @@ -22,6 +22,7 @@ import static org.commonwl.view.git.GitDetails.normaliseUrl; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.mockito.Mockito.when; import java.io.File; @@ -48,7 +49,7 @@ public class GitDetailsTest { /** Branch getter, should default to "master" if null */ @Test - public void getBranch() throws Exception { + public void getBranch() { GitDetails details1 = new GitDetails("https://repo.url/repo.git", "testbranch", "path/within/workflow.cwl"); assertEquals("testbranch", details1.getBranch()); @@ -60,7 +61,7 @@ public void getBranch() throws Exception { /** Path getter, should default to / if null */ @Test - public void getPath() throws Exception { + public void getPath() { GitDetails details1 = new GitDetails("https://repo.url/repo.git", "branch", "subdir/"); assertEquals("subdir/", details1.getPath()); @@ -74,7 +75,7 @@ public void getPath() throws Exception { /** Get the type of URLs from Git details */ @Test - public void getType() throws Exception { + public void getType() { assertEquals(GitType.GITHUB, GITHUB_DETAILS.getType()); assertEquals(GitType.GITLAB, GITLAB_DETAILS.getType()); assertEquals(GitType.BITBUCKET, BITBUCKET_DETAILS.getType()); @@ -83,7 +84,7 @@ public void getType() throws Exception { /** Construct a URL from Git details */ @Test - public void getUrl() throws Exception { + public void getUrl() { assertEquals( "https://github.com/owner/repoName/blob/branch/path/within/structure.cwl", GITHUB_DETAILS.getUrl()); @@ -101,7 +102,7 @@ public void getUrl() throws Exception { /** Construct the internal link to a workflow from Git details */ @Test - public void getInternalUrl() throws Exception { + public void getInternalUrl() { assertEquals( "/workflows/github.com/owner/repoName/blob/branch/path/within/structure.cwl", GITHUB_DETAILS.getInternalUrl()); @@ -121,7 +122,7 @@ public void getInternalUrl() throws Exception { /** Construct the raw URL to a workflow file from Git details */ @Test - public void getRawUrl() throws Exception { + public void getRawUrl() { assertEquals( "https://raw.githubusercontent.com/owner/repoName/branch/path/within/structure.cwl", GITHUB_DETAILS.getRawUrl()); @@ -138,7 +139,7 @@ public void getRawUrl() throws Exception { /** Normalise a URL removing protocol and www. */ @Test - public void getNormaliseUrl() throws Exception { + public void getNormaliseUrl() { assertEquals("github.com/test/url/here", normaliseUrl("https://www.github.com/test/url/here")); assertEquals("github.com/test/url/here", normaliseUrl("ssh://www.github.com/test/url/here")); assertEquals( @@ -147,27 +148,47 @@ public void getNormaliseUrl() throws Exception { assertEquals("github.com/test/url/here", normaliseUrl("http://github.com/test/url/here")); } + /** + * Check if the licensee gem is installed and its binary is available. + * + * @return {@code true} if {@code licensee} is found in the {@code $PATH}. + */ + private boolean isLicenseeAvailable() { + try { + Process p = new ProcessBuilder("licensee", "--version").redirectErrorStream(true).start(); + + int exit = p.waitFor(); + return exit == 0; + } catch (Exception e) { + return false; + } + } + /** Retrieves license details from the repo, if present. */ @Test - public void getLicense() throws Exception { - Repository mockRepo = Mockito.mock(Repository.class); - - when(mockRepo.getWorkTree()).thenReturn(new File("src/test/resources/cwl/licenses/apache/")); - assertEquals( - "https://spdx.org/licenses/Apache-2.0", - GENERIC_DETAILS.getLicense(mockRepo.getWorkTree().toPath())); - - when(mockRepo.getWorkTree()).thenReturn(new File("src/test/resources/cwl/licenses/multiple/")); - assertEquals( - "https://spdx.org/licenses/Apache-2.0", - GENERIC_DETAILS.getLicense(mockRepo.getWorkTree().toPath())); - - when(mockRepo.getWorkTree()).thenReturn(new File("src/test/resources/cwl/licenses/other/")); - assertEquals( - "https://could.com/be/anything.git", - GENERIC_DETAILS.getLicense(mockRepo.getWorkTree().toPath())); - - when(mockRepo.getWorkTree()).thenReturn(new File("src/test/resources/cwl/licenses/none/")); - assertNull(GENERIC_DETAILS.getLicense(mockRepo.getWorkTree().toPath())); + public void getLicense() { + assumeTrue(isLicenseeAvailable(), "Skipping test: licensee not in PATH"); + + try (Repository mockRepo = Mockito.mock(Repository.class)) { + + when(mockRepo.getWorkTree()).thenReturn(new File("src/test/resources/cwl/licenses/apache/")); + assertEquals( + "https://spdx.org/licenses/Apache-2.0", + GENERIC_DETAILS.getLicense(mockRepo.getWorkTree().toPath())); + + when(mockRepo.getWorkTree()) + .thenReturn(new File("src/test/resources/cwl/licenses/multiple/")); + assertEquals( + "https://spdx.org/licenses/Apache-2.0", + GENERIC_DETAILS.getLicense(mockRepo.getWorkTree().toPath())); + + when(mockRepo.getWorkTree()).thenReturn(new File("src/test/resources/cwl/licenses/other/")); + assertEquals( + "https://could.com/be/anything.git", + GENERIC_DETAILS.getLicense(mockRepo.getWorkTree().toPath())); + + when(mockRepo.getWorkTree()).thenReturn(new File("src/test/resources/cwl/licenses/none/")); + assertNull(GENERIC_DETAILS.getLicense(mockRepo.getWorkTree().toPath())); + } } } diff --git a/src/test/java/org/commonwl/view/git/GitSemaphoreTest.java b/src/test/java/org/commonwl/view/git/GitSemaphoreTest.java index aea4c71c..c69035ba 100644 --- a/src/test/java/org/commonwl/view/git/GitSemaphoreTest.java +++ b/src/test/java/org/commonwl/view/git/GitSemaphoreTest.java @@ -27,7 +27,7 @@ public class GitSemaphoreTest { @Test - public void basicOperation() throws Exception { + public void basicOperation() { GitSemaphore sem = new GitSemaphore(); boolean first = sem.acquire("1"); diff --git a/src/test/java/org/commonwl/view/git/GitServiceTest.java b/src/test/java/org/commonwl/view/git/GitServiceTest.java index b1295283..ca40aae5 100644 --- a/src/test/java/org/commonwl/view/git/GitServiceTest.java +++ b/src/test/java/org/commonwl/view/git/GitServiceTest.java @@ -68,7 +68,7 @@ public void setup() throws GitAPIException { } @Test - public void transferPathToBranch() throws Exception { + public void transferPathToBranch() { GitService gitService = new GitService(null, false); GitDetails slashesInBranch = new GitDetails(null, "branchpart1", "branchpart2/branchpart3/workflowInRoot.cwl"); diff --git a/src/test/java/org/commonwl/view/graphviz/GraphVizServiceTest.java b/src/test/java/org/commonwl/view/graphviz/GraphVizServiceTest.java index 024ab90d..dfe1b8c6 100644 --- a/src/test/java/org/commonwl/view/graphviz/GraphVizServiceTest.java +++ b/src/test/java/org/commonwl/view/graphviz/GraphVizServiceTest.java @@ -32,6 +32,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.UUID; import javax.imageio.ImageIO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -46,13 +47,13 @@ public class GraphVizServiceTest { /** Generate a service for testing using the temporary folder */ @BeforeEach - public void setUp() throws Exception { + public void setUp() { graphVizService = new GraphVizService(graphvizFolder.getAbsolutePath()); } /** Check that a valid png file can be generated from DOT source */ @Test - public void getGraphAsPng() throws Exception { + public void getGraphAsPng() throws IOException { Path dotSource = Paths.get("src/test/resources/graphviz/testWorkflow.dot"); @@ -66,7 +67,7 @@ public void getGraphAsPng() throws Exception { /** Check that a valid png file can be generated from DOT source */ @Test - public void getGraphAsPngStream() throws Exception { + public void getGraphAsPngStream() throws IOException { Path dotSource = Paths.get("src/test/resources/graphviz/testWorkflow.dot"); @@ -79,12 +80,12 @@ public void getGraphAsPngStream() throws Exception { } private String readFileToString(Path path) throws IOException { - return new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + return Files.readString(path); } private String readStreamToString(InputStream is) throws IOException { BufferedReader r = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); while (true) { String line = r.readLine(); if (line == null) break; @@ -96,7 +97,7 @@ private String readStreamToString(InputStream is) throws IOException { /** Check that a valid svg file can be generated from DOT source */ @Test - public void getGraphAsSvg() throws Exception { + public void getGraphAsSvg() throws IOException { Path dotSource = Paths.get("src/test/resources/graphviz/testWorkflow.dot"); @@ -107,7 +108,7 @@ public void getGraphAsSvg() throws Exception { /** Check that a valid svg file can be generated from DOT source */ @Test - public void getGraphAsSvgStream() throws Exception { + public void getGraphAsSvgStream() throws IOException { Path dotSource = Paths.get("src/test/resources/graphviz/testWorkflow.dot"); @@ -118,32 +119,32 @@ public void getGraphAsSvgStream() throws Exception { /** Check that an xdot file can be generated from DOT source */ @Test - public void getGraphAsXDot() throws Exception { + public void getGraphAsXDot() throws IOException { Path dotSource = Paths.get("src/test/resources/graphviz/testWorkflow.dot"); Path xdot = graphVizService.getGraphPath("workflowid.dot", readFileToString(dotSource), "xdot"); String xdotString = readFileToString(xdot); - assertTrue(xdotString.length() > 0); + assertFalse(xdotString.isEmpty()); } /** Check that an xdot stream can be generated from DOT source */ @Test - public void getGraphAsXDotStream() throws Exception { + public void getGraphAsXDotStream() throws IOException { Path dotSource = Paths.get("src/test/resources/graphviz/testWorkflow.dot"); InputStream xdot = graphVizService.getGraphStream(readFileToString(dotSource), "xdot"); String xdotString = readStreamToString(xdot); - assertTrue(xdotString.length() > 0); + assertFalse(xdotString.isEmpty()); } /** Check that files in the graphVizFolder can be deleted with deleteCache() */ @Test - public void deleteCache() throws Exception { + public void deleteCache() { File png = new File(graphvizFolder, "exampleid.png"); File svg = new File(graphvizFolder, "exampleid.svg"); File dot = new File(graphvizFolder, "exampleid.dot"); - graphVizService.deleteCache("exampleid"); + graphVizService.deleteCache(UUID.randomUUID()); assertFalse(png.exists()); assertFalse(svg.exists()); diff --git a/src/test/java/org/commonwl/view/graphviz/ModelDotWriterTest.java b/src/test/java/org/commonwl/view/graphviz/ModelDotWriterTest.java index a30fd3f8..a8017d65 100644 --- a/src/test/java/org/commonwl/view/graphviz/ModelDotWriterTest.java +++ b/src/test/java/org/commonwl/view/graphviz/ModelDotWriterTest.java @@ -43,7 +43,7 @@ public class ModelDotWriterTest { * generation TODO: This is a pain, can it be made simpler? */ @BeforeEach - public void setUp() throws Exception { + public void setUp() { // Inputs Map inputs = new HashMap<>(); diff --git a/src/test/java/org/commonwl/view/researchobject/ROBundleFactoryTest.java b/src/test/java/org/commonwl/view/researchobject/ROBundleFactoryTest.java index e6cd3568..ed27e6aa 100644 --- a/src/test/java/org/commonwl/view/researchobject/ROBundleFactoryTest.java +++ b/src/test/java/org/commonwl/view/researchobject/ROBundleFactoryTest.java @@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import org.commonwl.view.git.GitDetails; @@ -49,7 +50,8 @@ public void bundleForValidWorkflow() throws Exception { // Mocked path to a RO bundle ROBundleService mockROBundleService = Mockito.mock(ROBundleService.class); - when(mockROBundleService.saveToFile(any())).thenReturn(Paths.get("test/path/to/check/for.zip")); + Path zipPath = Paths.get("test/path/to/check/for.zip"); + when(mockROBundleService.saveToFile(any())).thenReturn(zipPath); // Test method retries multiple times to get workflow model before success WorkflowRepository mockRepository = Mockito.mock(WorkflowRepository.class); @@ -64,7 +66,6 @@ public void bundleForValidWorkflow() throws Exception { // Attempt to add RO to workflow factory.createWorkflowRO(validWorkflow); - assertEquals( - Paths.get("test/path/to/check/for.zip"), Paths.get(validWorkflow.getRoBundlePath())); + assertEquals(zipPath, Paths.get(validWorkflow.getRoBundlePath())); } } diff --git a/src/test/java/org/commonwl/view/researchobject/ROBundleServiceTest.java b/src/test/java/org/commonwl/view/researchobject/ROBundleServiceTest.java index 29f3540a..caa96ccc 100644 --- a/src/test/java/org/commonwl/view/researchobject/ROBundleServiceTest.java +++ b/src/test/java/org/commonwl/view/researchobject/ROBundleServiceTest.java @@ -33,6 +33,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; import org.apache.jena.query.ResultSet; import org.apache.taverna.robundle.Bundle; import org.apache.taverna.robundle.Bundles; @@ -52,7 +53,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public class ROBundleServiceTest { @@ -130,7 +130,7 @@ public void setUp() throws Exception { "933bf2a1a1cce32d88f88f136275535da9df0954", "workflows/lobSTR/lobSTR-workflow.cwl"); lobSTRdraft3 = Mockito.mock(Workflow.class); - when(lobSTRdraft3.getID()).thenReturn("testID"); + when(lobSTRdraft3.getId()).thenReturn(UUID.randomUUID()); when(lobSTRdraft3.getRetrievedFrom()).thenReturn(lobSTRdraft3Details); when(lobSTRdraft3.getLastCommit()).thenReturn("933bf2a1a1cce32d88f88f136275535da9df0954"); final String permalink = @@ -140,13 +140,11 @@ public void setUp() throws Exception { when(lobSTRdraft3.getPermalink()).thenReturn(permalink); when(lobSTRdraft3.getPermalink(any())) .thenAnswer( - new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - return permalink + "?format=" + args[0]; - } - }); + (Answer) + invocation -> { + Object[] args = invocation.getArguments(); + return permalink + "?format=" + args[0]; + }); } /** Use a temporary directory for testing */ @@ -174,7 +172,7 @@ public void generateAndSaveROBundle() throws Exception { Manifest manifest = bundle.getManifest(); assertEquals("CWL Viewer", manifest.getCreatedBy().getName()); assertEquals("https://view.commonwl.org", manifest.getCreatedBy().getUri().toString()); - assertEquals("Mark Robinson", manifest.getAuthoredBy().get(0).getName()); + assertEquals("Mark Robinson", manifest.getAuthoredBy().getFirst().getName()); assertEquals( new URI( "https://w3id.org/cwl/view/git/933bf2a1a1cce32d88f88f136275535da9df0954/workflows/lobSTR/lobSTR-workflow.cwl"), @@ -192,10 +190,10 @@ public void generateAndSaveROBundle() throws Exception { assertEquals( "https://w3id.org/cwl/view/git/933bf2a1a1cce32d88f88f136275535da9df0954/lobstr-draft3/lobSTR-workflow.cwl?format=raw", cwlAggregate.getRetrievedFrom().toString()); - assertEquals("Mark Robinson", cwlAggregate.getAuthoredBy().get(0).getName()); + assertEquals("Mark Robinson", cwlAggregate.getAuthoredBy().getFirst().getName()); assertEquals( - "mailto:mark@example.com", cwlAggregate.getAuthoredBy().get(0).getUri().toString()); - assertNull(cwlAggregate.getAuthoredBy().get(0).getOrcid()); + "mailto:mark@example.com", cwlAggregate.getAuthoredBy().getFirst().getUri().toString()); + assertNull(cwlAggregate.getAuthoredBy().getFirst().getOrcid()); assertEquals("text/x-yaml", cwlAggregate.getMediatype()); assertEquals("https://w3id.org/cwl/draft-3", cwlAggregate.getConformsTo().toString()); @@ -216,12 +214,13 @@ public void generateAndSaveROBundle() throws Exception { assertEquals(1, history.size()); assertEquals( "http:/git2prov.org/git2prov?giturl=https:/github.com/common-workflow-language/workflows.git&serialization=PROV-JSON", - history.get(0).toString()); + history.getFirst().toString()); // Save and check it exists in the temporary folder roBundleService.saveToFile(bundle); File[] fileList = roBundleFolder.listFiles(); - assertTrue(fileList.length == 1); + assertNotNull(fileList); + assertEquals(1, fileList.length); for (File ro : fileList) { assertTrue(ro.getName().endsWith(".zip")); Bundle savedBundle = Bundles.openBundle(ro.toPath()); @@ -252,6 +251,6 @@ public void filesOverLimit() throws Exception { manifest.getAggregation( new URI( "https://w3id.org/cwl/view/git/933bf2a1a1cce32d88f88f136275535da9df0954/lobstr-draft3/models/illumina_v3.pcrfree.stepmodel?format=raw")); - assertEquals("Mark Robinson", urlAggregate.getAuthoredBy().get(0).getName()); + assertEquals("Mark Robinson", urlAggregate.getAuthoredBy().getFirst().getName()); } } diff --git a/src/test/java/org/commonwl/view/util/FileUtilsTest.java b/src/test/java/org/commonwl/view/util/FileUtilsTest.java index ef3330c2..0371511a 100644 --- a/src/test/java/org/commonwl/view/util/FileUtilsTest.java +++ b/src/test/java/org/commonwl/view/util/FileUtilsTest.java @@ -17,15 +17,14 @@ import org.commonwl.view.git.GitDetails; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.Repository; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullSource; -import org.junit.rules.TemporaryFolder; import org.mockito.MockedStatic; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; +import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; /** @@ -33,17 +32,16 @@ * * @since 1.4.6 */ +@ExtendWith(MockitoExtension.class) public class FileUtilsTest { - @Rule public MockitoRule initRule = MockitoJUnit.rule(); - - @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); + @TempDir public Path temporaryFolder; // --- git files @ParameterizedTest @NullSource - public void testNullRepository(Git repository) throws IOException { + void testNullRepository(Git repository) throws IOException { try (MockedStatic fileUtilsMocked = mockStatic(org.apache.commons.io.FileUtils.class)) { fileUtilsMocked @@ -55,7 +53,7 @@ public void testNullRepository(Git repository) throws IOException { } @Test - public void testRepositoryNullRepository() throws IOException { + void testRepositoryNullRepository() throws IOException { Git repository = mock(Git.class); when(repository.getRepository()).thenReturn(null); try (MockedStatic fileUtilsMocked = @@ -69,7 +67,7 @@ public void testRepositoryNullRepository() throws IOException { } @Test - public void testRepositoryRepositoryNullDirectory() throws IOException { + void testRepositoryRepositoryNullDirectory() throws IOException { Git repository = mock(Git.class); Repository repository1 = mock(Repository.class); when(repository.getRepository()).thenReturn(repository1); @@ -85,7 +83,7 @@ public void testRepositoryRepositoryNullDirectory() throws IOException { } @Test - public void testRepositoryRepositoryDirectoryDoesNotExist() throws IOException { + void testRepositoryRepositoryDirectoryDoesNotExist() throws IOException { Git repository = mock(Git.class); Repository repository1 = mock(Repository.class); when(repository.getRepository()).thenReturn(repository1); @@ -102,22 +100,22 @@ public void testRepositoryRepositoryDirectoryDoesNotExist() throws IOException { } @Test - public void testRepositoryRepositoryDirectory() throws IOException { + void testRepositoryRepositoryDirectory() throws IOException { Git repository = mock(Git.class); Repository repository1 = mock(Repository.class); when(repository.getRepository()).thenReturn(repository1); - File gitRepository = temporaryFolder.newFile(".git"); - when(repository1.getDirectory()).thenReturn(gitRepository); - assertTrue(gitRepository.exists()); + Path gitRepository = Files.createFile(temporaryFolder.resolve(".git")); + when(repository1.getDirectory()).thenReturn(gitRepository.toFile()); + assertTrue(Files.exists(gitRepository)); FileUtils.deleteGitRepository(repository); - assertFalse(gitRepository.exists()); + assertFalse(Files.exists(gitRepository)); } // --- bundle temporary files @ParameterizedTest @NullSource - public void testBundleTemporaryDirectoryNullBundle(Bundle bundle) throws IOException { + void testBundleTemporaryDirectoryNullBundle(Bundle bundle) throws IOException { try (MockedStatic fileUtilsMocked = mockStatic(org.apache.commons.io.FileUtils.class)) { fileUtilsMocked @@ -129,7 +127,7 @@ public void testBundleTemporaryDirectoryNullBundle(Bundle bundle) throws IOExcep } @Test - public void testBundleTemporaryDirectoryNoParentFileName() throws IOException { + void testBundleTemporaryDirectoryNoParentFileName() throws IOException { try (MockedStatic fileUtilsMocked = mockStatic(org.apache.commons.io.FileUtils.class)) { fileUtilsMocked @@ -145,7 +143,7 @@ public void testBundleTemporaryDirectoryNoParentFileName() throws IOException { } @Test - public void testBundleTemporaryDirectoryDoesNotExist() throws IOException { + void testBundleTemporaryDirectoryDoesNotExist() throws IOException { try (MockedStatic fileUtilsMocked = mockStatic(org.apache.commons.io.FileUtils.class)) { fileUtilsMocked @@ -162,7 +160,7 @@ public void testBundleTemporaryDirectoryDoesNotExist() throws IOException { } @Test - public void testBundleTemporaryDirectoryNotEmpty() throws IOException { + void testBundleTemporaryDirectoryNotEmpty() throws IOException { try (MockedStatic fileUtilsMocked = mockStatic(org.apache.commons.io.FileUtils.class)) { fileUtilsMocked @@ -172,35 +170,35 @@ public void testBundleTemporaryDirectoryNotEmpty() throws IOException { BundleFileSystem fileSystem = mock(BundleFileSystem.class); when(bundle.getFileSystem()).thenReturn(fileSystem); final String hash = "bundle.zip"; - File bundleTemporaryDirectory = temporaryFolder.newFile(hash); - when(fileSystem.getSource()).thenReturn(bundleTemporaryDirectory.toPath()); + Path bundleTemporaryDirectory = Files.createFile(temporaryFolder.resolve(hash)); + when(fileSystem.getSource()).thenReturn(bundleTemporaryDirectory); FileUtils.deleteBundleTemporaryDirectory(bundle); fileUtilsMocked.verifyNoInteractions(); } } @Test - public void testBundleTemporaryDirectory() throws IOException { + void testBundleTemporaryDirectory() throws IOException { Bundle bundle = mock(Bundle.class); BundleFileSystem fileSystem = mock(BundleFileSystem.class); when(bundle.getFileSystem()).thenReturn(fileSystem); final String hash = "bundle.zip"; - File bundleTemporaryDirectory = temporaryFolder.newFile(hash); + Path bundleTemporaryDirectory = Files.createFile(temporaryFolder.resolve(hash)); // We want an empty temporary directory, so we can delete the bundle.zip file // (in real-life it will have been moved from the temporary location to the // bundles' directory). - org.apache.commons.io.FileUtils.forceDelete(bundleTemporaryDirectory); - when(fileSystem.getSource()).thenReturn(bundleTemporaryDirectory.toPath()); - assertTrue(bundleTemporaryDirectory.getParentFile().exists()); + Files.deleteIfExists(bundleTemporaryDirectory); + when(fileSystem.getSource()).thenReturn(bundleTemporaryDirectory); + assertTrue(Files.exists(bundleTemporaryDirectory.getParent())); FileUtils.deleteBundleTemporaryDirectory(bundle); - assertFalse(bundleTemporaryDirectory.exists()); + assertFalse(Files.exists(bundleTemporaryDirectory)); } // --- bundle parent directory @ParameterizedTest @NullSource - public void testBundleParentDirectoryNullBundle(Bundle bundle) throws IOException { + void testBundleParentDirectoryNullBundle(Bundle bundle) throws IOException { try (MockedStatic fileUtilsMocked = mockStatic(org.apache.commons.io.FileUtils.class)) { fileUtilsMocked @@ -212,7 +210,7 @@ public void testBundleParentDirectoryNullBundle(Bundle bundle) throws IOExceptio } @Test - public void testBundleParentDirectoryDoesNotExist() throws IOException { + void testBundleParentDirectoryDoesNotExist() throws IOException { try (MockedStatic fileUtilsMocked = mockStatic(org.apache.commons.io.FileUtils.class)) { fileUtilsMocked @@ -226,7 +224,7 @@ public void testBundleParentDirectoryDoesNotExist() throws IOException { } @Test - public void testBundleParentDirectoryWithTooManyFiles() throws IOException { + void testBundleParentDirectoryWithTooManyFiles() throws IOException { try (MockedStatic fileUtilsMocked = mockStatic(org.apache.commons.io.FileUtils.class)) { fileUtilsMocked @@ -234,35 +232,36 @@ public void testBundleParentDirectoryWithTooManyFiles() throws IOException { .thenAnswer((Answer) invocation -> null); Bundle bundle = mock(Bundle.class); final String hash = "bundle.zip"; - File bundleTemporaryDirectory = temporaryFolder.newFile(hash); + Path bundleTemporaryDirectory = Files.createFile(temporaryFolder.resolve(hash)); for (int i = 0; i < 3; i++) { - temporaryFolder.newFile("file-" + i); + Files.createFile(temporaryFolder.resolve("file-" + i)); } - when(bundle.getSource()).thenReturn(bundleTemporaryDirectory.toPath()); + when(bundle.getSource()).thenReturn(bundleTemporaryDirectory); FileUtils.deleteBundleParentDirectory(bundle); fileUtilsMocked.verifyNoInteractions(); } } @Test - public void testBundleParentDirectory() throws IOException { + void testBundleParentDirectory() throws IOException { Bundle bundle = mock(Bundle.class); final String hash = "bundle.zip"; - File bundleTemporaryDirectory = temporaryFolder.newFile(hash); - when(bundle.getSource()).thenReturn(bundleTemporaryDirectory.toPath()); - assertTrue(bundleTemporaryDirectory.getParentFile().exists()); + Path bundleTemporaryDirectory = Files.createFile(temporaryFolder.resolve(hash)); + when(bundle.getSource()).thenReturn(bundleTemporaryDirectory); + assertTrue(Files.exists(bundleTemporaryDirectory.getParent())); FileUtils.deleteBundleParentDirectory(bundle); - assertFalse(bundleTemporaryDirectory.exists()); + assertFalse(Files.exists(bundleTemporaryDirectory)); } @Test - public void testRemoveTemporaryRepository() throws IOException { + void testRemoveTemporaryRepository() throws IOException { Git tempRepository = mock(Git.class); Repository tempRepository1 = mock(Repository.class); when(tempRepository.getRepository()).thenReturn(tempRepository1); - File tempGitRepositoryParent = temporaryFolder.newFolder(String.valueOf(UUID.randomUUID())); - File tempGitRepository = tempGitRepositoryParent.toPath().resolve(".git").toFile(); - Files.createDirectory(tempGitRepository.toPath()); + Path tempGitRepositoryParent = + Files.createDirectories(temporaryFolder.resolve(String.valueOf(UUID.randomUUID()))); + File tempGitRepository = tempGitRepositoryParent.resolve(".git").toFile(); + Files.createDirectories(tempGitRepository.toPath()); when(tempRepository1.getDirectory()).thenReturn(tempGitRepository); assertTrue(tempGitRepository.exists()); FileUtils.deleteTemporaryGitRepository(tempRepository); @@ -271,13 +270,14 @@ public void testRemoveTemporaryRepository() throws IOException { Git notTempRepository = mock(Git.class); Repository notTempRepository1 = mock(Repository.class); when(notTempRepository.getRepository()).thenReturn(notTempRepository1); - File notTempGitRepositoryParent = - temporaryFolder.newFolder( - DigestUtils.sha1Hex( - GitDetails.normaliseUrl( - "https://github.com/common-workflow-language/cwlviewer.git"))); - File notTempGitRepository = notTempGitRepositoryParent.toPath().resolve(".git").toFile(); - Files.createDirectory(notTempGitRepository.toPath()); + Path notTempGitRepositoryParent = + Files.createDirectories( + Path.of( + DigestUtils.sha1Hex( + GitDetails.normaliseUrl( + "https://github.com/common-workflow-language/cwlviewer.git")))); + File notTempGitRepository = notTempGitRepositoryParent.resolve(".git").toFile(); + Files.createDirectories(notTempGitRepository.toPath()); when(notTempRepository1.getDirectory()).thenReturn(notTempGitRepository); assertTrue(notTempGitRepository.exists()); FileUtils.deleteTemporaryGitRepository(notTempRepository); diff --git a/src/test/java/org/commonwl/view/workflow/PostgreSQLContextInitializer.java b/src/test/java/org/commonwl/view/workflow/PostgreSQLContextInitializer.java index 7e52973a..d5692aa2 100644 --- a/src/test/java/org/commonwl/view/workflow/PostgreSQLContextInitializer.java +++ b/src/test/java/org/commonwl/view/workflow/PostgreSQLContextInitializer.java @@ -29,11 +29,6 @@ public void initialize(ConfigurableApplicationContext applicationContext) { "spring.jpa.hibernate.ddl-auto=create") .applyTo(applicationContext.getEnvironment()); applicationContext.addApplicationListener( - new ApplicationListener() { - @Override - public void onApplicationEvent(ContextClosedEvent event) { - postgreSQLContainer.stop(); - } - }); + (ApplicationListener) event -> postgreSQLContainer.stop()); } } diff --git a/src/test/java/org/commonwl/view/workflow/QueuedWorkflowRepositoryTest.java b/src/test/java/org/commonwl/view/workflow/QueuedWorkflowRepositoryTest.java index e19d189a..1d3df2e7 100644 --- a/src/test/java/org/commonwl/view/workflow/QueuedWorkflowRepositoryTest.java +++ b/src/test/java/org/commonwl/view/workflow/QueuedWorkflowRepositoryTest.java @@ -8,15 +8,15 @@ import org.commonwl.view.git.GitDetails; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; +import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @TestPropertySource(locations = "classpath:it-application.properties") -@DataJpaTest(showSql = true) +@DataJpaTest() @Transactional(propagation = Propagation.NOT_SUPPORTED) @ContextConfiguration(initializers = PostgreSQLContextInitializer.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @@ -53,8 +53,7 @@ public void deleteQueuedWorkflowByRetrievedFromTest() { assertNotNull(retrievedQueuedWorkflowAfterSave); // delete saved queued workflow by workflow git details - repository.deleteByTempRepresentation_RetrievedFrom( - queuedWorkflow.getTempRepresentation().getRetrievedFrom()); + repository.deleteByRetrievedFrom(queuedWorkflow.getTempRepresentation().getRetrievedFrom()); // retrieve deleted queued workflow by workflow git details QueuedWorkflow retrievedQueuedWorkflowAfterDelete = diff --git a/src/test/java/org/commonwl/view/workflow/WorkflowControllerTest.java b/src/test/java/org/commonwl/view/workflow/WorkflowControllerTest.java index 8f0c7c42..4a7aac22 100644 --- a/src/test/java/org/commonwl/view/workflow/WorkflowControllerTest.java +++ b/src/test/java/org/commonwl/view/workflow/WorkflowControllerTest.java @@ -40,7 +40,6 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -58,7 +57,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.mockito.Mockito; -import org.springframework.core.io.PathResource; +import org.springframework.core.io.FileSystemResource; import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @@ -222,11 +221,7 @@ public void errorCreatingQueuedWorkflowDeletesTemporaryFiles() when(repository.getDirectory()).thenReturn(temporaryFile); when(git.getRepository()).thenThrow(RuntimeException.class).thenReturn(repository); assertTrue(temporaryFile.exists()); - assertThrows( - RuntimeException.class, - () -> { - service.createQueuedWorkflow(gitDetails); - }); + assertThrows(RuntimeException.class, () -> service.createQueuedWorkflow(gitDetails)); assertFalse(temporaryFile.exists()); } @@ -372,12 +367,12 @@ public void downloadGraphVizFiles() throws Exception { // Mock service to return mock workflow WorkflowService mockWorkflowService = Mockito.mock(WorkflowService.class); when(mockWorkflowService.getWorkflowGraph(any(String.class), Mockito.any(GitDetails.class))) - .thenReturn(new PathResource(Paths.get("src/test/resources/graphviz/testVis.svg"))) - .thenReturn(new PathResource(Paths.get("src/test/resources/graphviz/testVis.png"))) - .thenReturn(new PathResource(Paths.get("src/test/resources/graphviz/testWorkflow.dot"))) - .thenReturn(new PathResource(Paths.get("src/test/resources/graphviz/testVis.svg"))) - .thenReturn(new PathResource(Paths.get("src/test/resources/graphviz/testVis.png"))) - .thenReturn(new PathResource(Paths.get("src/test/resources/graphviz/testWorkflow.dot"))) + .thenReturn(new FileSystemResource("src/test/resources/graphviz/testVis.svg")) + .thenReturn(new FileSystemResource("src/test/resources/graphviz/testVis.png")) + .thenReturn(new FileSystemResource("src/test/resources/graphviz/testWorkflow.dot")) + .thenReturn(new FileSystemResource("src/test/resources/graphviz/testVis.svg")) + .thenReturn(new FileSystemResource("src/test/resources/graphviz/testVis.png")) + .thenReturn(new FileSystemResource("src/test/resources/graphviz/testWorkflow.dot")) .thenThrow(new WorkflowNotFoundException()); // Mock controller/MVC diff --git a/src/test/java/org/commonwl/view/workflow/WorkflowFormTest.java b/src/test/java/org/commonwl/view/workflow/WorkflowFormTest.java index 433da8ee..e4a20e16 100644 --- a/src/test/java/org/commonwl/view/workflow/WorkflowFormTest.java +++ b/src/test/java/org/commonwl/view/workflow/WorkflowFormTest.java @@ -27,7 +27,7 @@ public class WorkflowFormTest { /** Test for the form stripping unnecessary trailing slashes from directory URLs */ @Test - public void getURL() throws Exception { + public void getURL() { String unchangedURL = "https://github.com/common-workflow-language/workflows/tree/master/workflows/compile"; diff --git a/src/test/java/org/commonwl/view/workflow/WorkflowFormValidatorTest.java b/src/test/java/org/commonwl/view/workflow/WorkflowFormValidatorTest.java index e12fac70..12a9f24e 100644 --- a/src/test/java/org/commonwl/view/workflow/WorkflowFormValidatorTest.java +++ b/src/test/java/org/commonwl/view/workflow/WorkflowFormValidatorTest.java @@ -19,7 +19,11 @@ package org.commonwl.view.workflow; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.commonwl.view.git.GitDetails; import org.junit.jupiter.api.Test; @@ -30,12 +34,11 @@ public class WorkflowFormValidatorTest { /** Workflow form validator to test */ - private WorkflowFormValidator workflowFormValidator = new WorkflowFormValidator(); + private final WorkflowFormValidator workflowFormValidator = new WorkflowFormValidator(); - /** Github File URL */ + /** GitHub File URL */ @Test - public void githubUrl() throws Exception { - + public void gitHubUrl() { WorkflowForm githubUrl = new WorkflowForm( "https://github.com/nlesc-sherlock/deeplearning/blob/master/CWLworkflow/pipeline.cwl"); @@ -51,8 +54,7 @@ public void githubUrl() throws Exception { /** Gitlab File URL */ @Test - public void gitlabUrl() throws Exception { - + public void gitLabUrl() { WorkflowForm gitlabUrl = new WorkflowForm( "https://gitlab.com/unduthegun/stellaris-emblem-lab/blob/cwl/textures/textures.cwl"); @@ -68,15 +70,15 @@ public void gitlabUrl() throws Exception { /** Generic File URL */ @Test - public void genericUrl() throws Exception { + public void genericGitUrl() { - WorkflowForm genericUrl = + WorkflowForm genericGitUrl = new WorkflowForm("https://bitbucket.org/markrobinson96/workflows.git"); - genericUrl.setBranch("branchName"); - genericUrl.setPath("path/to/workflow.cwl"); + genericGitUrl.setBranch("branchName"); + genericGitUrl.setPath("path/to/workflow.cwl"); - Errors errors = new BeanPropertyBindingResult(genericUrl, "workflowForm"); - GitDetails details = workflowFormValidator.validateAndParse(genericUrl, errors); + Errors errors = new BeanPropertyBindingResult(genericGitUrl, "workflowForm"); + GitDetails details = workflowFormValidator.validateAndParse(genericGitUrl, errors); assertNotNull(details); assertEquals("https://bitbucket.org/markrobinson96/workflows.git", details.getRepoUrl()); @@ -87,8 +89,7 @@ public void genericUrl() throws Exception { /** Packed URL */ @Test - public void packedUrl() throws Exception { - + public void packedUrl() { WorkflowForm githubUrl = new WorkflowForm( "https://github.com/MarkRobbo/workflows/tree/master/packed.cwl#workflowId"); @@ -105,8 +106,7 @@ public void packedUrl() throws Exception { /** Empty URL */ @Test - public void emptyURL() throws Exception { - + public void emptyURL() { WorkflowForm emptyURL = new WorkflowForm(""); Errors errors = new BeanPropertyBindingResult(emptyURL, "workflowForm"); @@ -118,8 +118,7 @@ public void emptyURL() throws Exception { /** Invalid URL */ @Test - public void invalidURL() throws Exception { - + public void invalidURL() { WorkflowForm invalidURL = new WorkflowForm("https://google.com/clearly/not/github/url"); Errors errors = new BeanPropertyBindingResult(invalidURL, "workflowForm"); @@ -131,15 +130,16 @@ public void invalidURL() throws Exception { /** Generic File URL without branch or path */ @Test - public void noBranchOrPath() throws Exception { - + public void noBranchOrPath() { WorkflowForm genericUrl = new WorkflowForm("https://bitbucket.org/markrobinson96/workflows.git"); Errors errors = new BeanPropertyBindingResult(genericUrl, "workflowForm"); GitDetails details = workflowFormValidator.validateAndParse(genericUrl, errors); - assertNull(details); + assertNotNull(details); + assertEquals("master", details.getBranch()); + assertEquals("/", details.getPath()); assertTrue(errors.hasErrors()); } } diff --git a/src/test/java/org/commonwl/view/workflow/WorkflowJSONControllerTest.java b/src/test/java/org/commonwl/view/workflow/WorkflowJSONControllerTest.java index 04d13b3f..61d411d2 100644 --- a/src/test/java/org/commonwl/view/workflow/WorkflowJSONControllerTest.java +++ b/src/test/java/org/commonwl/view/workflow/WorkflowJSONControllerTest.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; import org.commonwl.view.cwl.CWLToolStatus; import org.commonwl.view.cwl.CWLValidationException; import org.commonwl.view.git.GitDetails; @@ -47,6 +48,7 @@ public class WorkflowJSONControllerTest { @Test public void newWorkflowFromGithubURLJson() throws Exception { + final var uuid = UUID.randomUUID(); // Validator pass or fail WorkflowFormValidator mockValidator = Mockito.mock(WorkflowFormValidator.class); @@ -63,7 +65,7 @@ public void newWorkflowFromGithubURLJson() throws Exception { .thenReturn( new GitDetails("https://github.com/owner/repoName.git", "branch", "path/workflow.cwl")); QueuedWorkflow mockQueuedWorkflow = Mockito.mock(QueuedWorkflow.class); - when(mockQueuedWorkflow.getId()).thenReturn("123"); + when(mockQueuedWorkflow.getId()).thenReturn(uuid); when(mockQueuedWorkflow.getTempRepresentation()).thenReturn(mockWorkflow); List listOfTwoOverviews = new ArrayList<>(); listOfTwoOverviews.add(new WorkflowOverview("#packedId1", "label", "doc")); @@ -124,7 +126,7 @@ public void newWorkflowFromGithubURLJson() throws Exception { .param("url", "https://github.com/owner/repoName/tree/branch/path/success.cwl") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isAccepted()) - .andExpect(header().string("Location", is("/queue/123"))); + .andExpect(header().string("Location", is("/queue/" + uuid))); // Packed workflow with one ID is still accepted and parsed using that ID mockMvc @@ -133,7 +135,7 @@ public void newWorkflowFromGithubURLJson() throws Exception { .param("url", "https://github.com/owner/repoName/tree/branch/path/singlePacked.cwl") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isAccepted()) - .andExpect(header().string("Location", is("/queue/123"))); + .andExpect(header().string("Location", is("/queue/" + uuid))); // Packed workflow with multiple IDs is unprocessable mockMvc @@ -142,7 +144,7 @@ public void newWorkflowFromGithubURLJson() throws Exception { .param( "url", "https://github.com/owner/repoName/tree/branch/path/multiplePacked.cwl") .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isUnprocessableEntity()) + .andExpect(status().isUnprocessableContent()) .andExpect( jsonPath( "$.message", @@ -193,6 +195,8 @@ public void getWorkflowByGithubDetailsJson() throws Exception { @Test public void checkQueue() throws Exception { + final var uuid = UUID.randomUUID(); + QueuedWorkflow qwfRunning = new QueuedWorkflow(); qwfRunning.setCwltoolStatus(CWLToolStatus.RUNNING); qwfRunning.setCwltoolVersion("v1.0"); @@ -223,12 +227,12 @@ public void checkQueue() throws Exception { // No workflow mockMvc - .perform(get("/queue/123").accept(MediaType.APPLICATION_JSON)) + .perform(get("/queue/" + uuid).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); // Running workflow mockMvc - .perform(get("/queue/123").accept(MediaType.APPLICATION_JSON)) + .perform(get("/queue/" + uuid).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.cwltoolStatus", is("RUNNING"))) @@ -236,7 +240,7 @@ public void checkQueue() throws Exception { // Error workflow mockMvc - .perform(get("/queue/123").accept(MediaType.APPLICATION_JSON)) + .perform(get("/queue/" + uuid).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.cwltoolStatus", is("ERROR"))) @@ -245,7 +249,7 @@ public void checkQueue() throws Exception { // Success workflow mockMvc - .perform(get("/queue/123").accept(MediaType.APPLICATION_JSON)) + .perform(get("/queue/" + uuid).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isSeeOther()) .andExpect( header() diff --git a/src/test/java/org/commonwl/view/workflow/WorkflowPermalinkControllerTest.java b/src/test/java/org/commonwl/view/workflow/WorkflowPermalinkControllerTest.java index 5ddbfe6c..91abbe12 100644 --- a/src/test/java/org/commonwl/view/workflow/WorkflowPermalinkControllerTest.java +++ b/src/test/java/org/commonwl/view/workflow/WorkflowPermalinkControllerTest.java @@ -19,8 +19,8 @@ package org.commonwl.view.workflow; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.any; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -31,7 +31,6 @@ import java.io.FileInputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Optional; import org.apache.commons.io.IOUtils; import org.commonwl.view.cwl.RDFService; @@ -40,7 +39,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.mockito.Mockito; -import org.springframework.core.io.PathResource; +import org.springframework.core.io.FileSystemResource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -50,12 +49,12 @@ public class WorkflowPermalinkControllerTest { @TempDir private Path tempDir; private MockMvc mockMvc; private byte[] rdfResponse; - private final PathResource png = - new PathResource(Paths.get("src/test/resources/graphviz/testVis.png")); - private final PathResource svg = - new PathResource(Paths.get("src/test/resources/graphviz/testVis.svg")); - private final PathResource dot = - new PathResource(Paths.get("src/test/resources/graphviz/testWorkflow.dot")); + private final FileSystemResource png = + new FileSystemResource("src/test/resources/graphviz/testVis.png"); + private final FileSystemResource svg = + new FileSystemResource("src/test/resources/graphviz/testVis.svg"); + private final FileSystemResource dot = + new FileSystemResource("src/test/resources/graphviz/testWorkflow.dot"); @BeforeEach public void setUp() throws Exception { @@ -67,8 +66,9 @@ public void setUp() throws Exception { "https://github.com/MarkRobbo/workflows.git", "master", "path/to/workflow.cwl")); WorkflowService mockWorkflowService = Mockito.mock(WorkflowService.class); + //noinspection unchecked when(mockWorkflowService.findByCommitAndPath( - any(String.class), any(String.class), any(Optional.class))) + any(String.class), any(String.class), (Optional) any(Optional.class))) .thenReturn(mockWorkflow); when(mockWorkflowService.findRawBaseForCommit(any(String.class))) diff --git a/src/test/java/org/commonwl/view/workflow/WorkflowRepositoryTest.java b/src/test/java/org/commonwl/view/workflow/WorkflowRepositoryTest.java index bc2bd8dc..a2c89ccb 100644 --- a/src/test/java/org/commonwl/view/workflow/WorkflowRepositoryTest.java +++ b/src/test/java/org/commonwl/view/workflow/WorkflowRepositoryTest.java @@ -26,15 +26,15 @@ import org.commonwl.view.git.GitDetails; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; +import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @TestPropertySource(locations = "classpath:it-application.properties") -@DataJpaTest(showSql = true) +@DataJpaTest() @Transactional(propagation = Propagation.NOT_SUPPORTED) @ContextConfiguration(initializers = PostgreSQLContextInitializer.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) diff --git a/src/test/java/org/commonwl/view/workflow/WorkflowServiceTest.java b/src/test/java/org/commonwl/view/workflow/WorkflowServiceTest.java index 13fd82d0..c00e7e91 100644 --- a/src/test/java/org/commonwl/view/workflow/WorkflowServiceTest.java +++ b/src/test/java/org/commonwl/view/workflow/WorkflowServiceTest.java @@ -30,6 +30,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.UUID; import org.commonwl.view.cwl.CWLService; import org.commonwl.view.cwl.CWLToolRunner; import org.commonwl.view.git.GitDetails; @@ -48,10 +49,6 @@ public class WorkflowServiceTest { /** Folder for test research object bundles */ @TempDir public Path roBundleFolder; - /** Retry the running of cwltool */ - @Test - public void retryCwltoolGeneration() throws Exception {} - /** Getting a list of workflow overviews from a directory */ @Test public void getWorkflowsFromDirectory() throws Exception { @@ -92,13 +89,13 @@ public void getWorkflowsFromDirectory() throws Exception { // 1 workflow should be found assertEquals(2, list.size()); - assertEquals("workflow.cwl", list.get(0).getFileName()); - assertEquals("label", list.get(0).getLabel()); - assertEquals("doc", list.get(0).getDoc()); + assertEquals("workflow.cwl", list.getFirst().fileName()); + assertEquals("label", list.getFirst().label()); + assertEquals("doc", list.getFirst().doc()); - assertEquals("workflow2.cwl", list.get(1).getFileName()); - assertEquals("label2", list.get(1).getLabel()); - assertEquals("doc2", list.get(1).getDoc()); + assertEquals("workflow2.cwl", list.get(1).fileName()); + assertEquals("label2", list.get(1).label()); + assertEquals("doc2", list.get(1).doc()); } /** Getting a workflow when cache has expired And a new workflow needs to be created */ @@ -116,7 +113,7 @@ public void getWorkflowCacheHasExpired() throws Exception { new HashMap<>(), new HashMap<>(), new HashMap<>()); - oldWorkflow.setId("theworkflowid"); + oldWorkflow.setId(UUID.randomUUID()); oldWorkflow.setRetrievedOn(new Date()); oldWorkflow.setRetrievedFrom(githubInfo); oldWorkflow.setLastCommit("d46ce365f1a10c4c4d6b0caed51c6f64b84c2f63"); @@ -130,7 +127,7 @@ public void getWorkflowCacheHasExpired() throws Exception { new HashMap<>(), new HashMap<>(), new HashMap<>()); - updatedWorkflow.setId("newworkflowid"); + updatedWorkflow.setId(UUID.randomUUID()); WorkflowRepository mockWorkflowRepo = Mockito.mock(WorkflowRepository.class); when(mockWorkflowRepo.findByRetrievedFrom(any())).thenReturn(oldWorkflow); diff --git a/src/test/resources/it-application.properties b/src/test/resources/it-application.properties index 334a196e..d91685e0 100644 --- a/src/test/resources/it-application.properties +++ b/src/test/resources/it-application.properties @@ -1,4 +1,4 @@ -spring.jpa.hibernate.ddl-auto=create +spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true #to show sql spring.jpa.properties.hibernate.show_sql=true