diff --git a/CITATION.cff b/CITATION.cff
index af5746b..4fb1023 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -9,8 +9,8 @@ type: software
authors:
- given-names: Vedran
family-names: Kasalica
- email: v.kasalica@esciencecenter.nl
- affiliation: Netherlands eScience Center, Netherlands
+ email: vedran.kasalica@health-ri.nl
+ affiliation: Health-RI, Netherlands
orcid: 'https://orcid.org/0000-0002-0097-1056'
- given-names: Peter
family-names: Kok
diff --git a/pom.xml b/pom.xml
index 53622a9..c1c22b4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,9 +8,9 @@
3.0.4
- nl.esciencecenter
+ org.workflomics
restape
- 1.0.0
+ 1.1.0
restape
RESTful API for the APE library
@@ -23,10 +23,16 @@
Vedran Kasalica
- v.kasalica@esciencecenter.nl
- Netherlands eScience Center
- https://www.esciencecenter.nl/
+ vedran.kasalica@health-ri.nl
+ Health-RI
+ https://www.health-ri.nl/
+
+ Mario Frank
+ mario.frank@uni-potsdam.de
+ University of Potsdam
+ https://www.uni-potsdam.de
+
scm:git:git://github.com/workflomics/restape.git
@@ -86,11 +92,11 @@
runtime
-
+
- io.github.sanctuuary
+ org.workflomics
APE
- 2.5.3
+ 2.6.1
diff --git a/src/main/java/nl/esciencecenter/controller/RestApeController.java b/src/main/java/nl/esciencecenter/controller/RestApeController.java
index 13a188f..75d5ec3 100644
--- a/src/main/java/nl/esciencecenter/controller/RestApeController.java
+++ b/src/main/java/nl/esciencecenter/controller/RestApeController.java
@@ -29,7 +29,8 @@
import io.swagger.v3.oas.annotations.Parameter;
import nl.esciencecenter.controller.dto.APEConfig;
import nl.esciencecenter.controller.dto.CWLFileInfo;
-import nl.esciencecenter.controller.dto.CWLZip;
+import nl.esciencecenter.controller.dto.SnakeFileInfo;
+import nl.esciencecenter.controller.dto.WorkflowsZip;
import nl.esciencecenter.controller.dto.ConstraintElem;
import nl.esciencecenter.controller.dto.ImgFileInfo;
import nl.esciencecenter.controller.dto.TaxonomyElem;
@@ -349,7 +350,7 @@ public ResponseEntity> postImage(
description = "Retrieve a cwl file from the file system, describing the workflow.",
tags = {"Download"},
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
- description = "JSON object containing the information for retrieving the CWL file, including the name of the CWL file (as 'figure_name') and the ID of the corresponding synthesis run.",
+ description = "JSON object containing the information for retrieving the CWL file, including the name of the CWL file (as 'cwl_name') and the ID of the corresponding synthesis run.",
required = true,
content = @Content(
mediaType = "application/json",
@@ -368,6 +369,40 @@ public ResponseEntity postCwl(
.body(IOUtils.getLocalCwlFile(path));
}
+ /**
+ * Retrieve the solution CWL workflow based on the provided run ID and a
+ * candidate solution.
+ *
+ * @param fileName Name of the Snakemake file (provided under 'name' after the
+ * synthesis run).
+ * @param runID ID of the corresponding synthesis run (provided under
+ * 'run_id' after the synthesis run).
+ * @return Snakemake file representing the workflow.
+ */
+ @PostMapping("/snakemake")
+ @Operation(summary = "Retrieve a Snakemake file",
+ description = "Retrieve a Snakemake file from the file system, describing the workflow.",
+ tags = {"Download"},
+ requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
+ description = "JSON object containing the information for retrieving the Snakemake file, including the name of the Snakemake file (as 'snakemake_name') and the ID of the corresponding synthesis run.",
+ required = true,
+ content = @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = SnakeFileInfo.class))
+ ),
+ responses = {
+ @ApiResponse(responseCode = "200", description = "Successful operation. Snakemake file describing the workflow is provided.", content = @Content(mediaType = "application/x-yaml")),
+ @ApiResponse(responseCode = "400", description = "Invalid input"),
+ @ApiResponse(responseCode = "404", description = "Not found")
+ })
+ public ResponseEntity postSnakemake(
+ @RequestBody(required = true) SnakeFileInfo snakeInfoJson) throws IOException {
+
+ Path path = snakeInfoJson.calculatePath();
+ return ResponseEntity.ok().contentType(MediaType.parseMediaType("application/x-yaml"))
+ .body(IOUtils.getLocalSnakemakeFile(path));
+ }
+
/**
* Retrieve the CWL input file based on the provided run ID.
*
@@ -461,26 +496,26 @@ public ResponseEntity getBenchmarks(
* @return CWL file representing the workflow.
*/
@PostMapping("/cwl_zip")
- @Operation(summary = "Retrieve the zip of cwl files.",
- description = "Retrieve the zip comprising CWL files specified in the request body. The request body should be a JSON object with the following fields: 'run_id' and 'workflows'. The 'run_id' field specifies the ID of the synthesis run, while the 'workflows' field is a list of CWL file names.",
+ @Operation(summary = "Retrieve the zip of cwl and Snakemake files.",
+ description = "Retrieve the zip comprising CWL and Snakemake files specified in the request body. The request body should be a JSON object with the following fields: 'run_id' and 'workflows'. The 'run_id' field specifies the ID of the synthesis run, while the 'workflows' field is a list of CWL and Snakemake file names.",
tags = {"Download"},
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "JSON object containing the following fields: 'run_id' and 'workflows'.",
required = true,
- content = @Content(schema = @Schema(implementation = CWLZip.class))
+ content = @Content(schema = @Schema(implementation = WorkflowsZip.class))
),
responses = {
@ApiResponse(responseCode = "200",
- description = "Successful operation. A zip file comprising specified CWL files is returned.",
+ description = "Successful operation. A zip file comprising specified CWL and Snakemake files is returned.",
content = @Content(mediaType = "application/zip")),
@ApiResponse(responseCode = "400", description = "Invalid input"),
@ApiResponse(responseCode = "404", description = "Not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
public ResponseEntity> postZipCWLs(
- @RequestBody(required = true) CWLZip cwlZipInfo) {
+ @RequestBody(required = true) WorkflowsZip workflowsZipInfo) {
try {
- Path zipPath = IOUtils.zipFilesForLocalExecution(cwlZipInfo);
+ Path zipPath = IOUtils.zipFilesForLocalExecution(workflowsZipInfo);
Resource zipResource = new UrlResource(zipPath.toUri());
String zipContentType = Files.probeContentType(zipPath);
diff --git a/src/main/java/nl/esciencecenter/controller/dto/APEConfig.java b/src/main/java/nl/esciencecenter/controller/dto/APEConfig.java
index 5199ad0..2d0bf37 100644
--- a/src/main/java/nl/esciencecenter/controller/dto/APEConfig.java
+++ b/src/main/java/nl/esciencecenter/controller/dto/APEConfig.java
@@ -50,6 +50,9 @@ public class APEConfig {
@JsonProperty("number_of_cwl_files")
private int numberOfCwlFiles;
+ @JsonProperty("number_of_snakemake_files")
+ private int numberOfSnakemakeFiles;
+
@JsonProperty("tool_seq_repeat")
private boolean toolSeqRepeat;
diff --git a/src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java b/src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java
new file mode 100644
index 0000000..d104739
--- /dev/null
+++ b/src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java
@@ -0,0 +1,63 @@
+package nl.esciencecenter.controller.dto;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import nl.esciencecenter.restape.RestApeUtils;
+
+@Getter
+@NoArgsConstructor
+public class SnakeFileInfo {
+ @JsonProperty("run_id")
+ private String runID;
+
+ @JsonProperty("file_name")
+ private String fileName;
+ static final String snakemakeExtension = "smk";
+
+
+ /**
+ * Set the runID in case it is valid, i.e., corresponds to runID formatting defined within RESTful APE. If the runID is not valid, an exception is thrown.
+ *
+ * @throws IllegalArgumentException If the structure is not valid.
+ */
+ public void setRunID(String runID) throws IllegalArgumentException {
+ if (!RestApeUtils.isValidRunID(runID)) {
+ throw new IllegalArgumentException("The runID format '" + runID + "' is invalid.");
+ }
+ this.runID = runID;
+ }
+
+ /**
+ * Set the file name in case it is valid, i.e., corresponds to file name formatting defined within APE and includes the supported Snakemake extension.
+ * If the file name is not valid, an exception is thrown.
+ *
+ * @throws IllegalArgumentException If the structure is not valid.
+ */
+ public void setFileName(String fileName) throws IllegalArgumentException {
+ if (!RestApeUtils.isValidAPEFileName(fileName, snakemakeExtension)) {
+ throw new IllegalArgumentException("The Snakemake file_name format '" + fileName + "' is invalid.");
+ }
+ this.fileName = fileName;
+ }
+
+ /**
+ * Get the path to the Snakemake file. If the file does not exist or is not accessible, an exception is thrown.
+ *
+ * @return Path to the Snakemake file.
+ */
+ public Path calculatePath() {
+ Path path = RestApeUtils.calculatePath(runID, "Snakemake", fileName);
+ if(Files.notExists(path)) {
+ throw new IllegalArgumentException("The specified Snakemake file does not exist: " + path.toString());
+ } else if (!Files.isReadable(path)) {
+ throw new IllegalArgumentException("The Snakemake file cannot be accessed: " + path.toString());
+ }
+ return path;
+ }
+
+}
diff --git a/src/main/java/nl/esciencecenter/controller/dto/CWLZip.java b/src/main/java/nl/esciencecenter/controller/dto/WorkflowsZip.java
similarity index 80%
rename from src/main/java/nl/esciencecenter/controller/dto/CWLZip.java
rename to src/main/java/nl/esciencecenter/controller/dto/WorkflowsZip.java
index 595c29f..97e7988 100644
--- a/src/main/java/nl/esciencecenter/controller/dto/CWLZip.java
+++ b/src/main/java/nl/esciencecenter/controller/dto/WorkflowsZip.java
@@ -19,7 +19,7 @@
@Getter
@Setter
@NoArgsConstructor
-public class CWLZip {
+public class WorkflowsZip {
@JsonProperty("run_id")
private String runID;
private List workflows;
@@ -51,7 +51,7 @@ public void setWorkflows(List workflows) throws IllegalArgumentException
/**
* Check whether all the workflows are valid CWL file names.
- * @param workflows2
+ * @param workflows The workflows to verify
*
* @return true if all the workflows are valid CWL file names, false otherwise.
*/
@@ -71,14 +71,26 @@ public List getCWLPaths() {
}
/**
- * Get the paths to the CWL files and the SVG files.
+ * Get the paths to the Snakemake files.
+ *
+ * @return List of paths to the Snakemake files.
+ */
+ public List getSnakemakePaths() {
+ return workflows.stream()
+ .map(fileName -> RestApeUtils.calculatePath(runID, "Snakemake", fileName))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Get the paths to the CWL, Snakemake and the SVG files.
*
- * @return List of paths to the CWL files and the SVG files.
+ * @return List of paths to the CWL, Snakemake and the SVG files.
*/
- public List getCWLandSVGPaths() {
+ public List getAllPaths() {
List paths = new ArrayList<>();
for (String fileName : workflows) {
paths.add(RestApeUtils.calculatePath(runID, "CWL", fileName));
+ paths.add(RestApeUtils.calculatePath(runID, "Snakemake", fileName.replace(".cwl", ".smk")));
paths.add(RestApeUtils.calculatePath(runID, "Figures", fileName.replace(".cwl", ".svg")));
}
return paths;
diff --git a/src/main/java/nl/esciencecenter/models/Workflow.java b/src/main/java/nl/esciencecenter/models/Workflow.java
index b6bd0d9..63b36b1 100644
--- a/src/main/java/nl/esciencecenter/models/Workflow.java
+++ b/src/main/java/nl/esciencecenter/models/Workflow.java
@@ -30,6 +30,9 @@ public class Workflow {
@Column
private String cwlFilePath;
+ @Column
+ private String snakemakeFilePath;
+
@Column
private String pngFilePath;
diff --git a/src/main/java/nl/esciencecenter/restape/APEWorkflowMetadata.java b/src/main/java/nl/esciencecenter/restape/APEWorkflowMetadata.java
index 7304bce..240ae9d 100644
--- a/src/main/java/nl/esciencecenter/restape/APEWorkflowMetadata.java
+++ b/src/main/java/nl/esciencecenter/restape/APEWorkflowMetadata.java
@@ -30,6 +30,8 @@ public class APEWorkflowMetadata {
private String runId;
@JsonProperty("cwl_name")
private String cwlName;
+ @JsonProperty("snakemake_name")
+ private String snakemakeName;
@JsonProperty("figure_name")
private String figureName;
@JsonProperty("benchmark_file")
@@ -49,6 +51,7 @@ public APEWorkflowMetadata(SolutionWorkflow sol, String runID, boolean benchmark
this.workflowLength = sol.getSolutionLength();
this.runId = runID;
this.cwlName = sol.getFileName() + ".cwl";
+ this.snakemakeName = sol.getFileName() + ".smk";
this.figureName = sol.getFileName();
if (benchmark) {
this.benchmarkFile = sol.getFileName() + ".json";
@@ -70,6 +73,7 @@ public JSONObject toJSONObject() {
json.put("workflow_length", this.workflowLength);
json.put("run_id", this.runId);
json.put("cwl_name", this.cwlName);
+ json.put("snakemake_name", this.snakemakeName);
json.put("figure_name", this.figureName);
if (this.benchmarkFile != null) {
json.put("benchmark_file", this.benchmarkFile);
diff --git a/src/main/java/nl/esciencecenter/restape/ApeAPI.java b/src/main/java/nl/esciencecenter/restape/ApeAPI.java
index ded7b17..14ac833 100644
--- a/src/main/java/nl/esciencecenter/restape/ApeAPI.java
+++ b/src/main/java/nl/esciencecenter/restape/ApeAPI.java
@@ -248,8 +248,9 @@ public static List runSynthesis(JSONObject configJson, bool
SolutionsList candidateSolutions = executeSynthesis(configJson, runID);
- // Write solutions (as CWL files and figures) to the file system.
- APE.writeCWLWorkflows(candidateSolutions);
+ // Write solutions (as CWL and Snakemake files and figures) to the file system.
+ APE.writeCWLWorkflows(candidateSolutions, true);
+ APE.writeSnakemakeWorkflows(candidateSolutions, true);
APE.writeTavernaDesignGraphs(candidateSolutions, Format.SVG);
APE.writeTavernaDesignGraphs(candidateSolutions, Format.PNG);
@@ -287,6 +288,7 @@ private static SolutionsList executeSynthesis(JSONObject configJson, String runI
runConfig.setSolutionPath(solutionPath);
int maxSol = runConfig.getMaxNoSolutions();
runConfig.setNoCWL(maxSol);
+ runConfig.setNoSnakemake(maxSol);
runConfig.setNoGraphs(maxSol);
runConfig.setDebugMode(true);
// run the synthesis and retrieve the solutions
diff --git a/src/main/java/nl/esciencecenter/restape/IOUtils.java b/src/main/java/nl/esciencecenter/restape/IOUtils.java
index 865fddc..5fac093 100644
--- a/src/main/java/nl/esciencecenter/restape/IOUtils.java
+++ b/src/main/java/nl/esciencecenter/restape/IOUtils.java
@@ -18,7 +18,7 @@
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
-import nl.esciencecenter.controller.dto.CWLZip;
+import nl.esciencecenter.controller.dto.WorkflowsZip;
/**
* The {@code IOUtils} class provides static methods to read the input files.
@@ -42,6 +42,17 @@ public static String getLocalCwlFile(Path filePath) throws IOException, NoSuchFi
return FileUtils.readFileToString(filePath.toFile(), StandardCharsets.UTF_8);
}
+ /**
+ * Get the Snakemake content of the file at the given path.
+ *
+ * @param filePath - path to the Snakemake file
+ * @return Snakemake content of the file representing a workflow
+ * @throws IOException - if the file cannot be read
+ */
+ public static String getLocalSnakemakeFile(Path filePath) throws IOException, NoSuchFileException {
+ return FileUtils.readFileToString(filePath.toFile(), StandardCharsets.UTF_8);
+ }
+
/**
* Get the JSON content of the file at the given path.
*
@@ -58,24 +69,24 @@ public static String getLocalBenchmarkFile(Path filePath) throws IOException {
* a single zip file. In addition, a `readme.txt` file with instructions on how
* to run the workflows is added to the zip.
*
- * @param cwlZipInfo - the CWL zip information, containing the runID and the list of workflow file names.
+ * @param workflowsZipInfo - the CWL zip information, containing the runID and the list of workflow file names.
*
* @return Path to the created zip file.
* @throws IOException Error is thrown if the zip file cannot be created or
* written to.
*/
- public static Path zipFilesForLocalExecution(CWLZip cwlZipInfo) throws IOException {
+ public static Path zipFilesForLocalExecution(WorkflowsZip workflowsZipInfo) throws IOException {
- List cwlFilePaths = cwlZipInfo.getCWLandSVGPaths();
+ List filePaths = workflowsZipInfo.getAllPaths();
// Add the CWL input file to the zip
- Path cwlInputPath = RestApeUtils.calculatePath(cwlZipInfo.getRunID(), "CWL", "input.yml");
- cwlFilePaths.add(cwlInputPath);
+ Path cwlInputPath = RestApeUtils.calculatePath(workflowsZipInfo.getRunID(), "CWL", "input.yml");
+ filePaths.add(cwlInputPath);
Path zipPath = cwlInputPath.getParent().resolve("workflows.zip");
try (FileOutputStream fos = new FileOutputStream(zipPath.toFile());
ZipOutputStream zipOut = new ZipOutputStream(fos)) {
- for (Path file : cwlFilePaths) {
+ for (Path file : filePaths) {
zipOut.putNextEntry(new ZipEntry(file.getFileName().toString()));
Files.copy(file, zipOut);
zipOut.closeEntry();
diff --git a/src/main/java/nl/esciencecenter/restape/RestApeUtils.java b/src/main/java/nl/esciencecenter/restape/RestApeUtils.java
index 39f876f..69f7982 100644
--- a/src/main/java/nl/esciencecenter/restape/RestApeUtils.java
+++ b/src/main/java/nl/esciencecenter/restape/RestApeUtils.java
@@ -131,13 +131,13 @@ public static boolean isValidAPEFileName(String fileName, String extension) {
/**
* Checks whether the subdirectory generated by {@link APE} is valid, by
- * checking its name. The subdirectory could be either `CWL` or `Figures`.
+ * checking its name. The subdirectory could be either `CWL`, Snakemake or `Figures`.
*
* @param fileSubDir - subdirectory to be verified
* @return true if the subdirectory is valid, false otherwise.
*/
private static boolean isValidAPESubDir(String fileSubDir) {
- return fileSubDir.equals("CWL") || fileSubDir.equals("Figures");
+ return fileSubDir.equals("CWL") || fileSubDir.equals("Snakemake") || fileSubDir.equals("Figures");
}
/**
@@ -186,7 +186,7 @@ public static Path calculatePath(String runID, String fileSubDir, String fileNam
isValidAPESubDir(fileSubDir)) {
return Paths.get(getSolutionPath(), runID, fileSubDir, fileName);
} else {
- throw new IllegalArgumentException("The provided runID, fileSubDir or fileName is not valid.");
+ throw new IllegalArgumentException(String.format("The provided runID (%s), fileSubDir (%s) or fileName (%s) is not valid.", runID, fileSubDir, fileName));
}
}
diff --git a/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java b/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java
index 020a455..ece98e2 100644
--- a/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java
+++ b/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java
@@ -131,6 +131,37 @@ void testRunSynthesisPass() throws Exception {
.andExpect(status().isOk());
}
+ /**
+ * Test the snakemake method with POST and a valid configuration file.
+ *
+ * @throws Exception
+ */
+ @Test
+ void testRunSnakemakeExtraction() throws Exception {
+
+ String configPath = "https://raw.githubusercontent.com/Workflomics/tools-and-domains/refs/heads/main/domains/proteomics/config.json";
+ String content = FileUtils.readFileToString(APEFiles.readPathToFile(configPath),
+ StandardCharsets.UTF_8);
+
+ JSONObject jsonObject = new JSONObject(content);
+ jsonObject.put("solutions", "1");
+ jsonObject.put("number_of_snakemake_files", "10");
+
+ List result = ApeAPI.runSynthesis(jsonObject, false);
+ assertFalse(result.isEmpty(), "The encoding should be SAT.");
+ String runID = result.get(0).getRunId();
+ String snakeFile = result.get(0).getSnakemakeName();
+
+ String jsonContent = "{\"run_id\": \"" + runID + "\", \"file_name\": \"" + snakeFile + "\"}";
+
+ mvc.perform(MockMvcRequestBuilders.post("/snakemake")
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON).content(jsonContent))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType("application/x-yaml"));
+
+ }
+
@Test
void testPostZipCWLs() throws Exception {
diff --git a/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java b/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java
index 34e0853..7bdbcd5 100644
--- a/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java
+++ b/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java
@@ -9,7 +9,7 @@
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
-import nl.esciencecenter.controller.dto.CWLZip;
+import nl.esciencecenter.controller.dto.WorkflowsZip;
import nl.uu.cs.ape.utils.APEFiles;
@SpringBootTest
@@ -29,10 +29,10 @@ void testZipFilesForLocalExecution() throws Exception {
String runID = result.get(0).getRunId();
String cwlFile = result.get(0).getCwlName();
- CWLZip cwlZip = new CWLZip();
- cwlZip.setRunID(runID);
- cwlZip.setWorkflows(List.of(cwlFile));
+ WorkflowsZip workflowsZip = new WorkflowsZip();
+ workflowsZip.setRunID(runID);
+ workflowsZip.setWorkflows(List.of(cwlFile));
- IOUtils.zipFilesForLocalExecution(cwlZip);
+ IOUtils.zipFilesForLocalExecution(workflowsZip);
}
}