diff --git a/pom.xml b/pom.xml index e6294f7..10a9279 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,13 @@ 3.2.2 + + + org.springframework.boot + spring-boot-starter-validation + 3.2.2 + + com.h2database diff --git a/src/main/java/nl/esciencecenter/controller/RestApeController.java b/src/main/java/nl/esciencecenter/controller/RestApeController.java index e8a4d12..e5ee418 100644 --- a/src/main/java/nl/esciencecenter/controller/RestApeController.java +++ b/src/main/java/nl/esciencecenter/controller/RestApeController.java @@ -33,16 +33,15 @@ import nl.esciencecenter.controller.dto.ConstraintElem; import nl.esciencecenter.controller.dto.ImgFileInfo; import nl.esciencecenter.controller.dto.TaxonomyElem; +import nl.esciencecenter.restape.APEWorkflowMetadata; import nl.esciencecenter.restape.ApeAPI; import nl.esciencecenter.restape.IOUtils; import nl.esciencecenter.restape.RestApeUtils; import nl.uu.cs.ape.configuration.APEConfigException; /** - * This class represents the RESTful APE controller. - * TODO: Setup response code 400 and 404 when needed. + * This class represents the RESTful APE controller. It provides the RESTful API for the APE library. * - * @author Vedran */ @RestController public class RestApeController { @@ -70,14 +69,24 @@ public String index() { * @throws OWLOntologyCreationException */ @GetMapping("/data_taxonomy") - @Operation(summary = "Retrieve data taxonomy", description = "Retrieve data (taxonomy) within the domain.", tags = { - "Domain" }, parameters = { - @Parameter(name = "config_path", description = "URL to the APE configuration file.", example = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json") }, externalDocs = @ExternalDocumentation(description = "More information about the APE configuration file can be found here.", url = "https://ape-framework.readthedocs.io/en/latest/docs/specifications/setup.html#configuration-file"), responses = { - @ApiResponse(responseCode = "200", description = "Successful operation. Taxonomy of data terms is provided.", content = @Content(schema = @Schema(implementation = TaxonomyElem.class), mediaType = MediaType.APPLICATION_JSON_VALUE)), - @ApiResponse(responseCode = "400", description = "Invalid input"), - @ApiResponse(responseCode = "404", description = "Not found") - - }) + @Operation(summary = "Retrieve data taxonomy", + description = "Retrieve data (taxonomy) within the domain.", + tags = {"Domain"}, + parameters = { + @Parameter(name = "config_path", + description = "URL to the APE configuration file.", + example = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json") + }, + externalDocs = @ExternalDocumentation(description = "More information about the APE configuration file can be found here.", + url = "https://ape-framework.readthedocs.io/en/latest/docs/specifications/setup.html#configuration-file"), + responses = { + @ApiResponse(responseCode = "200", + description = "Successful operation. Taxonomy of data terms is provided.", + content = @Content(schema = @Schema(implementation = TaxonomyElem.class), + mediaType = MediaType.APPLICATION_JSON_VALUE)), + @ApiResponse(responseCode = "400", description = "Invalid input"), + @ApiResponse(responseCode = "404", description = "Not found") + }) public ResponseEntity getData( @RequestParam("config_path") String configPath) throws OWLOntologyCreationException, IOException, IllegalArgumentException { @@ -95,14 +104,24 @@ public ResponseEntity getData( * @throws OWLOntologyCreationException */ @GetMapping("/tools_taxonomy") - @Operation(summary = "Retrieve tool taxonomy", description = "Retrieve tools (taxonomy) within the domain.", tags = { - "Domain" }, parameters = { - @Parameter(name = "config_path", description = "URL to the APE configuration file.", example = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json") }, externalDocs = @ExternalDocumentation(description = "More information about the APE configuration file can be found here.", url = "https://ape-framework.readthedocs.io/en/latest/docs/specifications/setup.html#configuration-file"), responses = { - @ApiResponse(responseCode = "200", description = "Successful operation. Taxonomy of data terms is provided.", content = @Content(schema = @Schema(implementation = TaxonomyElem.class), mediaType = MediaType.APPLICATION_JSON_VALUE)), - @ApiResponse(responseCode = "400", description = "Invalid input"), - @ApiResponse(responseCode = "404", description = "Not found") - - }) + @Operation(summary = "Retrieve tool taxonomy", + description = "Retrieve tools (taxonomy) within the domain.", + tags = {"Domain"}, + parameters = { + @Parameter(name = "config_path", + description = "URL to the APE configuration file.", + example = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json") + }, + externalDocs = @ExternalDocumentation(description = "More information about the APE configuration file can be found here.", + url = "https://ape-framework.readthedocs.io/en/latest/docs/specifications/setup.html#configuration-file"), + responses = { + @ApiResponse(responseCode = "200", + description = "Successful operation. Taxonomy of tool terms is provided.", + content = @Content(schema = @Schema(implementation = TaxonomyElem.class), + mediaType = MediaType.APPLICATION_JSON_VALUE)), + @ApiResponse(responseCode = "400", description = "Invalid input"), + @ApiResponse(responseCode = "404", description = "Not found") + }) public ResponseEntity getTools( @RequestParam("config_path") String configPath) throws OWLOntologyCreationException, IOException { @@ -119,14 +138,24 @@ public ResponseEntity getTools( * @return Constraint templates. */ @GetMapping("/constraints") - @Operation(summary = "Retrieve constraint templates", description = "Retrieve constraint templates used to specify synthesis problem.", tags = { - "Domain" }, parameters = { - @Parameter(name = "config_path", description = "URL to the APE configuration file.", example = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json") }, externalDocs = @ExternalDocumentation(description = "More information about the APE configuration file can be found here.", url = "https://ape-framework.readthedocs.io/en/latest/docs/specifications/setup.html#configuration-file"), responses = { - @ApiResponse(responseCode = "200", description = "Successful operation. Taxonomy of data terms is provided.", content = @Content(schema = @Schema(implementation = ConstraintElem.class), mediaType = MediaType.APPLICATION_JSON_VALUE)), - @ApiResponse(responseCode = "400", description = "Invalid input"), - @ApiResponse(responseCode = "404", description = "Not found") - - }) + @Operation(summary = "Retrieve constraint templates", + description = "Retrieve constraint templates used to specify the synthesis problem.", + tags = {"Domain"}, + parameters = { + @Parameter(name = "config_path", + description = "URL to the APE configuration file.", + example = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json") + }, + externalDocs = @ExternalDocumentation(description = "More information about the APE configuration file can be found here.", + url = "https://ape-framework.readthedocs.io/en/latest/docs/specifications/setup.html#configuration-file"), + responses = { + @ApiResponse(responseCode = "200", + description = "Successful operation. Constraint templates are provided.", + content = @Content(schema = @Schema(implementation = ConstraintElem.class), + mediaType = MediaType.APPLICATION_JSON_VALUE)), + @ApiResponse(responseCode = "400", description = "Invalid input"), + @ApiResponse(responseCode = "404", description = "Not found") + }) public ResponseEntity getConstraints(@RequestParam("config_path") String configPath) throws JSONException, OWLOntologyCreationException, IOException { @@ -143,22 +172,33 @@ public ResponseEntity getConstraints(@RequestParam("config_path") String * @throws OWLOntologyCreationException */ @PostMapping("/run_synthesis") - @Operation(summary = "Run workflow synthesis", description = "Run workflow synthesis using the APE library. Returns the list of resulting solutions, where each element describes a workflow (name,length, run_id, etc.).", tags = { - "APE" }, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "JSON object containing the configuration for the synthesis.", content = @Content(schema = @Schema(implementation = APEConfig.class))), parameters = { - @Parameter(name = "configJson", description = "APE configuration JSON file.", example = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json") }, externalDocs = @ExternalDocumentation(description = "More information about the APE configuration file can be found here.", url = "https://ape-framework.readthedocs.io/en/latest/docs/specifications/setup.html#configuration-file"), responses = { - @ApiResponse(responseCode = "200", description = "Successful operation. Synthesis solutions are returned."), - @ApiResponse(responseCode = "400", description = "Invalid input"), - @ApiResponse(responseCode = "404", description = "Not found"), - @ApiResponse(responseCode = "500", description = "Internal server error"), - - }) - public ResponseEntity runSynthesis( + @Operation(summary = "Run workflow synthesis", + description = "Run workflow synthesis using the APE library. Returns the list of resulting solutions, where each element describes a workflow (name, length, run_id, etc.).", + tags = {"APE"}, + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "JSON object containing the configuration for the synthesis.", + required = true, + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = APEConfig.class)) + ), + externalDocs = @ExternalDocumentation( + description = "More information about the APE configuration file can be found here.", + url = "https://ape-framework.readthedocs.io/en/latest/docs/specifications/setup.html#configuration-file"), + responses = { + @ApiResponse(responseCode = "200", description = "Successful operation. A list of synthesized workflow solutions is returned." + ), + @ApiResponse(responseCode = "400", description = "Invalid input"), + @ApiResponse(responseCode = "404", description = "Not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + public ResponseEntity> runSynthesis( @RequestBody(required = true) Map configJson) throws APEConfigException, JSONException, OWLOntologyCreationException, IOException { JSONObject config = new JSONObject(configJson); return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON) - .body(ApeAPI.runSynthesis(config, false).toString()); + .body(ApeAPI.runSynthesis(config, false)); } /** @@ -170,47 +210,53 @@ public ResponseEntity runSynthesis( * @throws IOException */ @PostMapping("/run_synthesis_and_bench") - @Operation(summary = "Run workflow synthesis and provide design-time benchmarks", description = "Run workflow synthesis using the APE library. In addition, evaluate design time benchmarks of the generated workflows. Returns the list of resulting solutions, where each element describes a workflow (name,length, run_id, etc.).", tags = { - "APE" }, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "JSON object containing the configuration for the synthesis.", content = @Content(schema = @Schema(implementation = APEConfig.class))), parameters = { - @Parameter(name = "configJson", description = "APE configuration JSON file.", example = "https://raw.githubusercontent.com/Workflomics/domain-annotations/main/WombatP_tools/config.json") }, externalDocs = @ExternalDocumentation(description = "More information about the APE configuration file can be found here.", url = "https://ape-framework.readthedocs.io/en/latest/docs/specifications/setup.html#configuration-file"), responses = { - @ApiResponse(responseCode = "200", description = "Successful operation. Synthesis solutions are returned."), - @ApiResponse(responseCode = "400", description = "Invalid input"), - @ApiResponse(responseCode = "404", description = "Not found"), - @ApiResponse(responseCode = "500", description = "Internal server error"), - - }) - public ResponseEntity runSynthesisAndBench( + @Operation(summary = "Run workflow synthesis and provide design-time benchmarks", + description = "This endpoint triggers the synthesis of workflows using the APE library and evaluates design-time benchmarks for the generated workflows. It returns a list of APEWorkflowMetadata objects, each representing a synthesized workflow solution with detailed metadata.", + tags = { "APE" }, + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "JSON object containing the configuration for the synthesis.", + content = @Content(schema = @Schema(implementation = APEConfig.class))), + responses = { + @ApiResponse(responseCode = "200", description = "Successful operation. A list of synthesized workflow solutions is returned."), + @ApiResponse(responseCode = "400", description = "Invalid input. The request body does not match the expected schema."), + @ApiResponse(responseCode = "404", description = "Not found. The specified resource could not be found."), + @ApiResponse(responseCode = "500", description = "Internal server error. An unexpected error occurred.") + }, + externalDocs = @ExternalDocumentation(description = "More information about the APE configuration file.", + url = "https://ape-framework.readthedocs.io/en/latest/docs/specifications/setup.html#configuration-file")) + public ResponseEntity> runSynthesisAndBench( @RequestBody(required = true) Map configJson) throws APEConfigException, JSONException, OWLOntologyCreationException, IOException { JSONObject config = new JSONObject(configJson); return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON) - .body(ApeAPI.runSynthesis(config, true).toString()); + .body(ApeAPI.runSynthesis(config, true)); } /** * Retrieve the solution workflow based on the provided run ID and a candidate * solution. * - * @param fileName Name of the workflow 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 Image in PNG format representing the workflow. - * @throws IOException + * @param imgFileInfo JSON object containing the configuration for the synthesis. + * @return ResponseEntity containing the image representing the workflow. + * @throws IOException if there is an error reading the image file. */ @PostMapping("/image") - @Operation(summary = "Retrieve an image representing the workflow.", description = "Retrieve a image from the file system representing the workflow generated.", tags = { - "Download" }, parameters = { - @Parameter(name = "file_name", description = "Name of the image file (provided under 'name' after the synthesis run).", example = "workflowSolution_0"), - @Parameter(name = "format", description = "Format of the image ('png' or 'svg').", example = "png"), - @Parameter(name = "run_id", description = "ID of the corresponding synthesis run (provided under 'run_id' after the synthesis run).", example = "04ce2ef00c1685150252568") - - }, responses = { - @ApiResponse(responseCode = "200", description = "Successful operation. Taxonomy of data terms is provided.", content = @Content(mediaType = MediaType.IMAGE_PNG_VALUE)), + @Operation(summary = "Retrieve an image representing the workflow", + description = "Retrieve an image from the file system representing the workflow generated.", + tags = {"Download"}, + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "JSON object containing the information about the image, including the name of the image file, format of the image ('png' or 'svg'), and ID of the corresponding synthesis run.", + required = true, + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ImgFileInfo.class)) + ), + responses = { + @ApiResponse(responseCode = "200", description = "Successful operation. Image representing the workflow is provided.", + content = @Content(mediaType = MediaType.IMAGE_PNG_VALUE)), @ApiResponse(responseCode = "400", description = "Invalid input"), @ApiResponse(responseCode = "404", description = "Not found") - }) + }) public ResponseEntity postImage( @RequestBody(required = true) ImgFileInfo imgFileInfo) throws IOException { @@ -231,18 +277,21 @@ public ResponseEntity postImage( * @return CWL file representing the workflow. */ @PostMapping("/cwl") - @Operation(summary = "Retrieve a cwl file", description = "Retrieve a cwl file from the file system, describing the workflow.", tags = { - "Download" }, parameters = { - - @Parameter(name = "file_name", description = "Name of the CWL file (provided under 'figure_name' after the synthesis run).", example = "workflowSolution_0.cwl"), - @Parameter(name = "run_id", description = "ID of the corresponding synthesis run (provided under 'run_id' after the synthesis run).", example = "04ce2ef00c1685150252568") - - }, responses = { - @ApiResponse(responseCode = "200", description = "Successful operation. Taxonomy of data terms is provided.", content = @Content(mediaType = "application/x-yaml")), + @Operation(summary = "Retrieve a cwl file", + 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.", + required = true, + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = CWLFileInfo.class)) + ), + responses = { + @ApiResponse(responseCode = "200", description = "Successful operation. CWL 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 postCwl( @RequestBody(required = true) CWLFileInfo cwlInfoJson) throws IOException { @@ -259,16 +308,21 @@ public ResponseEntity postCwl( * @return CWL input file (.yml) representing the workflow inputs. */ @GetMapping("/cwl_input") - @Operation(summary = "Retrieve a cwl input file", description = "Retrieve a cwl input file from the file system, allowing to execute the workflows in the run.", tags = { - "Download" }, parameters = { - @Parameter(name = "run_id", description = "ID of the corresponding synthesis run (provided under 'run_id' after the synthesis run).", example = "04ce2ef00c1685150252568") - - }, responses = { - @ApiResponse(responseCode = "200", description = "Successful operation. Taxonomy of data terms is provided.", content = @Content(mediaType = "application/x-yaml")), + @Operation(summary = "Retrieve a cwl input file", + description = "Retrieve a cwl input file from the file system, allowing to execute the workflows in the run.", + tags = {"Download"}, + parameters = { + @Parameter(name = "run_id", + description = "ID of the corresponding synthesis run (provided under 'run_id' after the synthesis run).", + example = "04ce2ef00c1685150252568") + }, + responses = { + @ApiResponse(responseCode = "200", + description = "Successful operation. CWL input file representing the workflow inputs is provided.", + content = @Content(mediaType = "application/x-yaml")), @ApiResponse(responseCode = "400", description = "Invalid input"), @ApiResponse(responseCode = "404", description = "Not found") - - }) + }) public ResponseEntity getCwlInput( @RequestParam("run_id") String runID) { if (!RestApeUtils.isValidRunID(runID)) { @@ -295,17 +349,24 @@ public ResponseEntity getCwlInput( * @return CWL file representing the workflow. */ @GetMapping("/design_time_benchmarks") - @Operation(summary = "Retrieve a design-time benchmark file", description = "Retrieve a design-time benchmark file from the file system, describing the workflow.", tags = { - "Download" }, parameters = { - @Parameter(name = "file_name", description = "Name of the benchmark file (provided under 'bench_name' after the synthesis run).", example = "workflowSolution_0.json"), - @Parameter(name = "run_id", description = "ID of the corresponding synthesis run (provided under 'run_id' after the synthesis run).", example = "04ce2ef00c1685150252568") - - }, responses = { - @ApiResponse(responseCode = "200", description = "Successful operation. Taxonomy of data terms is provided.", content = @Content(mediaType = "application/x-yaml")), + @Operation(summary = "Retrieve a design-time benchmark file", + description = "Retrieve a design-time benchmark file from the file system, describing the workflow.", + tags = {"Download"}, + parameters = { + @Parameter(name = "file_name", + description = "Name of the benchmark file (provided under 'bench_name' after the synthesis run).", + example = "workflowSolution_0.json"), + @Parameter(name = "run_id", + description = "ID of the corresponding synthesis run (provided under 'run_id' after the synthesis run).", + example = "04ce2ef00c1685150252568") + }, + responses = { + @ApiResponse(responseCode = "200", + description = "Successful operation. Design-time benchmark file describing the workflow is provided.", + content = @Content(mediaType = "application/json")), @ApiResponse(responseCode = "400", description = "Invalid input"), @ApiResponse(responseCode = "404", description = "Not found") - - }) + }) public ResponseEntity getBenchmarks( @RequestParam("file_name") String fileName, @RequestParam("run_id") String runID) { @@ -326,23 +387,28 @@ 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_workflow_1.cwl"`). * * @param cwlFilesJson JSON object containing the run_id and the list of CWL * files. * @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 (provided under 'name' after the synthesis run).", tags = { - "Download" }, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "JSON object containing the following fields: 'run_id' and 'workflows'.", content = @Content(schema = @Schema(implementation = CWLZip.class))), parameters = { - @Parameter(name = "cwlFilesJson", description = "Synthesis run_id and the cwl file names.") }, responses = { - @ApiResponse(responseCode = "200", description = "Successful operation. Synthesis solutions are returned."), - @ApiResponse(responseCode = "400", description = "Invalid input"), - @ApiResponse(responseCode = "404", description = "Not found"), - @ApiResponse(responseCode = "500", description = "Internal server error"), - - }) + @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.", + 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)) + ), + responses = { + @ApiResponse(responseCode = "200", + description = "Successful operation. A zip file comprising specified CWL 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) { try { diff --git a/src/main/java/nl/esciencecenter/restape/APEWorkflowMetadata.java b/src/main/java/nl/esciencecenter/restape/APEWorkflowMetadata.java new file mode 100644 index 0000000..0e32af4 --- /dev/null +++ b/src/main/java/nl/esciencecenter/restape/APEWorkflowMetadata.java @@ -0,0 +1,88 @@ +package nl.esciencecenter.restape; + +import org.json.JSONObject; +import lombok.Getter; +import lombok.NoArgsConstructor; +import nl.uu.cs.ape.solver.solutionStructure.SolutionWorkflow; + +/** + * The {@link APEWorkflowMetadata} class represents metadata for a workflow solution from the APE solver. + * This class encapsulates the details and metadata of a workflow solution, + * including descriptive names, descriptions, the length of the solution, and benchmark information. + * It provides functionality to convert these details into a JSONObject for serialization or further processing. + */ +@Getter +@NoArgsConstructor +public class APEWorkflowMetadata { + + private String workflowName; + private String descriptiveName; + private String description; + private int workflowLength; + private String runId; + private String cwlName; + private String figureName; + private String benchmarkFile; // Optional, indicates if benchmark data should be included. + + /** + * Constructs a APEWorkflowMetadata instance from a given SolutionWorkflow and run configuration. + * + * @param sol The SolutionWorkflow containing necessary details of the workflow solution. + * @param runID The identifier for the run to which this solution belongs. + * @param benchmark Indicates whether benchmark data is to be included for this solution. + */ + public APEWorkflowMetadata(SolutionWorkflow sol, String runID, boolean benchmark) { + this.workflowName = sol.getFileName(); + this.descriptiveName = sol.getDescriptiveName(); + this.description = sol.getDescription(); + this.workflowLength = sol.getSolutionLength(); + this.runId = runID; + this.cwlName = sol.getFileName() + ".cwl"; + this.figureName = sol.getFileName(); + if (benchmark) { + this.benchmarkFile = sol.getFileName() + ".json"; + } + } + + /** + * Converts the APEWorkflowMetadata instance into a JSONObject representing its properties. + * This facilitates the serialization of the workflow solution's metadata to a JSON format, + * making it compatible with JSON-based data handling, APIs, and storage mechanisms. + * + * @return A JSONObject representation of the APEWorkflowMetadata instance. + */ + public JSONObject toJSONObject() { + JSONObject json = new JSONObject(); + json.put("workflow_name", this.workflowName); + json.put("descriptive_name", this.descriptiveName); + json.put("description", this.description); + json.put("workflow_length", this.workflowLength); + json.put("run_id", this.runId); + json.put("cwl_name", this.cwlName); + json.put("figure_name", this.figureName); + if (this.benchmarkFile != null) { + json.put("benchmark_file", this.benchmarkFile); + } + return json; + } + + /** + * Converts the APEWorkflowMetadata instance into a JSON string representing its properties. + * + * @return A JSON string representation of the APEWorkflowMetadata instance. + */ + public String toString() { + JSONObject json = new JSONObject(); + json.put("workflow_name", this.workflowName); + json.put("descriptive_name", this.descriptiveName); + json.put("description", this.description); + json.put("workflow_length", this.workflowLength); + json.put("run_id", this.runId); + json.put("cwl_name", this.cwlName); + json.put("figure_name", this.figureName); + if (this.benchmarkFile != null && !this.benchmarkFile.isEmpty()) { + json.put("benchmark_file", this.benchmarkFile); + } + return json.toString(); + } +} diff --git a/src/main/java/nl/esciencecenter/restape/ApeAPI.java b/src/main/java/nl/esciencecenter/restape/ApeAPI.java index ef13213..7ac933f 100644 --- a/src/main/java/nl/esciencecenter/restape/ApeAPI.java +++ b/src/main/java/nl/esciencecenter/restape/ApeAPI.java @@ -1,7 +1,10 @@ package nl.esciencecenter.restape; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; + import nl.uu.cs.ape.APE; import nl.uu.cs.ape.configuration.APECoreConfig; import nl.uu.cs.ape.configuration.APERunConfig; @@ -10,7 +13,6 @@ import nl.uu.cs.ape.models.AllPredicates; import nl.uu.cs.ape.models.AllTypes; import nl.uu.cs.ape.models.logic.constructs.TaxonomyPredicate; -import nl.uu.cs.ape.solver.solutionStructure.SolutionWorkflow; import nl.uu.cs.ape.solver.solutionStructure.SolutionsList; import nl.uu.cs.ape.utils.APEUtils; @@ -139,13 +141,13 @@ public static JSONArray getConstraints(String configFileURL) throws OWLOntologyC * @param configJson - configuration of the synthesis run * @param benchmark - boolean to indicate if the workflows should be * benchmarked - * @return - JSONArray with the metadata results of the synthesis, each element + * @return - List of {@link APEWorkflowMetadata}s with the metadata results of the synthesis, each element * describes a workflow solution (name, length, runID, path to a CWL * file, etc.). * @throws OWLOntologyCreationException * @throws IOException */ - public static JSONArray runSynthesis(JSONObject configJson, boolean benchmark) + public static List runSynthesis(JSONObject configJson, boolean benchmark) throws OWLOntologyCreationException, IOException { // Define the synthesis run ID @@ -205,32 +207,22 @@ private static SolutionsList executeSynthesis(JSONObject configJson, String runI * of the synthesis as well as information about the * synthesis run. * @runID - ID of the synthesis run - * @return - JSONArray with the results of the synthesis, each element describes + * @return - List of {@link APEWorkflowMetadata}s with the results of the synthesis, each element describes * a workflow solution (name, length, runID, path to a CWL file, etc.). */ - private static JSONArray workflowMetadataToJson(SolutionsList candidateSolutions, String runID, boolean benchmark) { - JSONArray generatedSolutionsJson = new JSONArray(); + private static List workflowMetadataToJson(SolutionsList candidateSolutions, String runID, boolean benchmark) { + List generatedSolutionsJson = new ArrayList<>(); if (candidateSolutions.isEmpty()) { - return new JSONArray(); + return new ArrayList<>(); } else { // Generate objects that return the solutions in JSON format int noSolutions = candidateSolutions.getNumberOfSolutions(); for (int i = 0; i < noSolutions; i++) { - SolutionWorkflow sol = candidateSolutions.get(i); - JSONObject solutionJson = new JSONObject(); - solutionJson.put("workflow_name", sol.getFileName()); - solutionJson.put("descriptive_name", sol.getDescriptiveName()); - solutionJson.put("description", sol.getDescription()); - solutionJson.put("workflow_length", sol.getSolutionLength()); - solutionJson.put("run_id", runID); - // Add reference to the generated cwl file and figure - solutionJson.put("cwl_name", sol.getFileName() + ".cwl"); - solutionJson.put("figure_name", sol.getFileName()); - if (benchmark) { - solutionJson.put("benchmark_file", sol.getFileName() + ".json"); - } - generatedSolutionsJson.put(solutionJson); + + APEWorkflowMetadata solutionJson = new APEWorkflowMetadata(candidateSolutions.get(i), runID, benchmark); + + generatedSolutionsJson.add(solutionJson); } return generatedSolutionsJson; } diff --git a/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java b/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java index f7a4c60..218829e 100644 --- a/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java +++ b/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java @@ -5,10 +5,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.junit.jupiter.api.Assertions.assertFalse; import java.nio.charset.StandardCharsets; -import java.util.HashMap; +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.beans.factory.annotation.Autowired; @@ -18,7 +17,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import nl.esciencecenter.controller.dto.CWLZip; +import nl.esciencecenter.restape.APEWorkflowMetadata; import nl.esciencecenter.restape.ApeAPI; import nl.uu.cs.ape.utils.APEFiles; @@ -140,10 +139,10 @@ void testPostZipCWLs() throws Exception { StandardCharsets.UTF_8); JSONObject jsonObject = new JSONObject(content); jsonObject.put("solutions", "1"); - JSONArray result = ApeAPI.runSynthesis(jsonObject, false); + List 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"); + String runID = result.get(0).getRunId(); + String cwlFile = result.get(0).getCwlName(); String jsonContent = "{\"run_id\": \"" + runID + "\", \"workflows\": [\"" + cwlFile + "\"]}"; diff --git a/src/test/java/nl/esciencecenter/restape/ApeAPITest.java b/src/test/java/nl/esciencecenter/restape/ApeAPITest.java index ae2bab3..c80b5ed 100644 --- a/src/test/java/nl/esciencecenter/restape/ApeAPITest.java +++ b/src/test/java/nl/esciencecenter/restape/ApeAPITest.java @@ -5,9 +5,9 @@ import java.io.IOException; 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.semanticweb.owlapi.model.OWLOntologyCreationException; @@ -32,7 +32,7 @@ void runSynthesisFail() throws IOException, OWLOntologyCreationException { StandardCharsets.UTF_8); JSONObject jsonObject = new JSONObject(content); jsonObject.put("solutions", "1"); - JSONArray result = ApeAPI.runSynthesis(jsonObject, false); + List result = ApeAPI.runSynthesis(jsonObject, false); assertTrue(result.isEmpty(), "The encoding should be UNSAT."); } @@ -50,7 +50,7 @@ void runSynthesisPass() throws IOException, OWLOntologyCreationException { StandardCharsets.UTF_8); JSONObject jsonObject = new JSONObject(content); jsonObject.put("solutions", "1"); - JSONArray result = ApeAPI.runSynthesis(jsonObject, false); + List result = ApeAPI.runSynthesis(jsonObject, false); assertFalse(result.isEmpty(), "The encoding should be SAT."); } @@ -68,7 +68,7 @@ void runSynthesisAndBenchmarkPass() throws IOException, OWLOntologyCreationExcep StandardCharsets.UTF_8); JSONObject jsonObject = new JSONObject(content); jsonObject.put("solutions", "1"); - JSONArray result = ApeAPI.runSynthesis(jsonObject, true); + List result = ApeAPI.runSynthesis(jsonObject, true); assertFalse(result.isEmpty(), "The encoding should be SAT."); } } diff --git a/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java b/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java index 2f35899..b69737f 100644 --- a/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java +++ b/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java @@ -6,7 +6,6 @@ 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; @@ -25,10 +24,10 @@ void testZipFilesForLocalExecution() throws Exception { StandardCharsets.UTF_8); JSONObject jsonObject = new JSONObject(content); jsonObject.put("solutions", "1"); - JSONArray result = ApeAPI.runSynthesis(jsonObject, false); + List 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"); + String runID = result.get(0).getRunId(); + String cwlFile = result.get(0).getCwlName(); CWLZip cwlZip = new CWLZip(); cwlZip.setRunID(runID);