From d8bbcb3cc2fb649c12486ef7d6f8bc4b42159e02 Mon Sep 17 00:00:00 2001 From: jcomedouteau Date: Wed, 16 Oct 2024 14:28:25 +0200 Subject: [PATCH] #2388-automatic executions - back --- .../ng/vip/controller/ExecutionApi.java | 18 +--- .../controller/ExecutionApiController.java | 25 ++--- .../controller/PlannedExecutionApi.java | 74 +++++++++++++++ .../PlannedExecutionApiController.java | 52 ++++++++++ .../model/PlannedExecution.java} | 34 +++++-- .../PlannedExecutionRepository.java | 12 +++ .../service/PlannedExecutionService.java | 23 +++++ .../service/PlannedExecutionServiceImpl.java | 94 +++++++++++++++++++ shanoir-uploader/pom.xml | 7 -- 9 files changed, 294 insertions(+), 45 deletions(-) create mode 100644 shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/controller/PlannedExecutionApi.java create mode 100644 shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/controller/PlannedExecutionApiController.java rename shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/{model/AutomaticExecution.java => planning/model/PlannedExecution.java} (58%) create mode 100644 shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/repository/PlannedExecutionRepository.java create mode 100644 shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/service/PlannedExecutionService.java create mode 100644 shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/service/PlannedExecutionServiceImpl.java diff --git a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/controller/ExecutionApi.java b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/controller/ExecutionApi.java index 0c216b1058..8b8e9fc228 100644 --- a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/controller/ExecutionApi.java +++ b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/controller/ExecutionApi.java @@ -26,13 +26,14 @@ import org.shanoir.ng.shared.exception.SecurityException; import org.shanoir.ng.vip.dto.ExecutionCandidateDTO; import org.shanoir.ng.vip.dto.VipExecutionDTO; -import org.shanoir.ng.vip.model.AutomaticExecution; import org.shanoir.ng.vip.monitoring.model.ExecutionStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; import java.io.IOException; -import java.util.List; /** * @author Alae Es-saki @@ -65,17 +66,6 @@ ResponseEntity createExecution( method = RequestMethod.GET) ResponseEntity getExecution(@Parameter(description = "The execution identifier", required=true) @PathVariable("identifier") String identifier) throws IOException, RestServiceException, EntityNotFoundException, SecurityException; - @Operation(summary = "Get list of existing automatic executions for the given study_id", description = "Returns the list of existing automatic executions for the given study id", tags={ }) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "successful response, returns the list of automatic executions"), - @ApiResponse(responseCode = "403", description = "forbidden"), - @ApiResponse(responseCode = "500", description = "unexpected error"), - @ApiResponse(responseCode = "503", description = "Internal error")}) - @GetMapping(value = "/automatic/{studyId}", - produces = { "application/json", "application/octet-stream" }) - ResponseEntity> getAutomaticExecutions(@Parameter(description = "The study Id", required=true) @PathVariable("studyId") Long studyId) throws IOException, RestServiceException, EntityNotFoundException, SecurityException; - - @Operation(summary = "Get stderr logs for the given VIP execution identifier", description = "Returns the stderr logs of the VIP execution that has the given identifier in parameter.", tags={ }) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "successful response, returns the status"), diff --git a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/controller/ExecutionApiController.java b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/controller/ExecutionApiController.java index 7cc2af12bc..63d81d4155 100644 --- a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/controller/ExecutionApiController.java +++ b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/controller/ExecutionApiController.java @@ -30,7 +30,6 @@ import org.shanoir.ng.vip.dto.DatasetParameterDTO; import org.shanoir.ng.vip.dto.ExecutionCandidateDTO; import org.shanoir.ng.vip.dto.VipExecutionDTO; -import org.shanoir.ng.vip.model.AutomaticExecution; import org.shanoir.ng.vip.monitoring.model.ExecutionMonitoring; import org.shanoir.ng.vip.monitoring.model.ExecutionStatus; import org.shanoir.ng.vip.monitoring.schedule.ExecutionStatusMonitorService; @@ -46,7 +45,10 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Controller public class ExecutionApiController implements ExecutionApi { @@ -96,7 +98,7 @@ public ResponseEntity createExecution( return new ResponseEntity<>(createdMonitoring, HttpStatus.OK); } - private List getDatasetsFromParams(List parameters){ + private List getDatasetsFromParams(List parameters) { List datasetsIds = new ArrayList<>(); for (DatasetParameterDTO param : parameters) { datasetsIds.addAll(param.getDatasetIds()); @@ -189,13 +191,13 @@ private String getResultsLocationUri(String resultLocation, ExecutionCandidateDT + "&md5=none&type=File"; } - private String getInputValueUri(ExecutionCandidateDTO candidate, String groupBy, String exportFormat, String resourceId, String authenticationToken){ + private String getInputValueUri(ExecutionCandidateDTO candidate, String groupBy, String exportFormat, String resourceId, String authenticationToken) { String entityName = "resource_id+" + resourceId + "+" + groupBy + ("dcm".equals(exportFormat) ? ".zip" : ".nii.gz"); return SHANOIR_URI_SCHEME + entityName + "?format=" + exportFormat + "&resourceId=" + resourceId + "&token=" + authenticationToken - + (candidate.getConverterId() != null ? ("&converterId=" + candidate.getConverterId()) : "") + + (candidate.getConverterId() != null ? ("&converterId=" + candidate.getConverterId()) : "") + "&refreshToken=" + candidate.getRefreshToken() + "&clientId=" + candidate.getClient() + "&md5=none&type=File"; @@ -226,13 +228,13 @@ private ExecutionMonitoring createExecutionMonitoring(ExecutionCandidateDTO exec } @Override - public ResponseEntity getExecution(@Parameter(description = "The execution identifier", required=true) @PathVariable("identifier") String identifier) { + public ResponseEntity getExecution(@Parameter(description = "The execution identifier", required = true) @PathVariable("identifier") String identifier) { return ResponseEntity.ok(vipClient.getExecution(identifier).block()); } @Override - public ResponseEntity getExecutionStatus(@Parameter(description = "The execution identifier", required=true) @PathVariable("identifier") String identifier) { + public ResponseEntity getExecutionStatus(@Parameter(description = "The execution identifier", required = true) @PathVariable("identifier") String identifier) { return ResponseEntity.ok(vipClient.getExecution(identifier).map(VipExecutionDTO::getStatus).block()); } @@ -246,12 +248,5 @@ public ResponseEntity getExecutionStderr(String identifier) { public ResponseEntity getExecutionStdout(String identifier) { return ResponseEntity.ok(vipClient.getExecutionStdout(identifier).block()); } - - @Override - public ResponseEntity> getAutomaticExecutions(@Parameter(description = "The study Id", required=true) @PathVariable("studyId") Long studyId) { - AutomaticExecution autoExec = new AutomaticExecution(); - autoExec.setName("superbname"); - return new ResponseEntity<>(Collections.singletonList(autoExec), HttpStatus.OK); - } - } + diff --git a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/controller/PlannedExecutionApi.java b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/controller/PlannedExecutionApi.java new file mode 100644 index 0000000000..f10a0946c8 --- /dev/null +++ b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/controller/PlannedExecutionApi.java @@ -0,0 +1,74 @@ +package org.shanoir.ng.vip.planning.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.shanoir.ng.shared.exception.EntityNotFoundException; +import org.shanoir.ng.shared.exception.RestServiceException; +import org.shanoir.ng.shared.exception.SecurityException; +import org.shanoir.ng.vip.planning.model.PlannedExecution; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +/* + Planned execution are VIP execution atuomatically applied after an import. + */ +@Tag(name = "plannedexecution", description = "the planned execution API") +@RequestMapping("/vip/execution/planned") +public interface PlannedExecutionApi { + + @Operation(summary = "Get list of existing planned executions for the given study_id", description = "Returns the list of existing planned executions for the given study id", tags={ }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "successful response, returns the list of planned executions"), + @ApiResponse(responseCode = "403", description = "forbidden"), + @ApiResponse(responseCode = "500", description = "unexpected error"), + @ApiResponse(responseCode = "503", description = "Internal error")}) + @GetMapping(value = "/byStudy/{studyId}", + produces = { "application/json", "application/octet-stream" }) + ResponseEntity> getPlannedExecutionsByStudyId(@Parameter(description = "The study Id", required=true) @PathVariable("studyId") Long studyId) throws IOException, RestServiceException, EntityNotFoundException, SecurityException; + + @Operation(summary = "Create a new PlannedExecution entity", description = "Creates a new planned execution", tags={ }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "successful creation"), + @ApiResponse(responseCode = "403", description = "forbidden"), + @ApiResponse(responseCode = "500", description = "unexpected error"), + @ApiResponse(responseCode = "503", description = "Internal error")}) + @PostMapping(value = "/create", consumes = "application/json", produces = "application/json") + ResponseEntity createPlannedExecution(@RequestBody PlannedExecution plannedExecution) throws IOException, RestServiceException, SecurityException; + + @Operation(summary = "Delete a PlannedExecution entity", description = "Deletes the planned execution by its ID", tags={ }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "successful deletion"), + @ApiResponse(responseCode = "403", description = "forbidden"), + @ApiResponse(responseCode = "404", description = "not found"), + @ApiResponse(responseCode = "500", description = "unexpected error"), + @ApiResponse(responseCode = "503", description = "Internal error")}) + @DeleteMapping(value = "/delete/{executionId}") + ResponseEntity deletePlannedExecution(@Parameter(description = "The PlannedExecution Id", required=true) @PathVariable("executionId") Long executionId) throws IOException, RestServiceException, EntityNotFoundException, SecurityException; + + @Operation(summary = "Get a PlannedExecution entity by ID", description = "Returns a planned execution by its ID", tags={ }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "successful response, returns the planned execution"), + @ApiResponse(responseCode = "403", description = "forbidden"), + @ApiResponse(responseCode = "404", description = "not found"), + @ApiResponse(responseCode = "500", description = "unexpected error"), + @ApiResponse(responseCode = "503", description = "Internal error")}) + @GetMapping(value = "/{executionId}", produces = "application/json") + ResponseEntity getPlannedExecutionById(@Parameter(description = "The PlannedExecution Id", required=true) @PathVariable("executionId") Long executionId) throws IOException, RestServiceException, EntityNotFoundException, SecurityException; + + @Operation(summary = "Update a PlannedExecution entity", description = "Updates the existing planned execution by its ID", tags={ }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "successful update, returns the updated planned execution"), + @ApiResponse(responseCode = "403", description = "forbidden"), + @ApiResponse(responseCode = "404", description = "not found"), + @ApiResponse(responseCode = "500", description = "unexpected error"), + @ApiResponse(responseCode = "503", description = "Internal error")}) + @PostMapping(value = "/update/{executionId}", consumes = "application/json", produces = "application/json") + ResponseEntity updatePlannedExecution(@RequestBody PlannedExecution plannedExecution) throws IOException, RestServiceException, EntityNotFoundException, SecurityException; + +} diff --git a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/controller/PlannedExecutionApiController.java b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/controller/PlannedExecutionApiController.java new file mode 100644 index 0000000000..a13c800c78 --- /dev/null +++ b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/controller/PlannedExecutionApiController.java @@ -0,0 +1,52 @@ +package org.shanoir.ng.vip.planning.controller; + +import io.swagger.v3.oas.annotations.Parameter; +import org.shanoir.ng.shared.exception.EntityNotFoundException; +import org.shanoir.ng.shared.exception.RestServiceException; +import org.shanoir.ng.shared.exception.SecurityException; +import org.shanoir.ng.vip.planning.model.PlannedExecution; +import org.shanoir.ng.vip.planning.service.PlannedExecutionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; + +import java.io.IOException; +import java.util.List; + +/* + Planned execution are VIP execution atuomatically applied after an import. + */ +@Service +public class PlannedExecutionApiController implements PlannedExecutionApi { + + @Autowired + PlannedExecutionService plannedExecutionService; + + @Override + public ResponseEntity> getPlannedExecutionsByStudyId (@Parameter(description = "The study Id", required=true) @PathVariable("studyId") Long studyId) { + List executions = this.plannedExecutionService.findByStudyId(studyId); + return new ResponseEntity<>(executions, HttpStatus.OK); + } + + @Override + public ResponseEntity createPlannedExecution(@RequestBody PlannedExecution plannedExecution) throws IOException, RestServiceException, SecurityException { + return new ResponseEntity(this.plannedExecutionService.save(plannedExecution), HttpStatus.OK); + } + @Override + public ResponseEntity deletePlannedExecution(@Parameter(description = "The PlannedExecution Id", required=true) @PathVariable("executionId") Long executionId) throws IOException, RestServiceException, EntityNotFoundException, SecurityException{ + this.plannedExecutionService.delete(executionId); + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + @Override + public ResponseEntity getPlannedExecutionById(@Parameter(description = "The PlannedExecution Id", required=true) @PathVariable("executionId") Long executionId) throws IOException, RestServiceException, EntityNotFoundException, SecurityException{ + return new ResponseEntity(this.plannedExecutionService.findById(executionId), HttpStatus.OK); + } + @Override + public ResponseEntity updatePlannedExecution(@RequestBody PlannedExecution plannedExecution) throws IOException, RestServiceException, EntityNotFoundException, SecurityException { + return new ResponseEntity(this.plannedExecutionService.update(plannedExecution), HttpStatus.OK); + } + +} diff --git a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/model/AutomaticExecution.java b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/model/PlannedExecution.java similarity index 58% rename from shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/model/AutomaticExecution.java rename to shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/model/PlannedExecution.java index 5779c58561..699ff41535 100644 --- a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/model/AutomaticExecution.java +++ b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/model/PlannedExecution.java @@ -1,18 +1,18 @@ -package org.shanoir.ng.vip.model; +package org.shanoir.ng.vip.planning.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.*; import org.shanoir.ng.shared.model.Study; +import org.shanoir.ng.vip.monitoring.model.PipelineParameter; + +import java.util.List; /** * This class represents the associated criterias for an automatic execution realized after an import in shanoir. */ @Entity @JsonIgnoreProperties(ignoreUnknown = true) -public class AutomaticExecution { +public class PlannedExecution { @Id private Long id; @@ -23,11 +23,11 @@ public class AutomaticExecution { @JoinColumn(name = "study_id") private Study study; - /* + private String execution; + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) - @JoinTable(name = "pipeline_parameter") + @JoinTable(name = "execution_pipeline_parameters") private List parameters; - */ public String getName() { return name; @@ -52,4 +52,20 @@ public Study getStudy() { public void setStudy(Study study) { this.study = study; } + + public String getExecution() { + return execution; + } + + public void setExecution(String execution) { + this.execution = execution; + } + + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } } diff --git a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/repository/PlannedExecutionRepository.java b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/repository/PlannedExecutionRepository.java new file mode 100644 index 0000000000..ecfc42b0af --- /dev/null +++ b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/repository/PlannedExecutionRepository.java @@ -0,0 +1,12 @@ +package org.shanoir.ng.vip.planning.repository; + +import org.shanoir.ng.vip.planning.model.PlannedExecution; +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +public interface PlannedExecutionRepository extends CrudRepository { + + List findByStudyId(Long studyId); + +} diff --git a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/service/PlannedExecutionService.java b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/service/PlannedExecutionService.java new file mode 100644 index 0000000000..3653e043ec --- /dev/null +++ b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/service/PlannedExecutionService.java @@ -0,0 +1,23 @@ +package org.shanoir.ng.vip.planning.service; + +import org.shanoir.ng.datasetacquisition.model.DatasetAcquisition; +import org.shanoir.ng.vip.planning.model.PlannedExecution; +import org.springframework.scheduling.annotation.Async; + +import java.util.List; + +public interface PlannedExecutionService { + + @Async + void checkForPlannedExecutions(List createdAcquisitions); + + List findByStudyId(Long studyId); + + PlannedExecution update(PlannedExecution plannedExecution); + + PlannedExecution findById(Long executionId); + + void delete(Long executionId); + + PlannedExecution save(PlannedExecution plannedExecution); +} diff --git a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/service/PlannedExecutionServiceImpl.java b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/service/PlannedExecutionServiceImpl.java new file mode 100644 index 0000000000..84f78eeade --- /dev/null +++ b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/vip/planning/service/PlannedExecutionServiceImpl.java @@ -0,0 +1,94 @@ +package org.shanoir.ng.vip.planning.service; + +import org.shanoir.ng.datasetacquisition.model.DatasetAcquisition; +import org.shanoir.ng.vip.planning.model.PlannedExecution; +import org.shanoir.ng.vip.planning.repository.PlannedExecutionRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class PlannedExecutionServiceImpl implements PlannedExecutionService { + + private static final Logger LOG = LoggerFactory.getLogger(PlannedExecutionServiceImpl.class); + + @Autowired + private PlannedExecutionRepository plannedExecutionRepository; + + @Override + public List findByStudyId(Long studyId) { + return plannedExecutionRepository.findByStudyId(studyId); + } + + @Override + public PlannedExecution update(PlannedExecution plannedExecution) { + return this.plannedExecutionRepository.save(plannedExecution); + } + + @Override + public PlannedExecution findById(Long executionId) { + return this.plannedExecutionRepository.findById(executionId).orElse(null); + } + + @Override + public void delete(Long executionId) { + PlannedExecution execution = this.findById(executionId); + if (execution == null) { + // Already deleted + return; + } + this.plannedExecutionRepository.delete(execution); + } + + @Override + public PlannedExecution save(PlannedExecution plannedExecution) { + return this.plannedExecutionRepository.save(plannedExecution); + } + + /** + * This method is called aynchroneously at the end of the import to check if a planned execution has to be done. + * @param createdAcquisitions the list of acqusitions to check for planned executions + */ + @Override + @Async + public void checkForPlannedExecutions(List createdAcquisitions) { + if (createdAcquisitions == null || createdAcquisitions.isEmpty()) { + LOG.error("No data imported, no planned execution."); + return; + } + // Retrieve the list of potential executions to plan + List potentialPlannedExecutions = this.findByStudyId(createdAcquisitions.get(0).getExamination().getStudyId()); + + // Filter the executions to apply + List executionsToApply = new ArrayList<>(); + for (PlannedExecution potentialPlannedExecution : potentialPlannedExecutions) { + if (filterExecution(potentialPlannedExecution, createdAcquisitions)) { + executionsToApply.add(potentialPlannedExecution); + } + } + + if (executionsToApply.isEmpty()) { + LOG.error("No executions filter match the import, no planned execution."); + return; + } + + // Apply all filtered executions + for(PlannedExecution executionToApply : executionsToApply) { + applyExecution(executionToApply, createdAcquisitions); + } + } + + private boolean filterExecution(PlannedExecution potentialPlannedExecution, List createdAcquisitions) { + // TODO: complete + return true; + } + + private void applyExecution(PlannedExecution executionToApply, List createdAcquisitions) { + // Create an execution and infer the logic + } +} diff --git a/shanoir-uploader/pom.xml b/shanoir-uploader/pom.xml index 706a9714fc..e0861e5c7f 100644 --- a/shanoir-uploader/pom.xml +++ b/shanoir-uploader/pom.xml @@ -17,7 +17,6 @@ 6.1.3 1.5.3 - 1.18.26 5.31.1 25.0.4 2.13.4 @@ -213,12 +212,6 @@ 4.0.0 runtime - - org.projectlombok - lombok - ${lombok.version} - provided - org.json