Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 14 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
<version>3.0.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>nl.esciencecenter</groupId>
<groupId>org.workflomics</groupId>
<artifactId>restape</artifactId>
<version>1.0.0</version>
<version>1.1.0</version>
<name>restape</name>
<description>RESTful API for the APE library</description>
<licenses>
Expand All @@ -23,10 +23,16 @@
<developers>
<developer>
<name>Vedran Kasalica</name>
<email>v.kasalica@esciencecenter.nl</email>
<organization>Netherlands eScience Center</organization>
<organizationUrl>https://www.esciencecenter.nl/</organizationUrl>
<email>vedran.kasalica@health-ri.nl</email>
<organization>Health-RI</organization>
<organizationUrl>https://www.health-ri.nl/</organizationUrl>
</developer>
<developer>
<name>Mario Frank</name>
<email>mario.frank@uni-potsdam.de</email>
<organization>University of Potsdam</organization>
<organizationUrl>https://www.uni-potsdam.de</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/workflomics/restape.git</connection>
Expand Down Expand Up @@ -86,11 +92,11 @@
<scope>runtime</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/io.github.sanctuuary/APE -->
<!-- https://mvnrepository.com/artifact/org.workflomics/APE -->
<dependency>
<groupId>io.github.sanctuuary</groupId>
<groupId>org.workflomics</groupId>
<artifactId>APE</artifactId>
<version>2.5.3</version>
<version>2.6.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui -->
Expand Down
51 changes: 43 additions & 8 deletions src/main/java/nl/esciencecenter/controller/RestApeController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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",
Expand All @@ -368,6 +369,40 @@ public ResponseEntity<String> 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<String> 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.
*
Expand Down Expand Up @@ -461,26 +496,26 @@ public ResponseEntity<String> 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);
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/nl/esciencecenter/controller/dto/APEConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
63 changes: 63 additions & 0 deletions src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
@Getter
@Setter
@NoArgsConstructor
public class CWLZip {
public class WorkflowsZip {
@JsonProperty("run_id")
private String runID;
private List<String> workflows;
Expand Down Expand Up @@ -51,7 +51,7 @@ public void setWorkflows(List<String> 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.
*/
Expand All @@ -71,14 +71,26 @@ public List<Path> 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<Path> 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<Path> getCWLandSVGPaths() {
public List<Path> getAllPaths() {
List<Path> 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;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/nl/esciencecenter/models/Workflow.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public class Workflow {
@Column
private String cwlFilePath;

@Column
private String snakemakeFilePath;

@Column
private String pngFilePath;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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";
Expand All @@ -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);
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/nl/esciencecenter/restape/ApeAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,9 @@ public static List<APEWorkflowMetadata> 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);

Expand Down Expand Up @@ -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
Expand Down
25 changes: 18 additions & 7 deletions src/main/java/nl/esciencecenter/restape/IOUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
*
Expand All @@ -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<Path> cwlFilePaths = cwlZipInfo.getCWLandSVGPaths();
List<Path> 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();
Expand Down
Loading
Loading