From 27697b00484d0f83538d65726d7a0ebcf47a7cb8 Mon Sep 17 00:00:00 2001 From: Vedran Kasalica Date: Sat, 17 Feb 2024 02:51:58 +0100 Subject: [PATCH 1/6] Remove unused dependencies --- pom.xml | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 59254f1..c0731a2 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ org.springframework.boot spring-boot-starter-web - 3.2.1 + 3.2.2 @@ -61,28 +61,14 @@ org.springframework.boot spring-boot-starter-actuator - 3.2.1 + 3.2.2 org.springframework.boot spring-boot-starter-data-jpa - 3.2.1 - - - - - org.springframework.boot - spring-boot-starter-validation - 3.2.1 - - - - - org.springframework.boot - spring-boot-devtools - 3.2.1 + 3.2.2 From c8c288830e9873271374e0f47adcb48f00fcc267 Mon Sep 17 00:00:00 2001 From: Vedran Kasalica Date: Sun, 18 Feb 2024 13:14:52 +0100 Subject: [PATCH 2/6] Update the APE version --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c0731a2..e6294f7 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ nl.esciencecenter restape - 0.3.0 + 0.3.1 restape RESTful API for the APE library @@ -82,7 +82,7 @@ io.github.sanctuuary APE - 2.2.6 + 2.3.0 From 909820be2ec63f2267f6bed765bcad15c7f8d38c Mon Sep 17 00:00:00 2001 From: Vedran Kasalica Date: Sun, 18 Feb 2024 18:31:37 +0100 Subject: [PATCH 3/6] Update naming of APE solution and add benchmarker instructions to zip --- .../controller/RestApeController.java | 17 +-- .../esciencecenter/controller/dto/CWLZip.java | 4 + .../nl/esciencecenter/restape/IOUtils.java | 103 +++++++++++++----- .../esciencecenter/restape/RestApeUtils.java | 10 +- 4 files changed, 90 insertions(+), 44 deletions(-) diff --git a/src/main/java/nl/esciencecenter/controller/RestApeController.java b/src/main/java/nl/esciencecenter/controller/RestApeController.java index bf660ca..e8a4d12 100644 --- a/src/main/java/nl/esciencecenter/controller/RestApeController.java +++ b/src/main/java/nl/esciencecenter/controller/RestApeController.java @@ -211,7 +211,7 @@ public ResponseEntity runSynthesisAndBench( @ApiResponse(responseCode = "400", description = "Invalid input"), @ApiResponse(responseCode = "404", description = "Not found") }) - public ResponseEntity getImage( + public ResponseEntity postImage( @RequestBody(required = true) ImgFileInfo imgFileInfo) throws IOException { Path path = imgFileInfo.calculatePath(); @@ -243,7 +243,7 @@ public ResponseEntity getImage( @ApiResponse(responseCode = "404", description = "Not found") }) - public ResponseEntity getCwl( + public ResponseEntity postCwl( @RequestBody(required = true) CWLFileInfo cwlInfoJson) throws IOException { Path path = cwlInfoJson.calculatePath(); @@ -327,7 +327,7 @@ public ResponseEntity getBenchmarks( * Retrieve the CWL solution files based on the provided run ID and CWL file * names. * TODO: Exeptions don't handle all cases or illegal arguments (e.g. invalid - * workflow name that ends with an open quotation`candidate_solution_1.cwl"`). + * workflow name that ends with an open quotation`candidate_workflow_1.cwl"`). * * @param cwlFilesJson JSON object containing the run_id and the list of CWL * files. @@ -343,17 +343,10 @@ public ResponseEntity getBenchmarks( @ApiResponse(responseCode = "500", description = "Internal server error"), }) - public ResponseEntity getZipCWLs( + public ResponseEntity postZipCWLs( @RequestBody(required = true) CWLZip cwlZipInfo) { try { - List cwlFilePaths = cwlZipInfo.getCWLPaths(); - - // Add the CWL input file to the zip - Path cwlInputPath = RestApeUtils.calculatePath(cwlZipInfo.getRunID(), "CWL", "input.yml"); - cwlFilePaths.add(cwlInputPath); - - // Zip the CWL files - Path zipPath = IOUtils.zipFiles(cwlFilePaths, cwlInputPath.getParent()); + Path zipPath = IOUtils.zipFilesForLocalExecution(cwlZipInfo); Resource zipResource = new UrlResource(zipPath.toUri()); String zipContentType = Files.probeContentType(zipPath); diff --git a/src/main/java/nl/esciencecenter/controller/dto/CWLZip.java b/src/main/java/nl/esciencecenter/controller/dto/CWLZip.java index 1b184fd..5d3af30 100644 --- a/src/main/java/nl/esciencecenter/controller/dto/CWLZip.java +++ b/src/main/java/nl/esciencecenter/controller/dto/CWLZip.java @@ -11,6 +11,10 @@ import lombok.Setter; import nl.esciencecenter.restape.RestApeUtils; +/** + * The {@code CWLZip} class represents the structure of the request to zip CWL files. It contains the runID and the list of workflow file names. + + */ @Getter @Setter @NoArgsConstructor diff --git a/src/main/java/nl/esciencecenter/restape/IOUtils.java b/src/main/java/nl/esciencecenter/restape/IOUtils.java index a7371b4..8eaaa80 100644 --- a/src/main/java/nl/esciencecenter/restape/IOUtils.java +++ b/src/main/java/nl/esciencecenter/restape/IOUtils.java @@ -1,7 +1,11 @@ package nl.esciencecenter.restape; +import java.io.BufferedInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -14,6 +18,7 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; +import nl.esciencecenter.controller.dto.CWLZip; /** * The {@code IOUtils} class provides static methods to read the input files. @@ -21,40 +26,53 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class IOUtils { - /** - * Get the CWL content of the file at the given path. - * - * @param filePath - path to the CWL file - * @return CWL content of the file representing a workflow - * @throws IOException - if the file cannot be read - */ - public static String getLocalCwlFile(Path filePath) throws IOException, NoSuchFileException { - return FileUtils.readFileToString(filePath.toFile(), StandardCharsets.UTF_8); - } + /** + * URL to the README file containing instructions on how to run the workflows. + */ + private static final String README_URL = "https://raw.githubusercontent.com/Workflomics/containers/add_instructions/instructions.txt"; - /** - * Get the JSON content of the file at the given path. - * - * @param filePath - path to the benchmarking JSON file - * @return CWL content of the file representing a workflow - * @throws IOException - if the file cannot be read - */ - public static String getLocalBenchmarkFile(Path filePath) throws IOException { - return FileUtils.readFileToString(filePath.toFile(), StandardCharsets.UTF_8); - } + /** + * Get the CWL content of the file at the given path. + * + * @param filePath - path to the CWL file + * @return CWL content of the file representing a workflow + * @throws IOException - if the file cannot be read + */ + public static String getLocalCwlFile(Path filePath) throws IOException, NoSuchFileException { + return FileUtils.readFileToString(filePath.toFile(), StandardCharsets.UTF_8); + } - /** - * Zip the provided CWL files as well as the CWL input file (`inputs.yml`). + /** + * Get the JSON content of the file at the given path. + * + * @param filePath - path to the benchmarking JSON file + * @return CWL content of the file representing a workflow + * @throws IOException - if the file cannot be read + */ + public static String getLocalBenchmarkFile(Path filePath) throws IOException { + return FileUtils.readFileToString(filePath.toFile(), StandardCharsets.UTF_8); + } + + /** + * Zip the provided CWL files as well as the CWL input file (`inputs.yml`) into + * 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 cwlFilePaths List of CWL file names (with extensions). - * @param locationDirPath Path to the directory where the zip file will be - * created. * @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 zipFiles(List cwlFilePaths, Path locationDirPath) throws IOException { - Path zipPath = locationDirPath.resolve("workflows.zip"); + public static Path zipFilesForLocalExecution(CWLZip cwlZipInfo) throws IOException { + + List cwlFilePaths = cwlZipInfo.getCWLPaths(); + + // Add the CWL input file to the zip + Path cwlInputPath = RestApeUtils.calculatePath(cwlZipInfo.getRunID(), "CWL", "input.yml"); + cwlFilePaths.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) { @@ -62,8 +80,39 @@ public static Path zipFiles(List cwlFilePaths, Path locationDirPath) throw Files.copy(file, zipOut); zipOut.closeEntry(); } + addReadmeToZip(zipOut); } return zipPath; } + /** + * Add the `readme.txt` file to the zip from the given URL. The file contains + * instructions on how to run the workflows. + * + * @param zipOut - the zip output stream + * @throws IOException - if the README file cannot be read + */ + private static void addReadmeToZip(ZipOutputStream zipOut) throws IOException { + // Download readme.txt and add to the zip + URL readmeUrl = new URL(README_URL); + HttpURLConnection httpURLConnection = (HttpURLConnection) readmeUrl.openConnection(); + httpURLConnection.setRequestMethod("GET"); + // Ensure the connection timeout is set to a reasonable value + httpURLConnection.setConnectTimeout(5000); // 5 seconds + httpURLConnection.setReadTimeout(5000); // 5 seconds + + try (InputStream in = new BufferedInputStream(httpURLConnection.getInputStream())) { + zipOut.putNextEntry(new ZipEntry("readme.txt")); + + byte[] buffer = new byte[1024]; + int count; + while ((count = in.read(buffer)) != -1) { + zipOut.write(buffer, 0, count); + } + + zipOut.closeEntry(); + } finally { + httpURLConnection.disconnect(); + } + } } diff --git a/src/main/java/nl/esciencecenter/restape/RestApeUtils.java b/src/main/java/nl/esciencecenter/restape/RestApeUtils.java index f29dc03..39f876f 100644 --- a/src/main/java/nl/esciencecenter/restape/RestApeUtils.java +++ b/src/main/java/nl/esciencecenter/restape/RestApeUtils.java @@ -94,19 +94,19 @@ public static boolean isValidRunID(String runID) { /** * Checks whether the file name is valid, by checking its format. The name - * should start with `candidate_solution_` followed by a number and without an + * should start with `candidate_workflow_` followed by a number and without an * extension. * * @param fileName - file name to be verified * @return true if the file name is valid, false otherwise. */ public static boolean isValidAPEFileNameNoExtension(String fileName) { - return fileName != null && fileName.matches("candidate_solution_\\d+"); + return fileName != null && fileName.matches("candidate_workflow_\\d+"); } /** * Checks whether the file name is valid, by checking its format. The name - * should start with `candidate_solution_` followed by a number and with an + * should start with `candidate_workflow_` followed by a number and with an * extension. * * @param fileName - file name to be verified @@ -118,7 +118,7 @@ public static boolean isValidFileNameWithExtension(String fileName) { /** * Checks whether the file name is valid, by checking its extension and format. - * The name should start with `candidate_solution_` followed by a number and end + * The name should start with `candidate_workflow_` followed by a number and end * with the specified extension. * * @param fileName - file name to be verified @@ -126,7 +126,7 @@ public static boolean isValidFileNameWithExtension(String fileName) { * @return true if the file name is valid, false otherwise. */ public static boolean isValidAPEFileName(String fileName, String extension) { - return fileName != null && fileName.matches("candidate_solution_\\d+\\." + extension); + return fileName != null && fileName.matches("candidate_workflow_\\d+\\." + extension); } /** From 04b3b6112ed3603e5ffe72b208d8868a34270563 Mon Sep 17 00:00:00 2001 From: Vedran Kasalica Date: Sun, 18 Feb 2024 18:33:16 +0100 Subject: [PATCH 4/6] Improve tests --- .../controller/RestApeControllerTest.java | 18 ++++---- .../esciencecenter/restape/IOUtilsTest.java | 43 +++++++++++++++++++ 2 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 src/test/java/nl/esciencecenter/restape/IOUtilsTest.java diff --git a/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java b/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java index ab7f49e..f7a4c60 100644 --- a/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java +++ b/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java @@ -35,7 +35,7 @@ class RestApeControllerTest { * @throws Exception */ @Test - void getGreetings() throws Exception { + void testGetGreetings() throws Exception { mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string(equalTo("Welcome to the RESTful APE API!"))); @@ -47,7 +47,7 @@ void getGreetings() throws Exception { * @throws Exception */ @Test - void getDataFail() throws Exception { + void testGetDataFail() throws Exception { mvc.perform(MockMvcRequestBuilders.get("/data_taxonomy").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); } @@ -58,7 +58,7 @@ void getDataFail() throws Exception { * @throws Exception */ @Test - void getDataTest() throws Exception { + void testGetData() throws Exception { String path = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json"; mvc.perform(MockMvcRequestBuilders.get("/data_taxonomy?config_path=" + path).accept(MediaType.APPLICATION_JSON)) @@ -72,7 +72,7 @@ void getDataTest() throws Exception { * @throws Exception */ @Test - void getToolsFail() throws Exception { + void testGetToolsFail() throws Exception { mvc.perform(MockMvcRequestBuilders.get("/tools_taxonomy").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); } @@ -83,7 +83,7 @@ void getToolsFail() throws Exception { * @throws Exception */ @Test - void getToolsTest() throws Exception { + void testGetTools() throws Exception { String path = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json"; mvc.perform( @@ -98,7 +98,7 @@ void getToolsTest() throws Exception { * @throws Exception */ @Test - void runSynthesisGetFail() throws Exception { + void testRunSynthesisGestFail() throws Exception { mvc.perform(MockMvcRequestBuilders.get("/run_synthesis").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isMethodNotAllowed()); } @@ -109,7 +109,7 @@ void runSynthesisGetFail() throws Exception { * @throws Exception */ @Test - void runSynthesisPostFail() throws Exception { + void testRunSynthesisFail() throws Exception { mvc.perform(MockMvcRequestBuilders.post("/run_synthesis").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); } @@ -120,7 +120,7 @@ void runSynthesisPostFail() throws Exception { * @throws Exception */ @Test - void runSynthesisPostPass() throws Exception { + void testRunSynthesisPass() throws Exception { String configPath = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json"; String jsonContent = FileUtils.readFileToString(APEFiles.readPathToFile(configPath), @@ -133,7 +133,7 @@ void runSynthesisPostPass() throws Exception { } @Test - void getZipCWLs() throws Exception { + void testPostZipCWLs() throws Exception { String path = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json"; String content = FileUtils.readFileToString(APEFiles.readPathToFile(path), diff --git a/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java b/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java new file mode 100644 index 0000000..3073df2 --- /dev/null +++ b/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java @@ -0,0 +1,43 @@ +package nl.esciencecenter.restape; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import nl.esciencecenter.controller.dto.CWLFileInfo; +import nl.esciencecenter.controller.dto.CWLZip; +import nl.uu.cs.ape.utils.APEFiles; + +@SpringBootTest +public class IOUtilsTest { + + + @Test + void testZipFilesForLocalExecution() throws Exception { + + String path = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json"; + String content = FileUtils.readFileToString(APEFiles.readPathToFile(path), + StandardCharsets.UTF_8); + JSONObject jsonObject = new JSONObject(content); + jsonObject.put("solutions", "1"); + JSONArray result = ApeAPI.runSynthesis(jsonObject, false); + assertFalse(result.isEmpty(), "The encoding should be SAT."); + String runID = result.getJSONObject(0).getString("run_id"); + String cwlFile = result.getJSONObject(0).getString("cwl_name"); + + CWLZip cwlZip = new CWLZip(); + cwlZip.setRunID(runID); + cwlZip.setWorkflows(List.of(cwlFile)); + + IOUtils.zipFilesForLocalExecution(cwlZip); + } +} From 4f1f8aa5bc4e1f4093b6ffd75a6e717ba03b70e2 Mon Sep 17 00:00:00 2001 From: Vedran Kasalica Date: Sun, 18 Feb 2024 18:33:55 +0100 Subject: [PATCH 5/6] Remove public modifier from the test class --- src/test/java/nl/esciencecenter/restape/IOUtilsTest.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java b/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java index 3073df2..2f35899 100644 --- a/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java +++ b/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java @@ -10,15 +10,11 @@ import org.json.JSONObject; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import nl.esciencecenter.controller.dto.CWLFileInfo; import nl.esciencecenter.controller.dto.CWLZip; import nl.uu.cs.ape.utils.APEFiles; @SpringBootTest -public class IOUtilsTest { +class IOUtilsTest { @Test From 99115d428508474754171fb2f873528aa6daa3fd Mon Sep 17 00:00:00 2001 From: Vedran Kasalica Date: Mon, 19 Feb 2024 02:26:09 +0100 Subject: [PATCH 6/6] Setup test added --- .../nl/esciencecenter/RestApeApplicationTest.java | 12 ++++++++++-- .../restape/ToolBenchmarkingAPIsTest.java | 5 ----- 2 files changed, 10 insertions(+), 7 deletions(-) delete mode 100644 src/test/java/nl/esciencecenter/restape/ToolBenchmarkingAPIsTest.java diff --git a/src/test/java/nl/esciencecenter/RestApeApplicationTest.java b/src/test/java/nl/esciencecenter/RestApeApplicationTest.java index 48a4747..ede3ea1 100644 --- a/src/test/java/nl/esciencecenter/RestApeApplicationTest.java +++ b/src/test/java/nl/esciencecenter/RestApeApplicationTest.java @@ -1,13 +1,21 @@ package nl.esciencecenter; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; @SpringBootTest class RestApeApplicationTest { - @Test - void contextLoads() { + @Autowired + private ApplicationContext context; + + @Test + void contextLoads() { + assertNotNull(context, "Application context should not be null"); } diff --git a/src/test/java/nl/esciencecenter/restape/ToolBenchmarkingAPIsTest.java b/src/test/java/nl/esciencecenter/restape/ToolBenchmarkingAPIsTest.java deleted file mode 100644 index 8da87a2..0000000 --- a/src/test/java/nl/esciencecenter/restape/ToolBenchmarkingAPIsTest.java +++ /dev/null @@ -1,5 +0,0 @@ -package nl.esciencecenter.restape; - -public class ToolBenchmarkingAPIsTest { - -}