diff --git a/src/main/java/org/gridsuite/study/server/StudyConstants.java b/src/main/java/org/gridsuite/study/server/StudyConstants.java index ba4cc3bdb..d99451441 100644 --- a/src/main/java/org/gridsuite/study/server/StudyConstants.java +++ b/src/main/java/org/gridsuite/study/server/StudyConstants.java @@ -88,6 +88,9 @@ private StudyConstants() { public static final String QUERY_PARAM_ACTIVATED = "activated"; public static final String PATH_PARAM_PARAMETERS = "parameters"; public static final String DYNA_FLOW_PROVIDER = "DynaFlow"; + public static final String HEADER_FORMAT = "format"; + public static final String HEADER_FILE_NAME = "fileName"; + public static final String NETWORK_EXPORT_SUCCEEDED = "networkExportSucceeded"; public enum SldDisplayMode { FEEDER_POSITION, diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 29f61f438..5d4337a26 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -942,6 +942,13 @@ public ResponseEntity getExportFormats() { return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(formatsJson); } + @GetMapping(value = "/download-network-file/{exportUuid}") + @Operation(summary = "get the export file") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The available export file")}) + public void downloadExportNetworkFile(@PathVariable("exportUuid") UUID exportUuid, HttpServletResponse response) { + studyService.downloadExportNetworkFile(exportUuid, response); + } + @GetMapping(value = "/studies/{studyUuid}/root-networks/{rootNetworkUuid}/nodes/{nodeUuid}/export-network/{format}") @Operation(summary = "export the study's network in the given format") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The network in the given format")}) @@ -952,9 +959,10 @@ public void exportNetwork( @PathVariable("format") String format, @RequestParam(value = "formatParameters", required = false) String parametersJson, @RequestParam(value = "fileName") String fileName, + @RequestHeader(HEADER_USER_ID) String userId, HttpServletResponse response) { studyService.assertRootNodeOrBuiltNode(studyUuid, nodeUuid, rootNetworkUuid); - studyService.exportNetwork(nodeUuid, rootNetworkUuid, format, parametersJson, fileName, response); + studyService.exportNetwork(studyUuid, nodeUuid, rootNetworkUuid, format, parametersJson, fileName, userId, response); } @PostMapping(value = "/studies/{studyUuid}/root-networks/{rootNetworkUuid}/nodes/{nodeUuid}/security-analysis/run") diff --git a/src/main/java/org/gridsuite/study/server/notification/NotificationService.java b/src/main/java/org/gridsuite/study/server/notification/NotificationService.java index 49f2c6d19..588269c84 100644 --- a/src/main/java/org/gridsuite/study/server/notification/NotificationService.java +++ b/src/main/java/org/gridsuite/study/server/notification/NotificationService.java @@ -26,6 +26,8 @@ import java.time.Instant; import java.util.*; +import static org.gridsuite.study.server.StudyConstants.*; + /** * @author Nicolas Noir @@ -59,7 +58,7 @@ public class ConsumerService { private static final String HEADER_CASE_NAME = "caseName"; private static final String HEADER_WITH_RATIO_TAP_CHANGERS = "withRatioTapChangers"; private static final String HEADER_ERROR_MESSAGE = "errorMessage"; - + private static final String HEADER_EXPORT_UUID = "exportUuid"; private final ObjectMapper objectMapper; private final NotificationService notificationService; @@ -829,4 +828,25 @@ public Consumer> consumeStateEstimationStopped() { public Consumer> consumeStateEstimationFailed() { return message -> consumeCalculationFailed(message, STATE_ESTIMATION); } + + public void consumeNetworkExportSucceeded(Message msg) { + Optional.ofNullable(msg.getHeaders().get(NETWORK_UUID, String.class)) + .map(UUID::fromString) + .ifPresent(networkUuid -> { + UUID studyUuid = UUID.fromString(msg.getHeaders().get(HEADER_STUDY_UUID, String.class)); + UUID nodeUuid = UUID.fromString(Objects.requireNonNull(msg.getHeaders().get("nodeUuid")).toString()); + UUID rootNetworkUuid = UUID.fromString(Objects.requireNonNull(msg.getHeaders().get(HEADER_ROOT_NETWORK_UUID)).toString()); + String format = (String) msg.getHeaders().get("format"); + String userId = (String) msg.getHeaders().get(HEADER_USER_ID); + String fileName = (String) msg.getHeaders().get(HEADER_FILE_NAME); + UUID exportUuid = UUID.fromString(Objects.requireNonNull(msg.getHeaders().get(HEADER_EXPORT_UUID)).toString()); + String errorMessage = (String) msg.getHeaders().get(HEADER_ERROR_MESSAGE); + notificationService.emitNetworkExportSucceeded(studyUuid, nodeUuid, rootNetworkUuid, format, userId, fileName, exportUuid, errorMessage); + }); + } + + @Bean + public Consumer> consumeNetworkExportSucceeded() { + return this::consumeNetworkExportSucceeded; + } } diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkConversionService.java b/src/main/java/org/gridsuite/study/server/service/NetworkConversionService.java index 5e79d4d4d..b128a43f9 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkConversionService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkConversionService.java @@ -104,7 +104,39 @@ public String getExportFormats() { return restTemplate.exchange(networkConversionServerBaseUri + path, HttpMethod.GET, null, typeRef).getBody(); } - public void exportNetwork(UUID networkUuid, String variantId, String format, String parametersJson, String fileName, HttpServletResponse exportNetworkResponse) { + public void downloadExportNetworkFile(UUID exportUuid, HttpServletResponse exportNetworkResponse) { + + try (ServletOutputStream outputStream = exportNetworkResponse.getOutputStream()) { + var uriComponentsBuilder = UriComponentsBuilder.fromPath(DELIMITER + NETWORK_CONVERSION_API_VERSION + "/download/{exportUuid}"); + String path = uriComponentsBuilder.buildAndExpand(exportUuid).toUriString(); + + restTemplate.execute( + networkConversionServerBaseUri + path, + HttpMethod.GET, + request -> { }, + networkConversionServerResponse -> { + String fileNameFromResponse = networkConversionServerResponse.getHeaders().getContentDisposition().getFilename(); + long contentLength = networkConversionServerResponse.getHeaders().getContentLength(); + exportNetworkResponse.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.builder("attachment").filename(fileNameFromResponse).build().toString()); + exportNetworkResponse.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM.toString()); + if (contentLength != -1) { + exportNetworkResponse.setContentLengthLong(contentLength); + } + exportNetworkResponse.setStatus(HttpStatus.OK.value()); + StreamUtils.copy(networkConversionServerResponse.getBody(), outputStream); + outputStream.flush(); + return null; + } + ); + } catch (HttpStatusCodeException e) { + throw handleHttpError(e, NETWORK_EXPORT_FAILED); + } catch (IOException e) { + throw new StudyException(NETWORK_EXPORT_FAILED, e.getMessage()); + } + } + + public void exportNetwork(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, UUID networkUuid, String variantId, String format, + String parametersJson, String fileName, String userId, HttpServletResponse exportNetworkResponse) { try (ServletOutputStream outputStream = exportNetworkResponse.getOutputStream()) { var uriComponentsBuilder = UriComponentsBuilder.fromPath(DELIMITER + NETWORK_CONVERSION_API_VERSION @@ -116,31 +148,27 @@ public void exportNetwork(UUID networkUuid, String variantId, String format, Str if (!StringUtils.isEmpty(fileName)) { uriComponentsBuilder.queryParam("fileName", fileName); } - + String receiver = studyUuid + "|" + nodeUuid + "|" + rootNetworkUuid + "|" + userId; + uriComponentsBuilder.queryParam("receiver", receiver); String path = uriComponentsBuilder.buildAndExpand(networkUuid, format) .toUriString(); restTemplate.execute( - networkConversionServerBaseUri + path, - HttpMethod.POST, - request -> { - request.getHeaders().setContentType(MediaType.APPLICATION_JSON); - if (parametersJson != null && !parametersJson.isEmpty()) { - StreamUtils.copy(parametersJson, StandardCharsets.UTF_8, request.getBody()); - } - }, - networkConversionServerResponse -> { - String fileNameFromResponse = networkConversionServerResponse.getHeaders().getContentDisposition().getFilename(); - long contentLength = networkConversionServerResponse.getHeaders().getContentLength(); - exportNetworkResponse.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.builder("attachment").filename(fileNameFromResponse, StandardCharsets.UTF_8).build().toString()); - exportNetworkResponse.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM.toString()); - if (contentLength != -1) { - exportNetworkResponse.setContentLengthLong(contentLength); + networkConversionServerBaseUri + path, + HttpMethod.POST, + request -> { + request.getHeaders().setContentType(MediaType.APPLICATION_JSON); + if (parametersJson != null && !parametersJson.isEmpty()) { + StreamUtils.copy(parametersJson, StandardCharsets.UTF_8, request.getBody()); + } + }, + networkConversionServerResponse -> { + exportNetworkResponse.setStatus(HttpStatus.ACCEPTED.value()); + exportNetworkResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); + String responseJson = "{\"message\":\"Export started, you will receive a notification when ready\"}"; + StreamUtils.copy(responseJson, StandardCharsets.UTF_8, outputStream); + return null; } - exportNetworkResponse.setStatus(HttpStatus.OK.value()); - StreamUtils.copy(networkConversionServerResponse.getBody(), outputStream); - return null; - } ); } catch (HttpStatusCodeException e) { throw handleHttpError(e, NETWORK_EXPORT_FAILED); diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index e6e1e643d..84eeaa4fd 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -1051,11 +1051,16 @@ private void handleLoadflowRequest(StudyEntity studyEntity, UUID nodeUuid, UUID notificationService.emitStudyChanged(studyEntity.getId(), nodeUuid, rootNetworkUuid, LOAD_FLOW.getUpdateStatusType()); } - public void exportNetwork(UUID nodeUuid, UUID rootNetworkUuid, String format, String parametersJson, String fileName, HttpServletResponse exportNetworkResponse) { + public void exportNetwork(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, String format, String parametersJson, String fileName, String userId, HttpServletResponse exportNetworkResponse) { UUID networkUuid = rootNetworkService.getNetworkUuid(rootNetworkUuid); String variantId = networkModificationTreeService.getVariantId(nodeUuid, rootNetworkUuid); - networkConversionService.exportNetwork(networkUuid, variantId, format, parametersJson, fileName, exportNetworkResponse); + networkConversionService.exportNetwork(studyUuid, nodeUuid, rootNetworkUuid, networkUuid, variantId, format, + parametersJson, fileName, userId, exportNetworkResponse); + } + + public void downloadExportNetworkFile(UUID exportUuid, HttpServletResponse exportNetworkResponse) { + networkConversionService.downloadExportNetworkFile(exportUuid, exportNetworkResponse); } @Transactional(readOnly = true) diff --git a/src/main/resources/config/application.yaml b/src/main/resources/config/application.yaml index c2cc32a42..3bf42dd48 100644 --- a/src/main/resources/config/application.yaml +++ b/src/main/resources/config/application.yaml @@ -15,7 +15,7 @@ spring: consumeVoltageInitDebug;consumeVoltageInitResult;consumeVoltageInitStopped;consumeVoltageInitFailed;consumeVoltageInitCancelFailed;\ consumeLoadFlowResult;consumeLoadFlowStopped;consumeLoadFlowFailed;consumeLoadFlowCancelFailed;\ consumeNonEvacuatedEnergyResult;consumeNonEvacuatedEnergyStopped;consumeNonEvacuatedEnergyFailed;consumeNonEvacuatedEnergyCancelFailed;\ - consumeStateEstimationResult;consumeStateEstimationStopped;consumeStateEstimationFailed" + consumeStateEstimationResult;consumeStateEstimationStopped;consumeStateEstimationFailed;consumeNetworkExportSucceeded" stream: bindings: publishStudyUpdate-out-0: @@ -154,6 +154,9 @@ spring: consumeCaseImportFailed-in-0: destination: ${powsybl-ws.rabbitmq.destination.prefix:}case.import.start.dlx group: dlq + consumeNetworkExportSucceeded-in-0: + destination: ${powsybl-ws.rabbitmq.destination.prefix:}network.export.succeeded + group: dlq output-bindings: publishStudyUpdate-out-0;publishElementUpdate-out-0 powsybl: