From 7464d46847b6873054e94fc3dbe06d5bd331a65f Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 18 Aug 2026 15:26:36 +0200 Subject: [PATCH 01/18] export computations parameters --- .../server/service/StudyExportService.java | 18 ++++- .../study/server/service/StudyService.java | 5 ++ .../common/ComputationParametersService.java | 79 ++++++++++++++++--- .../studycontroller/TreeExportTest.java | 6 ++ .../wiremock/ComputationServerStubs.java | 9 +++ 5 files changed, 102 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index 39785ae8b6..4c174a2758 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -27,6 +27,7 @@ import java.nio.file.attribute.PosixFilePermissions; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.stream.Stream; @@ -44,6 +45,7 @@ public class StudyExportService { private static final Logger LOGGER = LoggerFactory.getLogger(StudyExportService.class); public static final String TREE_JSON_FILE_NAME = "tree.json"; public static final String CASES_FOLDER = "cases"; + public static final String PARAMETERS_FOLDER = "parameters"; private final StudyService studyService; private final CaseService caseService; @@ -68,7 +70,7 @@ public InputStreamResource exportStudy(UUID studyUuid, String userId) { Path tempDir = createTempWorkDir(studyUuid); Path zipFile = null; try { - zipFile = compressStudyToZip(studyUuid, tempDir); + zipFile = compressStudyToZip(studyUuid, userId, tempDir); InputStream stream = Files.newInputStream(zipFile, StandardOpenOption.DELETE_ON_CLOSE); zipFile = null; return new InputStreamResource(stream); @@ -93,7 +95,7 @@ public InputStreamResource exportStudy(UUID studyUuid, String userId) { /** * Build tree.json and the case files under tempDir, then compress them into a temp zip file */ - private Path compressStudyToZip(UUID studyUuid, Path tempDir) throws IOException { + private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) throws IOException { TreeExportInfos treeExportInfos = studyService.buildTreeExport(studyUuid); Path studyJsonPath = tempDir.resolve(TREE_JSON_FILE_NAME); objectMapper.writerWithDefaultPrettyPrinter().writeValue(studyJsonPath.toFile(), treeExportInfos); @@ -103,6 +105,7 @@ private Path compressStudyToZip(UUID studyUuid, Path tempDir) throws IOException String caseName = rootNetworkInfos.caseInfos().getCaseName(); exportCaseFile(caseUuid, caseName, casesDir); } + exportComputationParameters(studyUuid, userId, tempDir); Path zipFile = createTempExportFile(studyUuid); try (OutputStream fos = Files.newOutputStream(zipFile); ZipOutputStream zipOut = new ZipOutputStream(fos)) { @@ -111,6 +114,17 @@ private Path compressStudyToZip(UUID studyUuid, Path tempDir) throws IOException return zipFile; } + private void exportComputationParameters(UUID studyUuid, String userId, Path tempDir) throws IOException { + Map parametersByFileName = studyService.exportComputationParameters(studyUuid, userId); + if (parametersByFileName.isEmpty()) { + return; + } + Path parametersDir = Files.createDirectories(tempDir.resolve(PARAMETERS_FOLDER)); + for (Map.Entry entry : parametersByFileName.entrySet()) { + Files.writeString(parametersDir.resolve(entry.getKey()), entry.getValue()); + } + } + private Path createTempWorkDir(UUID studyUuid) { return createTempPath(studyUuid, "temp directory", "rwx------", attr -> Files.createTempDirectory("study-export-" + studyUuid, attr)); 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 8a3c31bd29..4210cd49f4 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -3041,6 +3041,11 @@ public TreeExportInfos buildTreeExport(UUID studyUuid) { return new TreeExportInfos(studyUuid, rootNetworks, nodeTree); } + public Map exportComputationParameters(UUID studyUuid, String userId) { + StudyEntity studyEntity = getStudy(studyUuid); + return computationParametersService.exportParameters(studyEntity, userId); + } + private RootNetworkExportInfos toRootNetworkExportInfos(RootNetworkInfos rootNetworkInfos, int index) { return new RootNetworkExportInfos( rootNetworkInfos.getName(), diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index 737d758a87..af097fb9ce 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -6,9 +6,12 @@ */ package org.gridsuite.study.server.service.common; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.gridsuite.study.server.dto.ComputationType; import org.gridsuite.study.server.dto.UserProfileInfos; import org.gridsuite.study.server.dto.computation.ComputationParameterUUIDs; +import org.gridsuite.study.server.error.StudyException; import org.gridsuite.study.server.repository.StudyEntity; import org.gridsuite.study.server.service.*; import org.gridsuite.study.server.service.dynamicmargincalculation.DynamicMarginCalculationRestService; @@ -25,11 +28,17 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.UUID; import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Function; +import static org.gridsuite.study.server.error.StudyBusinessErrorCode.EXPORT_STUDY_ERROR; + /** * @author Abdelsalem HEDHILI */ @@ -40,15 +49,17 @@ public class ComputationParametersService { private static final Logger LOGGER = LoggerFactory.getLogger(ComputationParametersService.class); private final UserAdminService userAdminService; + private final ObjectMapper objectMapper; private final List computationParametersDefinitions; - // this is useful to avoid repetitive calls when doing operation on all computation types (duplicate, delete) + // this is useful to avoid repetitive calls when doing operation on all computation types (duplicate, delete, export) private record ComputationParametersDefinition( ComputationType type, Function studyParameterGetter, Function profileParameterGetter, ComputationParameters service, - BiConsumer parametersSetter + BiConsumer parametersSetter, + BiFunction parametersFetcher ) { } @@ -62,70 +73,82 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis DynamicMarginCalculationRestService dynamicMarginCalculationRestService, StateEstimationRestService stateEstimationService, PccMinRestService pccMinService, - UserAdminService userAdminService) { + UserAdminService userAdminService, + ObjectMapper objectMapper) { this.userAdminService = userAdminService; + this.objectMapper = objectMapper; this.computationParametersDefinitions = List.of( new ComputationParametersDefinition( ComputationType.LOAD_FLOW, StudyEntity::getLoadFlowParametersUuid, UserProfileInfos::getLoadFlowParameterId, loadFlowRestService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::loadFlowParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::loadFlowParametersUuid, + (uuid, userId) -> loadFlowRestService.getParameters(uuid)), new ComputationParametersDefinition( ComputationType.SHORT_CIRCUIT, StudyEntity::getShortCircuitParametersUuid, UserProfileInfos::getShortcircuitParameterId, shortCircuitService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::shortCircuitParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::shortCircuitParametersUuid, + (uuid, userId) -> shortCircuitService.getParameters(uuid)), new ComputationParametersDefinition( ComputationType.DYNAMIC_SIMULATION, StudyEntity::getDynamicSimulationParametersUuid, UserProfileInfos::getDynamicSimulationParameterId, dynamicSimulationRestService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicSimulationParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicSimulationParametersUuid, + (uuid, userId) -> dynamicSimulationRestService.getParameters(uuid)), new ComputationParametersDefinition( ComputationType.VOLTAGE_INITIALIZATION, StudyEntity::getVoltageInitParametersUuid, UserProfileInfos::getVoltageInitParameterId, voltageInitService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::voltageInitParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::voltageInitParametersUuid, + (uuid, userId) -> voltageInitService.getParameters(uuid)), new ComputationParametersDefinition( ComputationType.SECURITY_ANALYSIS, StudyEntity::getSecurityAnalysisParametersUuid, UserProfileInfos::getSecurityAnalysisParameterId, securityAnalysisService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::securityAnalysisParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::securityAnalysisParametersUuid, + (uuid, userId) -> securityAnalysisService.getParameters(uuid)), new ComputationParametersDefinition( ComputationType.SENSITIVITY_ANALYSIS, StudyEntity::getSensitivityAnalysisParametersUuid, UserProfileInfos::getSensitivityAnalysisParameterId, sensitivityAnalysisService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::sensitivityAnalysisParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::sensitivityAnalysisParametersUuid, + (uuid, userId) -> sensitivityAnalysisService.getParameters(uuid)), new ComputationParametersDefinition( ComputationType.DYNAMIC_SECURITY_ANALYSIS, StudyEntity::getDynamicSecurityAnalysisParametersUuid, UserProfileInfos::getDynamicSecurityAnalysisParameterId, dynamicSecurityAnalysisRestService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicSecurityAnalysisParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicSecurityAnalysisParametersUuid, + (uuid, userId) -> dynamicSecurityAnalysisRestService.getParameters(uuid)), new ComputationParametersDefinition( ComputationType.DYNAMIC_MARGIN_CALCULATION, StudyEntity::getDynamicMarginCalculationParametersUuid, UserProfileInfos::getDynamicMarginCalculationParameterId, dynamicMarginCalculationRestService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicMarginCalculationParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicMarginCalculationParametersUuid, + dynamicMarginCalculationRestService::getParameters), new ComputationParametersDefinition( ComputationType.STATE_ESTIMATION, StudyEntity::getStateEstimationParametersUuid, userProfileInfos -> null, stateEstimationService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::stateEstimationParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::stateEstimationParametersUuid, + (uuid, userId) -> stateEstimationService.getStateEstimationParameters(uuid)), new ComputationParametersDefinition( ComputationType.PCC_MIN, StudyEntity::getPccMinParametersUuid, UserProfileInfos::getPccMinParameterId, pccMinService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::pccMinParametersUuid) + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::pccMinParametersUuid, + (uuid, userId) -> pccMinService.getParameters(uuid)) ); } @@ -220,4 +243,34 @@ public void createOrUpdateParameters( } } + public Map exportParameters(StudyEntity studyEntity, String userId) { + Map parametersByFileName = new LinkedHashMap<>(); + computationParametersDefinitions.forEach(definition -> { + UUID parametersUuid = definition.studyParameterGetter().apply(studyEntity); + if (parametersUuid == null) { + return; + } + try { + Object parameters = definition.parametersFetcher().apply(parametersUuid, userId); + String fileName = toFileName(definition.type()); + parametersByFileName.put(fileName, parameters instanceof String json ? json : writeAsJson(fileName, parameters)); + } catch (Exception e) { + LOGGER.warn("Failed to fetch {} parameters (uuid={}) for study export", definition.type().getLabel(), parametersUuid, e); + } + }); + return parametersByFileName; + } + + private static String toFileName(ComputationType type) { + return type.name().toLowerCase(Locale.ROOT).replace('_', '-') + ".json"; + } + + private String writeAsJson(String fileName, Object parameters) { + try { + return objectMapper.writeValueAsString(parameters); + } catch (JsonProcessingException e) { + throw new StudyException(EXPORT_STUDY_ERROR, "Failed to serialize computation parameters " + fileName); + } + } + } diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java index b7e70195c4..2785c8f5c5 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java @@ -63,6 +63,8 @@ void testExportStudy() throws Exception { .willReturn(WireMock.aResponse().withStatus(200) .withHeader("Content-Type", "application/octet-stream") .withBody("dummy case content".getBytes()))); + // Stub the computation parameters fetches + computationServerStubs.stubGetParametersAny("{}"); // Export as zip MvcResult result = mockMvc.perform(get("/v1/studies/{studyUuid}/export/{studyName}", studyUuid, "studyName").header(HEADER_USER_ID, "testUser")) .andExpect(status().isOk()) @@ -103,6 +105,8 @@ void testExportStudy() throws Exception { // Verify the case content download call WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); + // Verify the computation parameters fetches + computationServerStubs.verifyParametersGetAny(10); } @Test @@ -125,6 +129,7 @@ void testExportStudyFailToDeleteTempZipFile() throws Exception { wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo("/v1/cases/" + CASE_UUID)) .willReturn(WireMock.aResponse().withStatus(200).withHeader("Content-Type", "application/octet-stream") .withBody("dummy case content".getBytes()))); + computationServerStubs.stubGetParametersAny("{}"); // Capture the real zip file path as it is matched, so the test can clean it up itself: // the service's own Files.deleteIfExists call on this path is mocked to fail below. AtomicReference capturedZipFile = new AtomicReference<>(); @@ -156,5 +161,6 @@ void testExportStudyFailToDeleteTempZipFile() throws Exception { } WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); + computationServerStubs.verifyParametersGetAny(10); } } diff --git a/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java b/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java index 8588636775..a9fb47d85a 100644 --- a/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java +++ b/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java @@ -154,6 +154,15 @@ public void verifyParametersGet(String paramUuid) { WireMockUtilsCriteria.verifyGetRequest(wireMock, "/v1/parameters/" + paramUuid, Map.of()); } + public void stubGetParametersAny(String responseBody) { + wireMock.stubFor(WireMock.get(WireMock.urlPathMatching("/v1/parameters/.*")) + .willReturn(WireMock.ok().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).withBody(responseBody))); + } + + public void verifyParametersGetAny(int nbRequests) { + WireMockUtilsCriteria.verifyGetRequest(wireMock, "/v1/parameters/.*", true, Map.of(), nbRequests); + } + public void stubCreateParameter(String responseBody) { wireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/v1/parameters")) .atPriority(10) From d20e5fc1604be9db793b29f8861631ca586263bf Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 15 Sep 2026 15:43:04 +0200 Subject: [PATCH 02/18] clean code --- .../server/service/StudyExportService.java | 20 ++++----- .../study/server/service/StudyService.java | 7 +-- .../common/ComputationParametersService.java | 43 ++++++------------- 3 files changed, 21 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index 4c174a2758..b605804eec 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -11,6 +11,7 @@ import org.gridsuite.study.server.dto.studyexport.RootNetworkExportInfos; import org.gridsuite.study.server.dto.studyexport.TreeExportInfos; import org.gridsuite.study.server.error.StudyException; +import org.gridsuite.study.server.service.common.ComputationParametersService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.InputStreamResource; @@ -27,7 +28,6 @@ import java.nio.file.attribute.PosixFilePermissions; import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.stream.Stream; @@ -45,18 +45,21 @@ public class StudyExportService { private static final Logger LOGGER = LoggerFactory.getLogger(StudyExportService.class); public static final String TREE_JSON_FILE_NAME = "tree.json"; public static final String CASES_FOLDER = "cases"; - public static final String PARAMETERS_FOLDER = "parameters"; + public static final String PARAMETERS_FOLDER = "computationParameters"; private final StudyService studyService; private final CaseService caseService; private final DirectoryService directoryService; private final ObjectMapper objectMapper; + private final ComputationParametersService computationParametersService; - public StudyExportService(StudyService studyService, CaseService caseService, DirectoryService directoryService, ObjectMapper objectMapper) { + public StudyExportService(StudyService studyService, CaseService caseService, DirectoryService directoryService, + ObjectMapper objectMapper, ComputationParametersService computationParametersService) { this.studyService = studyService; this.caseService = caseService; this.directoryService = directoryService; this.objectMapper = objectMapper; + this.computationParametersService = computationParametersService; } /** @@ -114,15 +117,8 @@ private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) thr return zipFile; } - private void exportComputationParameters(UUID studyUuid, String userId, Path tempDir) throws IOException { - Map parametersByFileName = studyService.exportComputationParameters(studyUuid, userId); - if (parametersByFileName.isEmpty()) { - return; - } - Path parametersDir = Files.createDirectories(tempDir.resolve(PARAMETERS_FOLDER)); - for (Map.Entry entry : parametersByFileName.entrySet()) { - Files.writeString(parametersDir.resolve(entry.getKey()), entry.getValue()); - } + private void exportComputationParameters(UUID studyUuid, String userId, Path tempDir) { + computationParametersService.exportParameters(studyService.getStudy(studyUuid), userId, tempDir.resolve(PARAMETERS_FOLDER)); } private Path createTempWorkDir(UUID studyUuid) { 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 21e900f04e..795d739967 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -1782,7 +1782,7 @@ public void reindexRootNetwork(UUID studyUuid, UUID rootNetworkUuid) { reindexRootNetwork(getStudy(studyUuid), rootNetworkUuid); } - private StudyEntity getStudy(UUID studyUuid) { + StudyEntity getStudy(UUID studyUuid) { return studyRepository.findById(studyUuid).orElseThrow(() -> new StudyException(NOT_FOUND, STUDY_NOT_FOUND)); } @@ -2872,11 +2872,6 @@ public TreeExportInfos buildTreeExport(UUID studyUuid) { return new TreeExportInfos(studyUuid, rootNetworks, nodeTree); } - public Map exportComputationParameters(UUID studyUuid, String userId) { - StudyEntity studyEntity = getStudy(studyUuid); - return computationParametersService.exportParameters(studyEntity, userId); - } - private RootNetworkExportInfos toRootNetworkExportInfos(RootNetworkInfos rootNetworkInfos, int index) { return new RootNetworkExportInfos( rootNetworkInfos.getName(), diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index af097fb9ce..dd2802dd7e 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -6,14 +6,12 @@ */ package org.gridsuite.study.server.service.common; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.gridsuite.study.server.dto.ComputationType; import org.gridsuite.study.server.dto.UserProfileInfos; import org.gridsuite.study.server.dto.computation.ComputationParameterUUIDs; -import org.gridsuite.study.server.error.StudyException; import org.gridsuite.study.server.repository.StudyEntity; -import org.gridsuite.study.server.service.*; +import org.gridsuite.study.server.service.UserAdminService; import org.gridsuite.study.server.service.dynamicmargincalculation.DynamicMarginCalculationRestService; import org.gridsuite.study.server.service.dynamicsecurityanalysis.DynamicSecurityAnalysisRestService; import org.gridsuite.study.server.service.dynamicsimulation.DynamicSimulationRestService; @@ -28,17 +26,15 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import java.util.LinkedHashMap; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; -import java.util.Locale; -import java.util.Map; import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; -import static org.gridsuite.study.server.error.StudyBusinessErrorCode.EXPORT_STUDY_ERROR; - /** * @author Abdelsalem HEDHILI */ @@ -243,34 +239,19 @@ public void createOrUpdateParameters( } } - public Map exportParameters(StudyEntity studyEntity, String userId) { - Map parametersByFileName = new LinkedHashMap<>(); - computationParametersDefinitions.forEach(definition -> { + public void exportParameters(StudyEntity studyEntity, String userId, Path parametersDir) { + for (ComputationParametersDefinition definition : computationParametersDefinitions) { UUID parametersUuid = definition.studyParameterGetter().apply(studyEntity); if (parametersUuid == null) { - return; + continue; } + Object parameters = definition.parametersFetcher().apply(parametersUuid, userId); try { - Object parameters = definition.parametersFetcher().apply(parametersUuid, userId); - String fileName = toFileName(definition.type()); - parametersByFileName.put(fileName, parameters instanceof String json ? json : writeAsJson(fileName, parameters)); - } catch (Exception e) { - LOGGER.warn("Failed to fetch {} parameters (uuid={}) for study export", definition.type().getLabel(), parametersUuid, e); + Files.createDirectories(parametersDir); + Files.writeString(parametersDir.resolve(definition.type().name() + ".json"), objectMapper.writeValueAsString(parameters)); + } catch (IOException e) { + LOGGER.error(e.toString()); } - }); - return parametersByFileName; - } - - private static String toFileName(ComputationType type) { - return type.name().toLowerCase(Locale.ROOT).replace('_', '-') + ".json"; - } - - private String writeAsJson(String fileName, Object parameters) { - try { - return objectMapper.writeValueAsString(parameters); - } catch (JsonProcessingException e) { - throw new StudyException(EXPORT_STUDY_ERROR, "Failed to serialize computation parameters " + fileName); } } - } From 3c0e3dbee72c1e773b4b40128f91ae0f58c8cada Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Fri, 18 Sep 2026 16:44:26 +0200 Subject: [PATCH 03/18] add export filtersUuids and contingenciesUuids --- .../ComputationParametersExportInfos.java | 17 ++++++++ .../study/server/service/ActionsService.java | 18 ++++++++- .../server/service/StudyExportService.java | 40 +++++++++++++++++-- .../study/server/service/StudyService.java | 6 +++ .../common/ComputationParametersService.java | 30 ++++++++++---- .../SecurityAnalysisRestService.java | 6 +++ .../SensitivityAnalysisRestService.java | 10 +++++ 7 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/gridsuite/study/server/dto/studyexport/ComputationParametersExportInfos.java diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/ComputationParametersExportInfos.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/ComputationParametersExportInfos.java new file mode 100644 index 0000000000..514d118f62 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/ComputationParametersExportInfos.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.study.server.dto.studyexport; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * @author Ghazwa Rehili + */ +public record ComputationParametersExportInfos(Map parametersByFileName, Set filterUuids, Set contingencyListUuids) { +} diff --git a/src/main/java/org/gridsuite/study/server/service/ActionsService.java b/src/main/java/org/gridsuite/study/server/service/ActionsService.java index f863d1ee26..cc99034aea 100644 --- a/src/main/java/org/gridsuite/study/server/service/ActionsService.java +++ b/src/main/java/org/gridsuite/study/server/service/ActionsService.java @@ -11,6 +11,7 @@ * @author Kevin Le Saulnier */ +import lombok.Setter; import org.apache.commons.lang3.StringUtils; import org.gridsuite.study.server.RemoteServicesProperties; import org.gridsuite.study.server.dto.ContingencyCount; @@ -32,9 +33,11 @@ public class ActionsService { private static final String NETWORK_UUID = "networkUuid"; private static final String CONTINGENCY_LIST_IDS = "ids"; + private static final String CONTINGENCY_LIST_EXPORT_IDS = "contingencyListIds"; public static final ContingencyCount EMPTY_CONTINGENCY_COUNT = new ContingencyCount(Map.of()); + @Setter private String actionsServerBaseUri; public ActionsService(RemoteServicesProperties remoteServicesProperties, RestTemplate restTemplate) { @@ -58,7 +61,18 @@ public ContingencyCount getContingencyCount(UUID networkUuid, String variantId, ).getBody(); } - public void setActionsServerBaseUri(String actionsServerBaseUri) { - this.actionsServerBaseUri = actionsServerBaseUri; + public String exportContingencyLists(UUID networkUuid, String variantId, List contingencyListIds) { + var uriComponentsBuilder = UriComponentsBuilder + .fromPath(DELIMITER + ACTIONS_API_VERSION + "/contingency-lists/export") + .queryParam(CONTINGENCY_LIST_EXPORT_IDS, contingencyListIds) + .queryParam(NETWORK_UUID, networkUuid); + if (!StringUtils.isBlank(variantId)) { + uriComponentsBuilder.queryParam(QUERY_PARAM_VARIANT_ID, variantId); + } + return restTemplate.getForObject( + actionsServerBaseUri + uriComponentsBuilder.toUriString(), + String.class + ); } + } diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index b605804eec..97ecb3dcac 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -11,6 +11,7 @@ import org.gridsuite.study.server.dto.studyexport.RootNetworkExportInfos; import org.gridsuite.study.server.dto.studyexport.TreeExportInfos; import org.gridsuite.study.server.error.StudyException; +import org.gridsuite.study.server.repository.StudyEntity; import org.gridsuite.study.server.service.common.ComputationParametersService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,8 +29,10 @@ import java.nio.file.attribute.PosixFilePermissions; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Supplier; import java.util.stream.Stream; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; @@ -52,14 +55,19 @@ public class StudyExportService { private final DirectoryService directoryService; private final ObjectMapper objectMapper; private final ComputationParametersService computationParametersService; + private final NetworkModificationTreeService networkModificationTreeService; + private final RootNetworkService rootNetworkService; public StudyExportService(StudyService studyService, CaseService caseService, DirectoryService directoryService, - ObjectMapper objectMapper, ComputationParametersService computationParametersService) { + ObjectMapper objectMapper, ComputationParametersService computationParametersService, + NetworkModificationTreeService networkModificationTreeService, RootNetworkService rootNetworkService) { this.studyService = studyService; this.caseService = caseService; this.directoryService = directoryService; this.objectMapper = objectMapper; this.computationParametersService = computationParametersService; + this.networkModificationTreeService = networkModificationTreeService; + this.rootNetworkService = rootNetworkService; } /** @@ -117,8 +125,34 @@ private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) thr return zipFile; } - private void exportComputationParameters(UUID studyUuid, String userId, Path tempDir) { - computationParametersService.exportParameters(studyService.getStudy(studyUuid), userId, tempDir.resolve(PARAMETERS_FOLDER)); + private void exportComputationParameters(UUID studyUuid, String userId, Path tempDir) throws IOException { + StudyEntity study = studyService.getStudy(studyUuid); + var export = computationParametersService.exportParameters(study, userId); + Path parametersDir = tempDir.resolve(PARAMETERS_FOLDER); + + if (!export.parametersByFileName().isEmpty()) { + Files.createDirectories(parametersDir); + for (Map.Entry entry : export.parametersByFileName().entrySet()) { + Files.writeString(parametersDir.resolve(entry.getKey()), entry.getValue()); + } + } + + Set filteredUuids = export.filterUuids(); + Set contingencyListUuids = export.contingencyListUuids(); + UUID rootNetworkUuid = rootNetworkService.getFirstRootNetworkUuid(studyUuid); + UUID rootNodeUuid = networkModificationTreeService.getStudyRootNodeUuid(studyUuid); + writeIfNotEmpty(parametersDir, "filters.json", filteredUuids, + () -> studyService.exportFilters(rootNetworkUuid, List.copyOf(filteredUuids), rootNodeUuid, false)); + writeIfNotEmpty(parametersDir, "contingencyLists.json", contingencyListUuids, + () -> studyService.exportContingencyLists(rootNetworkUuid, List.copyOf(contingencyListUuids), rootNodeUuid, false)); + } + + private void writeIfNotEmpty(Path dir, String fileName, Set ids, Supplier contentSupplier) throws IOException { + if (ids.isEmpty()) { + return; + } + Files.createDirectories(dir); + Files.writeString(dir.resolve(fileName), contentSupplier.get()); } private Path createTempWorkDir(UUID studyUuid) { 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 0ad9b3edec..30edde5f44 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -2571,6 +2571,12 @@ public String exportFilters(UUID rootNetworkUuid, List filtersUuid, UUID n return filterService.exportFilters(rootNetworkService.getNetworkUuid(rootNetworkUuid), filtersUuid, variantId); } + public String exportContingencyLists(UUID rootNetworkUuid, List contingencyListUuids, UUID nodeUuid, boolean inUpstreamBuiltParentNode) { + UUID nodeUuidToSearchIn = getNodeUuidToSearchIn(nodeUuid, rootNetworkUuid, inUpstreamBuiltParentNode); + String variantId = networkModificationTreeService.getVariantId(nodeUuidToSearchIn, rootNetworkUuid); + return actionsService.exportContingencyLists(rootNetworkService.getNetworkUuid(rootNetworkUuid), variantId, contingencyListUuids); + } + @Transactional public NetworkModificationNode createNode(UUID studyUuid, UUID nodeId, NetworkModificationNode nodeInfo, InsertMode insertMode, String userId) { StudyEntity study = getStudy(studyUuid); diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index dd2802dd7e..330b625de3 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -6,10 +6,12 @@ */ package org.gridsuite.study.server.service.common; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.gridsuite.study.server.dto.ComputationType; import org.gridsuite.study.server.dto.UserProfileInfos; import org.gridsuite.study.server.dto.computation.ComputationParameterUUIDs; +import org.gridsuite.study.server.dto.studyexport.ComputationParametersExportInfos; import org.gridsuite.study.server.repository.StudyEntity; import org.gridsuite.study.server.service.UserAdminService; import org.gridsuite.study.server.service.dynamicmargincalculation.DynamicMarginCalculationRestService; @@ -26,10 +28,11 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -46,6 +49,8 @@ public class ComputationParametersService { private final UserAdminService userAdminService; private final ObjectMapper objectMapper; + private final SecurityAnalysisRestService securityAnalysisService; + private final SensitivityAnalysisRestService sensitivityAnalysisService; private final List computationParametersDefinitions; // this is useful to avoid repetitive calls when doing operation on all computation types (duplicate, delete, export) @@ -74,6 +79,8 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis this.userAdminService = userAdminService; this.objectMapper = objectMapper; + this.securityAnalysisService = securityAnalysisService; + this.sensitivityAnalysisService = sensitivityAnalysisService; this.computationParametersDefinitions = List.of( new ComputationParametersDefinition( ComputationType.LOAD_FLOW, @@ -239,7 +246,10 @@ public void createOrUpdateParameters( } } - public void exportParameters(StudyEntity studyEntity, String userId, Path parametersDir) { + public ComputationParametersExportInfos exportParameters(StudyEntity studyEntity, String userId) { + Map parametersByFileName = new HashMap<>(); + Set filterUuids = new HashSet<>(); + Set contingencyListUuids = new HashSet<>(); for (ComputationParametersDefinition definition : computationParametersDefinitions) { UUID parametersUuid = definition.studyParameterGetter().apply(studyEntity); if (parametersUuid == null) { @@ -247,11 +257,17 @@ public void exportParameters(StudyEntity studyEntity, String userId, Path parame } Object parameters = definition.parametersFetcher().apply(parametersUuid, userId); try { - Files.createDirectories(parametersDir); - Files.writeString(parametersDir.resolve(definition.type().name() + ".json"), objectMapper.writeValueAsString(parameters)); - } catch (IOException e) { + parametersByFileName.put(definition.type().name() + ".json", objectMapper.writeValueAsString(parameters)); + } catch (JsonProcessingException e) { LOGGER.error(e.toString()); } + if (definition.type() == ComputationType.SECURITY_ANALYSIS) { + contingencyListUuids.addAll(securityAnalysisService.getContingencyListUuids(parametersUuid)); + } else if (definition.type() == ComputationType.SENSITIVITY_ANALYSIS) { + filterUuids.addAll(sensitivityAnalysisService.getFilterUuids(parametersUuid)); + contingencyListUuids.addAll(sensitivityAnalysisService.getContingencyListUuids(parametersUuid)); + } } + return new ComputationParametersExportInfos(parametersByFileName, filterUuids, contingencyListUuids); } } diff --git a/src/main/java/org/gridsuite/study/server/service/securityanalysis/SecurityAnalysisRestService.java b/src/main/java/org/gridsuite/study/server/service/securityanalysis/SecurityAnalysisRestService.java index 54a143c7d9..fb0fe0c8e3 100644 --- a/src/main/java/org/gridsuite/study/server/service/securityanalysis/SecurityAnalysisRestService.java +++ b/src/main/java/org/gridsuite/study/server/service/securityanalysis/SecurityAnalysisRestService.java @@ -19,6 +19,7 @@ import org.gridsuite.study.server.service.common.AbstractComputationRestService; import org.gridsuite.study.server.service.common.ComputationParameters; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.http.*; @@ -307,6 +308,11 @@ public String getParameters(UUID parameterUuid) { return restTemplate.getForObject(getBaseUri() + path, String.class); } + public List getContingencyListUuids(UUID parameterUuid) { + String path = UriComponentsBuilder.fromPath(DELIMITER + SECURITY_ANALYSIS_API_VERSION + "/parameters/{parameterUuid}/contingency-list-uuids").buildAndExpand(parameterUuid).toUriString(); + return restTemplate.exchange(getBaseUri() + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); + } + public void updateParameters(UUID parameterUuid, @Nullable String parameters) { String path = UriComponentsBuilder.fromPath(DELIMITER + SECURITY_ANALYSIS_API_VERSION + "/parameters/{parameterUuid}").buildAndExpand(parameterUuid).toUriString(); HttpHeaders headers = new HttpHeaders(); diff --git a/src/main/java/org/gridsuite/study/server/service/sensitivityanalysis/SensitivityAnalysisRestService.java b/src/main/java/org/gridsuite/study/server/service/sensitivityanalysis/SensitivityAnalysisRestService.java index a2e7356c2d..d4a9836ba7 100644 --- a/src/main/java/org/gridsuite/study/server/service/sensitivityanalysis/SensitivityAnalysisRestService.java +++ b/src/main/java/org/gridsuite/study/server/service/sensitivityanalysis/SensitivityAnalysisRestService.java @@ -361,6 +361,16 @@ public String getParameters(UUID parameterUuid) { return getRestTemplate().getForObject(getBaseUri() + path, String.class); } + public List getFilterUuids(UUID parameterUuid) { + String path = UriComponentsBuilder.fromPath(DELIMITER + SENSITIVITY_ANALYSIS_API_VERSION + "/parameters/{parameterUuid}/filter-uuids").buildAndExpand(parameterUuid).toUriString(); + return getRestTemplate().exchange(getBaseUri() + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); + } + + public List getContingencyListUuids(UUID parameterUuid) { + String path = UriComponentsBuilder.fromPath(DELIMITER + SENSITIVITY_ANALYSIS_API_VERSION + "/parameters/{parameterUuid}/contingency-list-uuids").buildAndExpand(parameterUuid).toUriString(); + return getRestTemplate().exchange(getBaseUri() + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); + } + public void updateParameters(UUID parameterUuid, @Nullable String parameters) { String path = UriComponentsBuilder.fromPath(DELIMITER + SENSITIVITY_ANALYSIS_API_VERSION + "/parameters/{parameterUuid}").buildAndExpand(parameterUuid).toUriString(); HttpHeaders headers = new HttpHeaders(); From fa4a4af7977117f27b91dc7a1af3e13dfc00eb63 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 22 Sep 2026 08:42:16 +0200 Subject: [PATCH 04/18] handle referenced filters uuids --- .../ComputationParametersExportInfos.java | 17 ---- .../study/server/service/ActionsService.java | 24 +++-- .../study/server/service/FilterService.java | 13 +++ .../server/service/StudyExportService.java | 85 ++++++++++------ .../study/server/service/StudyService.java | 6 -- .../common/ComputationParametersService.java | 43 +++++---- .../service/pccmin/PccMinRestService.java | 6 ++ .../voltageinit/VoltageInitRestService.java | 6 ++ .../studycontroller/TreeExportTest.java | 96 +++++++++++++++++++ .../wiremock/ComputationServerStubs.java | 13 ++- 10 files changed, 220 insertions(+), 89 deletions(-) delete mode 100644 src/main/java/org/gridsuite/study/server/dto/studyexport/ComputationParametersExportInfos.java diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/ComputationParametersExportInfos.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/ComputationParametersExportInfos.java deleted file mode 100644 index 514d118f62..0000000000 --- a/src/main/java/org/gridsuite/study/server/dto/studyexport/ComputationParametersExportInfos.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2026, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.gridsuite.study.server.dto.studyexport; - -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -/** - * @author Ghazwa Rehili - */ -public record ComputationParametersExportInfos(Map parametersByFileName, Set filterUuids, Set contingencyListUuids) { -} diff --git a/src/main/java/org/gridsuite/study/server/service/ActionsService.java b/src/main/java/org/gridsuite/study/server/service/ActionsService.java index cc99034aea..7dc16d0d47 100644 --- a/src/main/java/org/gridsuite/study/server/service/ActionsService.java +++ b/src/main/java/org/gridsuite/study/server/service/ActionsService.java @@ -15,6 +15,8 @@ import org.apache.commons.lang3.StringUtils; import org.gridsuite.study.server.RemoteServicesProperties; import org.gridsuite.study.server.dto.ContingencyCount; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @@ -28,12 +30,12 @@ @Service public class ActionsService { + private static final Logger LOGGER = LoggerFactory.getLogger(ActionsService.class); private final RestTemplate restTemplate; private static final String NETWORK_UUID = "networkUuid"; private static final String CONTINGENCY_LIST_IDS = "ids"; - private static final String CONTINGENCY_LIST_EXPORT_IDS = "contingencyListIds"; public static final ContingencyCount EMPTY_CONTINGENCY_COUNT = new ContingencyCount(Map.of()); @@ -61,18 +63,14 @@ public ContingencyCount getContingencyCount(UUID networkUuid, String variantId, ).getBody(); } - public String exportContingencyLists(UUID networkUuid, String variantId, List contingencyListIds) { - var uriComponentsBuilder = UriComponentsBuilder - .fromPath(DELIMITER + ACTIONS_API_VERSION + "/contingency-lists/export") - .queryParam(CONTINGENCY_LIST_EXPORT_IDS, contingencyListIds) - .queryParam(NETWORK_UUID, networkUuid); - if (!StringUtils.isBlank(variantId)) { - uriComponentsBuilder.queryParam(QUERY_PARAM_VARIANT_ID, variantId); + public String getContingencyList(UUID id) { + String metadataPath = DELIMITER + ACTIONS_API_VERSION + "/contingency-lists/metadata?ids={id}"; + Map[] metadata = restTemplate.getForObject(actionsServerBaseUri + metadataPath, Map[].class, id); + if (metadata == null || metadata.length == 0) { + LOGGER.warn("Contingency list {} is referenced but does not exist anymore: it is not exported", id); + return null; } - return restTemplate.getForObject( - actionsServerBaseUri + uriComponentsBuilder.toUriString(), - String.class - ); + String endpoint = "IDENTIFIERS".equals(metadata[0].get("type")) ? "identifier-contingency-lists" : "filters-contingency-lists"; + return restTemplate.getForObject(actionsServerBaseUri + DELIMITER + ACTIONS_API_VERSION + "/" + endpoint + "/{id}", String.class, id); } - } diff --git a/src/main/java/org/gridsuite/study/server/service/FilterService.java b/src/main/java/org/gridsuite/study/server/service/FilterService.java index f39c6245b3..6ee7798d84 100644 --- a/src/main/java/org/gridsuite/study/server/service/FilterService.java +++ b/src/main/java/org/gridsuite/study/server/service/FilterService.java @@ -14,10 +14,13 @@ import org.gridsuite.filter.globalfilter.GlobalFilter; import org.gridsuite.filter.utils.EquipmentType; import org.gridsuite.study.server.RemoteServicesProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.*; import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -38,6 +41,7 @@ */ @Service public class FilterService { + private static final Logger LOGGER = LoggerFactory.getLogger(FilterService.class); public static final String FILTER_END_POINT_EVALUATE = "/filters/evaluate"; public static final String FILTER_END_POINT_EVALUATE_IDS = "/filters/evaluate/identifiables"; @@ -135,4 +139,13 @@ public String evaluateFilters(UUID networkUuid, String filters) { return restTemplate.postForObject(uriComponent.toUriString(), request, String.class); } + + public String getFilter(UUID filterUuid) { + try { + return restTemplate.getForObject(getBaseUri() + DELIMITER + FILTER_API_VERSION + "/filters/{id}", String.class, filterUuid); + } catch (HttpClientErrorException.NotFound e) { + LOGGER.warn("Filter {} is referenced but does not exist anymore: it is not exported", filterUuid); + return null; + } + } } diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index 97ecb3dcac..e4ad210510 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -6,12 +6,12 @@ */ package org.gridsuite.study.server.service; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.gridsuite.study.server.dto.networkexport.PermissionType; import org.gridsuite.study.server.dto.studyexport.RootNetworkExportInfos; import org.gridsuite.study.server.dto.studyexport.TreeExportInfos; import org.gridsuite.study.server.error.StudyException; -import org.gridsuite.study.server.repository.StudyEntity; import org.gridsuite.study.server.service.common.ComputationParametersService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,12 +27,16 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; +import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Comparator; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.function.Supplier; import java.util.stream.Stream; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; @@ -55,19 +59,19 @@ public class StudyExportService { private final DirectoryService directoryService; private final ObjectMapper objectMapper; private final ComputationParametersService computationParametersService; - private final NetworkModificationTreeService networkModificationTreeService; - private final RootNetworkService rootNetworkService; + private final FilterService filterService; + private final ActionsService actionsService; public StudyExportService(StudyService studyService, CaseService caseService, DirectoryService directoryService, ObjectMapper objectMapper, ComputationParametersService computationParametersService, - NetworkModificationTreeService networkModificationTreeService, RootNetworkService rootNetworkService) { + FilterService filterService, ActionsService actionsService) { this.studyService = studyService; this.caseService = caseService; this.directoryService = directoryService; this.objectMapper = objectMapper; this.computationParametersService = computationParametersService; - this.networkModificationTreeService = networkModificationTreeService; - this.rootNetworkService = rootNetworkService; + this.filterService = filterService; + this.actionsService = actionsService; } /** @@ -116,7 +120,11 @@ private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) thr String caseName = rootNetworkInfos.caseInfos().getCaseName(); exportCaseFile(caseUuid, caseName, casesDir); } - exportComputationParameters(studyUuid, userId, tempDir); + Path parametersDir = tempDir.resolve(PARAMETERS_FOLDER); + Set filterUuids = new HashSet<>(); + Set contingencyListUuids = new HashSet<>(); + computationParametersService.exportParameters(studyService.getStudy(studyUuid), userId, parametersDir, filterUuids, contingencyListUuids); + exportDefinitions(parametersDir, filterUuids, contingencyListUuids); Path zipFile = createTempExportFile(studyUuid); try (OutputStream fos = Files.newOutputStream(zipFile); ZipOutputStream zipOut = new ZipOutputStream(fos)) { @@ -125,34 +133,51 @@ private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) thr return zipFile; } - private void exportComputationParameters(UUID studyUuid, String userId, Path tempDir) throws IOException { - StudyEntity study = studyService.getStudy(studyUuid); - var export = computationParametersService.exportParameters(study, userId); - Path parametersDir = tempDir.resolve(PARAMETERS_FOLDER); + private void exportDefinitions(Path parametersDir, Set filterUuids, Set contingencyListUuids) throws IOException { + Map contingencyLists = new LinkedHashMap<>(); + for (UUID contingencyListUuid : contingencyListUuids) { + String content = actionsService.getContingencyList(contingencyListUuid); + if (content == null) { + continue; + } + JsonNode contentNode = objectMapper.readTree(content); + contingencyLists.put(contingencyListUuid, contentNode); + contentNode.path("filters").findValues("id").forEach(id -> filterUuids.add(UUID.fromString(id.asText()))); + contentNode.path("selectedEquipmentTypesByFilter").findValues("filterId").forEach(id -> filterUuids.add(UUID.fromString(id.asText()))); + } - if (!export.parametersByFileName().isEmpty()) { - Files.createDirectories(parametersDir); - for (Map.Entry entry : export.parametersByFileName().entrySet()) { - Files.writeString(parametersDir.resolve(entry.getKey()), entry.getValue()); + Map filters = new LinkedHashMap<>(); + Deque filtersToExport = new ArrayDeque<>(filterUuids); + while (!filtersToExport.isEmpty()) { + UUID filterUuid = filtersToExport.poll(); + if (filters.containsKey(filterUuid)) { + continue; + } + String content = filterService.getFilter(filterUuid); + if (content == null) { + continue; + } + JsonNode contentNode = objectMapper.readTree(content); + filters.put(filterUuid, contentNode); + for (JsonNode rule : contentNode.findParents("dataType")) { + if ("FILTER_UUID".equals(rule.get("dataType").asText())) { + rule.path("values").forEach(value -> filtersToExport.add(UUID.fromString(value.asText()))); + } } } - Set filteredUuids = export.filterUuids(); - Set contingencyListUuids = export.contingencyListUuids(); - UUID rootNetworkUuid = rootNetworkService.getFirstRootNetworkUuid(studyUuid); - UUID rootNodeUuid = networkModificationTreeService.getStudyRootNodeUuid(studyUuid); - writeIfNotEmpty(parametersDir, "filters.json", filteredUuids, - () -> studyService.exportFilters(rootNetworkUuid, List.copyOf(filteredUuids), rootNodeUuid, false)); - writeIfNotEmpty(parametersDir, "contingencyLists.json", contingencyListUuids, - () -> studyService.exportContingencyLists(rootNetworkUuid, List.copyOf(contingencyListUuids), rootNodeUuid, false)); + Set allUuids = new HashSet<>(filters.keySet()); + allUuids.addAll(contingencyLists.keySet()); + Map names = directoryService.getElementNames(allUuids); + + writeDefinitions(parametersDir.resolve("filterDefinitions.json"), filters, names); + writeDefinitions(parametersDir.resolve("contingencyListDefinitions.json"), contingencyLists, names); } - private void writeIfNotEmpty(Path dir, String fileName, Set ids, Supplier contentSupplier) throws IOException { - if (ids.isEmpty()) { - return; - } - Files.createDirectories(dir); - Files.writeString(dir.resolve(fileName), contentSupplier.get()); + private void writeDefinitions(Path file, Map contents, Map names) throws IOException { + List> definitions = new ArrayList<>(); + contents.forEach((uuid, content) -> definitions.add(Map.of("uuid", uuid, "name", names.getOrDefault(uuid, ""), "content", content))); + objectMapper.writeValue(file.toFile(), definitions); } private Path createTempWorkDir(UUID studyUuid) { 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 30edde5f44..0ad9b3edec 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -2571,12 +2571,6 @@ public String exportFilters(UUID rootNetworkUuid, List filtersUuid, UUID n return filterService.exportFilters(rootNetworkService.getNetworkUuid(rootNetworkUuid), filtersUuid, variantId); } - public String exportContingencyLists(UUID rootNetworkUuid, List contingencyListUuids, UUID nodeUuid, boolean inUpstreamBuiltParentNode) { - UUID nodeUuidToSearchIn = getNodeUuidToSearchIn(nodeUuid, rootNetworkUuid, inUpstreamBuiltParentNode); - String variantId = networkModificationTreeService.getVariantId(nodeUuidToSearchIn, rootNetworkUuid); - return actionsService.exportContingencyLists(rootNetworkService.getNetworkUuid(rootNetworkUuid), variantId, contingencyListUuids); - } - @Transactional public NetworkModificationNode createNode(UUID studyUuid, UUID nodeId, NetworkModificationNode nodeInfo, InsertMode insertMode, String userId) { StudyEntity study = getStudy(studyUuid); diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index 330b625de3..6ced4ecc5b 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -6,14 +6,12 @@ */ package org.gridsuite.study.server.service.common; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.gridsuite.study.server.dto.ComputationType; import org.gridsuite.study.server.dto.UserProfileInfos; import org.gridsuite.study.server.dto.computation.ComputationParameterUUIDs; -import org.gridsuite.study.server.dto.studyexport.ComputationParametersExportInfos; import org.gridsuite.study.server.repository.StudyEntity; -import org.gridsuite.study.server.service.UserAdminService; +import org.gridsuite.study.server.service.*; import org.gridsuite.study.server.service.dynamicmargincalculation.DynamicMarginCalculationRestService; import org.gridsuite.study.server.service.dynamicsecurityanalysis.DynamicSecurityAnalysisRestService; import org.gridsuite.study.server.service.dynamicsimulation.DynamicSimulationRestService; @@ -28,10 +26,10 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import java.util.HashMap; -import java.util.HashSet; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; @@ -51,6 +49,8 @@ public class ComputationParametersService { private final ObjectMapper objectMapper; private final SecurityAnalysisRestService securityAnalysisService; private final SensitivityAnalysisRestService sensitivityAnalysisService; + private final VoltageInitRestService voltageInitService; + private final PccMinRestService pccMinService; private final List computationParametersDefinitions; // this is useful to avoid repetitive calls when doing operation on all computation types (duplicate, delete, export) @@ -81,6 +81,8 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis this.objectMapper = objectMapper; this.securityAnalysisService = securityAnalysisService; this.sensitivityAnalysisService = sensitivityAnalysisService; + this.voltageInitService = voltageInitService; + this.pccMinService = pccMinService; this.computationParametersDefinitions = List.of( new ComputationParametersDefinition( ComputationType.LOAD_FLOW, @@ -246,28 +248,27 @@ public void createOrUpdateParameters( } } - public ComputationParametersExportInfos exportParameters(StudyEntity studyEntity, String userId) { - Map parametersByFileName = new HashMap<>(); - Set filterUuids = new HashSet<>(); - Set contingencyListUuids = new HashSet<>(); + public void exportParameters(StudyEntity studyEntity, String userId, Path parametersDir, Set filterUuids, Set contingencyListUuids) throws IOException { + Files.createDirectories(parametersDir); for (ComputationParametersDefinition definition : computationParametersDefinitions) { UUID parametersUuid = definition.studyParameterGetter().apply(studyEntity); if (parametersUuid == null) { continue; } Object parameters = definition.parametersFetcher().apply(parametersUuid, userId); - try { - parametersByFileName.put(definition.type().name() + ".json", objectMapper.writeValueAsString(parameters)); - } catch (JsonProcessingException e) { - LOGGER.error(e.toString()); - } - if (definition.type() == ComputationType.SECURITY_ANALYSIS) { - contingencyListUuids.addAll(securityAnalysisService.getContingencyListUuids(parametersUuid)); - } else if (definition.type() == ComputationType.SENSITIVITY_ANALYSIS) { - filterUuids.addAll(sensitivityAnalysisService.getFilterUuids(parametersUuid)); - contingencyListUuids.addAll(sensitivityAnalysisService.getContingencyListUuids(parametersUuid)); + String json = parameters instanceof String parametersJson ? parametersJson : objectMapper.writeValueAsString(parameters); + Files.writeString(parametersDir.resolve(definition.type().name() + ".json"), json); + switch (definition.type()) { + case SECURITY_ANALYSIS -> contingencyListUuids.addAll(securityAnalysisService.getContingencyListUuids(parametersUuid)); + case SENSITIVITY_ANALYSIS -> { + filterUuids.addAll(sensitivityAnalysisService.getFilterUuids(parametersUuid)); + contingencyListUuids.addAll(sensitivityAnalysisService.getContingencyListUuids(parametersUuid)); + } + case VOLTAGE_INITIALIZATION -> filterUuids.addAll(voltageInitService.getFilterUuids(parametersUuid)); + case PCC_MIN -> filterUuids.addAll(pccMinService.getFilterUuids(parametersUuid)); + default -> { + } } } - return new ComputationParametersExportInfos(parametersByFileName, filterUuids, contingencyListUuids); } } diff --git a/src/main/java/org/gridsuite/study/server/service/pccmin/PccMinRestService.java b/src/main/java/org/gridsuite/study/server/service/pccmin/PccMinRestService.java index e3617838c0..6179d0ad93 100644 --- a/src/main/java/org/gridsuite/study/server/service/pccmin/PccMinRestService.java +++ b/src/main/java/org/gridsuite/study/server/service/pccmin/PccMinRestService.java @@ -19,6 +19,7 @@ import org.gridsuite.study.server.utils.ResultParameters; import org.gridsuite.study.server.utils.StudyUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.http.*; @@ -287,4 +288,9 @@ public String getParameters(UUID parameterUuid) { String path = UriComponentsBuilder.fromPath(DELIMITER + PCC_MIN_API_VERSION + "/parameters/{parameterUuid}").buildAndExpand(parameterUuid).toUriString(); return restTemplate.getForObject(getBaseUri() + path, String.class); } + + public List getFilterUuids(UUID parameterUuid) { + String path = UriComponentsBuilder.fromPath(DELIMITER + PCC_MIN_API_VERSION + "/parameters/{parameterUuid}/filter-uuids").buildAndExpand(parameterUuid).toUriString(); + return restTemplate.exchange(baseUri + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); + } } diff --git a/src/main/java/org/gridsuite/study/server/service/voltageinit/VoltageInitRestService.java b/src/main/java/org/gridsuite/study/server/service/voltageinit/VoltageInitRestService.java index c479a40597..adfcca8882 100644 --- a/src/main/java/org/gridsuite/study/server/service/voltageinit/VoltageInitRestService.java +++ b/src/main/java/org/gridsuite/study/server/service/voltageinit/VoltageInitRestService.java @@ -19,6 +19,7 @@ import org.gridsuite.study.server.service.StudyService; import org.gridsuite.study.server.service.common.AbstractComputationRestService; import org.gridsuite.study.server.service.common.ComputationParameters; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.Resource; import org.springframework.http.*; import org.springframework.lang.Nullable; @@ -124,6 +125,11 @@ public VoltageInitParametersInfos getVoltageInitParameters(UUID parametersUuid) return restTemplate.getForObject(baseUri + path, VoltageInitParametersInfos.class); } + public List getFilterUuids(UUID parametersUuid) { + String path = UriComponentsBuilder.fromPath(DELIMITER + VOLTAGE_INIT_API_VERSION + PARAMETERS_URI + "/filter-uuids").buildAndExpand(parametersUuid).toUriString(); + return restTemplate.exchange(baseUri + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); + } + public UUID createVoltageInitParameters(@Nullable VoltageInitParametersInfos parameters) { var path = UriComponentsBuilder .fromPath(DELIMITER + VOLTAGE_INIT_API_VERSION + "/parameters") diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java index 2785c8f5c5..2bb19a8945 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java @@ -6,10 +6,13 @@ */ package org.gridsuite.study.server.studycontroller; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.tomakehurst.wiremock.client.WireMock; import org.gridsuite.study.server.dto.networkexport.PermissionType; import org.gridsuite.study.server.dto.studyexport.TreeExportInfos; +import org.gridsuite.study.server.service.ActionsService; +import org.gridsuite.study.server.service.FilterService; import org.gridsuite.study.server.utils.wiremock.WireMockUtilsCriteria; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatcher; @@ -25,8 +28,11 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.ZipEntry; @@ -51,6 +57,12 @@ class TreeExportTest extends StudyTestBase { @Autowired private ObjectMapper objectMapper; + @Autowired + private FilterService filterService; + + @Autowired + private ActionsService actionsService; + @Test void testExportStudy() throws Exception { // Create a study @@ -65,6 +77,7 @@ void testExportStudy() throws Exception { .withBody("dummy case content".getBytes()))); // Stub the computation parameters fetches computationServerStubs.stubGetParametersAny("{}"); + computationServerStubs.stubGetReferencedUuidsAny(); // Export as zip MvcResult result = mockMvc.perform(get("/v1/studies/{studyUuid}/export/{studyName}", studyUuid, "studyName").header(HEADER_USER_ID, "testUser")) .andExpect(status().isOk()) @@ -102,11 +115,92 @@ void testExportStudy() throws Exception { String rootNetworkCaseName = exportInfos.rootNetworks().getFirst().caseInfos().getCaseName(); String expectedCaseEntry = "cases/" + rootNetworkCaseUuid + "/" + rootNetworkCaseName; assertEquals(List.of(expectedCaseEntry), zipEntryNames.stream().filter(name -> name.startsWith("cases/")).toList()); + assertTrue(zipEntryNames.containsAll(List.of("computationParameters/filterDefinitions.json", "computationParameters/contingencyListDefinitions.json"))); // Verify the case content download call WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); // Verify the computation parameters fetches computationServerStubs.verifyParametersGetAny(10); + computationServerStubs.verifyReferencedUuidsGetAny(5); + } + + @Test + void testExportStudyWithFilterAndContingencyListDefinitions() throws Exception { + UUID studyUuid = createStudyWithStubs("testUser", CASE_UUID); + ReflectionTestUtils.setField(caseService, "caseServerBaseUri", wireMockServer.baseUrl()); + filterService.setBaseUri(wireMockServer.baseUrl()); + actionsService.setActionsServerBaseUri(wireMockServer.baseUrl()); + UUID filterA = UUID.randomUUID(); + UUID filterB = UUID.randomUUID(); + UUID filterC = UUID.randomUUID(); + UUID identifierList = UUID.randomUUID(); + UUID filterBasedList = UUID.randomUUID(); + wireMockStubs.directoryServer.stubCheckPermission(List.of(studyUuid), null, "testUser", PermissionType.READ, false, HttpStatus.OK.value()); + wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo("/v1/cases/" + CASE_UUID)) + .willReturn(WireMock.aResponse().withStatus(200).withHeader("Content-Type", "application/octet-stream").withBody("dummy case content".getBytes()))); + computationServerStubs.stubGetParametersAny("{}"); + stubJsonGet("/v1/parameters/[^/]+/filter-uuids", true, "[\"" + filterA + "\"]"); + stubJsonGet("/v1/parameters/[^/]+/contingency-list-uuids", true, "[\"" + identifierList + "\",\"" + filterBasedList + "\"]"); + String filterAJson = "{\"type\":\"EXPERT\",\"rules\":{\"dataType\":\"COMBINATOR\",\"rules\":[" + + "{\"dataType\":\"FILTER_UUID\",\"field\":\"ID\",\"operator\":\"IS_PART_OF\",\"values\":[\"" + filterC + "\"]}]}}"; + stubJsonGet("/v1/filters/" + filterA, false, filterAJson); + stubJsonGet("/v1/filters/" + filterB, false, "{\"type\":\"IDENTIFIER_LIST\",\"name\":\"b\"}"); + stubJsonGet("/v1/filters/" + filterC, false, "{\"type\":\"IDENTIFIER_LIST\",\"name\":\"c\"}"); + stubJsonGet("/v1/contingency-lists/metadata?ids=" + identifierList, false, "[{\"id\":\"" + identifierList + "\",\"type\":\"IDENTIFIERS\"}]"); + stubJsonGet("/v1/contingency-lists/metadata?ids=" + filterBasedList, false, "[{\"id\":\"" + filterBasedList + "\",\"type\":\"FILTERS\"}]"); + stubJsonGet("/v1/identifier-contingency-lists/" + identifierList, false, "{\"identifierContingencyList\":{}}"); + String filterBasedListJson = "{\"filters\":[{\"id\":\"" + filterB + "\"}]," + + "\"selectedEquipmentTypesByFilter\":[{\"filterId\":\"" + filterB + "\",\"equipmentTypes\":[\"LINE\"]}]}"; + stubJsonGet("/v1/filters-contingency-lists/" + filterBasedList, false, filterBasedListJson); + Map names = Map.of(filterA, "nameA", filterB, "nameB", filterC, "nameC", identifierList, "nameI", filterBasedList, "nameF"); + wireMockStubs.directoryServer.stubGetElementNames(objectMapper.writeValueAsString(names)); + + MvcResult result = mockMvc.perform(get("/v1/studies/{studyUuid}/export/{studyName}", studyUuid, "studyName").header(HEADER_USER_ID, "testUser")) + .andExpect(status().isOk()) + .andReturn(); + + Map zipContents = new HashMap<>(); + try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(result.getResponse().getContentAsByteArray()))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + zipContents.put(entry.getName(), new String(zis.readAllBytes())); + } + } + Set expectedFilters = Set.of( + objectMapper.readTree("{\"uuid\":\"" + filterA + "\",\"name\":\"nameA\",\"content\":" + filterAJson + "}"), + objectMapper.readTree("{\"uuid\":\"" + filterB + "\",\"name\":\"nameB\",\"content\":{\"type\":\"IDENTIFIER_LIST\",\"name\":\"b\"}}"), + objectMapper.readTree("{\"uuid\":\"" + filterC + "\",\"name\":\"nameC\",\"content\":{\"type\":\"IDENTIFIER_LIST\",\"name\":\"c\"}}")); + Set expectedContingencyLists = Set.of( + objectMapper.readTree("{\"uuid\":\"" + identifierList + "\",\"name\":\"nameI\",\"content\":{\"identifierContingencyList\":{}}}"), + objectMapper.readTree("{\"uuid\":\"" + filterBasedList + "\",\"name\":\"nameF\",\"content\":" + filterBasedListJson + "}")); + assertEquals(expectedFilters, readDefinitions(zipContents.get("computationParameters/filterDefinitions.json"))); + assertEquals(expectedContingencyLists, readDefinitions(zipContents.get("computationParameters/contingencyListDefinitions.json"))); + + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); + wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); + computationServerStubs.verifyParametersGetAny(10); + computationServerStubs.verifyReferencedUuidsGetAny(5); + for (UUID filterUuid : List.of(filterA, filterB, filterC)) { + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters/" + filterUuid, false, Map.of(), 1); + } + for (UUID contingencyListUuid : List.of(identifierList, filterBasedList)) { + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/contingency-lists/metadata", false, Map.of("ids", WireMock.equalTo(contingencyListUuid.toString())), 1); + } + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/identifier-contingency-lists/" + identifierList, false, Map.of(), 1); + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters-contingency-lists/" + filterBasedList, false, Map.of(), 1); + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/elements/names", false, Map.of("ids", WireMock.matching(".*"), "strictMode", WireMock.equalTo("false")), 1); + } + + private void stubJsonGet(String url, boolean regex, String body) { + var urlPattern = regex ? WireMock.urlPathMatching(url) : url.contains("?") ? WireMock.urlEqualTo(url) : WireMock.urlPathEqualTo(url); + wireMockServer.stubFor(WireMock.get(urlPattern) + .willReturn(WireMock.ok().withHeader("Content-Type", "application/json").withBody(body))); + } + + private Set readDefinitions(String json) throws IOException { + Set definitions = new HashSet<>(); + objectMapper.readTree(json).forEach(definitions::add); + return definitions; } @Test @@ -130,6 +224,7 @@ void testExportStudyFailToDeleteTempZipFile() throws Exception { .willReturn(WireMock.aResponse().withStatus(200).withHeader("Content-Type", "application/octet-stream") .withBody("dummy case content".getBytes()))); computationServerStubs.stubGetParametersAny("{}"); + computationServerStubs.stubGetReferencedUuidsAny(); // Capture the real zip file path as it is matched, so the test can clean it up itself: // the service's own Files.deleteIfExists call on this path is mocked to fail below. AtomicReference capturedZipFile = new AtomicReference<>(); @@ -162,5 +257,6 @@ void testExportStudyFailToDeleteTempZipFile() throws Exception { WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); computationServerStubs.verifyParametersGetAny(10); + computationServerStubs.verifyReferencedUuidsGetAny(5); } } diff --git a/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java b/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java index 98acdaf591..48b3179b45 100644 --- a/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java +++ b/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java @@ -203,12 +203,21 @@ public void verifyParametersGet(String paramUuid, String prefix) { } public void stubGetParametersAny(String responseBody) { - wireMock.stubFor(WireMock.get(WireMock.urlPathMatching("/v1/parameters/.*")) + wireMock.stubFor(WireMock.get(WireMock.urlPathMatching("/v1/parameters/[^/]+")) .willReturn(WireMock.ok().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).withBody(responseBody))); } + public void stubGetReferencedUuidsAny() { + wireMock.stubFor(WireMock.get(WireMock.urlPathMatching("/v1/parameters/[^/]+/(filter|contingency-list)-uuids")) + .willReturn(WireMock.ok().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).withBody("[]"))); + } + + public void verifyReferencedUuidsGetAny(int nbRequests) { + WireMockUtilsCriteria.verifyGetRequest(wireMock, "/v1/parameters/[^/]+/(filter|contingency-list)-uuids", true, Map.of(), nbRequests); + } + public void verifyParametersGetAny(int nbRequests) { - WireMockUtilsCriteria.verifyGetRequest(wireMock, "/v1/parameters/.*", true, Map.of(), nbRequests); + WireMockUtilsCriteria.verifyGetRequest(wireMock, "/v1/parameters/[^/]+", true, Map.of(), nbRequests); } public void stubCreateParameter(String responseBody) { From b17341d587620bf4afb29e17a5cbd32213bfc11f Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 22 Sep 2026 15:04:24 +0200 Subject: [PATCH 05/18] sonar issues and rabbit remark --- .../study/server/service/ActionsService.java | 17 +++--- .../study/server/service/FilterService.java | 4 -- .../server/service/StudyExportService.java | 54 ++++++++++--------- .../common/ComputationParametersService.java | 1 + 4 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/ActionsService.java b/src/main/java/org/gridsuite/study/server/service/ActionsService.java index 7dc16d0d47..142e08588c 100644 --- a/src/main/java/org/gridsuite/study/server/service/ActionsService.java +++ b/src/main/java/org/gridsuite/study/server/service/ActionsService.java @@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; @@ -64,13 +65,17 @@ public ContingencyCount getContingencyCount(UUID networkUuid, String variantId, } public String getContingencyList(UUID id) { - String metadataPath = DELIMITER + ACTIONS_API_VERSION + "/contingency-lists/metadata?ids={id}"; - Map[] metadata = restTemplate.getForObject(actionsServerBaseUri + metadataPath, Map[].class, id); - if (metadata == null || metadata.length == 0) { - LOGGER.warn("Contingency list {} is referenced but does not exist anymore: it is not exported", id); + try { + String metadataPath = DELIMITER + ACTIONS_API_VERSION + "/contingency-lists/metadata?ids={id}"; + Map[] metadata = restTemplate.getForObject(actionsServerBaseUri + metadataPath, Map[].class, id); + if (metadata == null || metadata.length == 0) { + LOGGER.warn("Contingency list {} is referenced but does not exist anymore: it is not exported", id); + return null; + } + String endpoint = "IDENTIFIERS".equals(metadata[0].get("type")) ? "identifier-contingency-lists" : "filters-contingency-lists"; + return restTemplate.getForObject(actionsServerBaseUri + DELIMITER + ACTIONS_API_VERSION + "/" + endpoint + "/{id}", String.class, id); + } catch (HttpClientErrorException.NotFound e) { return null; } - String endpoint = "IDENTIFIERS".equals(metadata[0].get("type")) ? "identifier-contingency-lists" : "filters-contingency-lists"; - return restTemplate.getForObject(actionsServerBaseUri + DELIMITER + ACTIONS_API_VERSION + "/" + endpoint + "/{id}", String.class, id); } } diff --git a/src/main/java/org/gridsuite/study/server/service/FilterService.java b/src/main/java/org/gridsuite/study/server/service/FilterService.java index 6ee7798d84..0330eaeece 100644 --- a/src/main/java/org/gridsuite/study/server/service/FilterService.java +++ b/src/main/java/org/gridsuite/study/server/service/FilterService.java @@ -14,8 +14,6 @@ import org.gridsuite.filter.globalfilter.GlobalFilter; import org.gridsuite.filter.utils.EquipmentType; import org.gridsuite.study.server.RemoteServicesProperties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.*; @@ -41,7 +39,6 @@ */ @Service public class FilterService { - private static final Logger LOGGER = LoggerFactory.getLogger(FilterService.class); public static final String FILTER_END_POINT_EVALUATE = "/filters/evaluate"; public static final String FILTER_END_POINT_EVALUATE_IDS = "/filters/evaluate/identifiables"; @@ -144,7 +141,6 @@ public String getFilter(UUID filterUuid) { try { return restTemplate.getForObject(getBaseUri() + DELIMITER + FILTER_API_VERSION + "/filters/{id}", String.class, filterUuid); } catch (HttpClientErrorException.NotFound e) { - LOGGER.warn("Filter {} is referenced but does not exist anymore: it is not exported", filterUuid); return null; } } diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index e4ad210510..ec98e3703f 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -134,44 +134,48 @@ private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) thr } private void exportDefinitions(Path parametersDir, Set filterUuids, Set contingencyListUuids) throws IOException { + Map contingencyLists = exportContingencyLists(contingencyListUuids, filterUuids); + Map filters = exportFilters(filterUuids); + + Set definitionUuids = new HashSet<>(filters.keySet()); + definitionUuids.addAll(contingencyLists.keySet()); + Map names = directoryService.getElementNames(definitionUuids); + + writeDefinitions(parametersDir.resolve("filterDefinitions.json"), filters, names); + writeDefinitions(parametersDir.resolve("contingencyListDefinitions.json"), contingencyLists, names); + } + + private Map exportContingencyLists(Set contingencyListUuids, Set filterUuids) throws IOException { Map contingencyLists = new LinkedHashMap<>(); for (UUID contingencyListUuid : contingencyListUuids) { String content = actionsService.getContingencyList(contingencyListUuid); - if (content == null) { - continue; + if (content != null) { + JsonNode contentNode = objectMapper.readTree(content); + contingencyLists.put(contingencyListUuid, contentNode); + contentNode.path("filters").findValues("id").forEach(id -> filterUuids.add(UUID.fromString(id.asText()))); + contentNode.path("selectedEquipmentTypesByFilter").findValues("filterId").forEach(id -> filterUuids.add(UUID.fromString(id.asText()))); } - JsonNode contentNode = objectMapper.readTree(content); - contingencyLists.put(contingencyListUuid, contentNode); - contentNode.path("filters").findValues("id").forEach(id -> filterUuids.add(UUID.fromString(id.asText()))); - contentNode.path("selectedEquipmentTypesByFilter").findValues("filterId").forEach(id -> filterUuids.add(UUID.fromString(id.asText()))); } + return contingencyLists; + } + private Map exportFilters(Set filterUuids) throws IOException { Map filters = new LinkedHashMap<>(); Deque filtersToExport = new ArrayDeque<>(filterUuids); while (!filtersToExport.isEmpty()) { UUID filterUuid = filtersToExport.poll(); - if (filters.containsKey(filterUuid)) { - continue; - } - String content = filterService.getFilter(filterUuid); - if (content == null) { - continue; - } - JsonNode contentNode = objectMapper.readTree(content); - filters.put(filterUuid, contentNode); - for (JsonNode rule : contentNode.findParents("dataType")) { - if ("FILTER_UUID".equals(rule.get("dataType").asText())) { - rule.path("values").forEach(value -> filtersToExport.add(UUID.fromString(value.asText()))); + String content = filters.containsKey(filterUuid) ? null : filterService.getFilter(filterUuid); + if (content != null) { + JsonNode contentNode = objectMapper.readTree(content); + filters.put(filterUuid, contentNode); + for (JsonNode rule : contentNode.findParents("dataType")) { + if ("FILTER_UUID".equals(rule.get("dataType").asText())) { + rule.path("values").forEach(value -> filtersToExport.add(UUID.fromString(value.asText()))); + } } } } - - Set allUuids = new HashSet<>(filters.keySet()); - allUuids.addAll(contingencyLists.keySet()); - Map names = directoryService.getElementNames(allUuids); - - writeDefinitions(parametersDir.resolve("filterDefinitions.json"), filters, names); - writeDefinitions(parametersDir.resolve("contingencyListDefinitions.json"), contingencyLists, names); + return filters; } private void writeDefinitions(Path file, Map contents, Map names) throws IOException { diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index 6ced4ecc5b..fcd9f50019 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -267,6 +267,7 @@ public void exportParameters(StudyEntity studyEntity, String userId, Path parame case VOLTAGE_INITIALIZATION -> filterUuids.addAll(voltageInitService.getFilterUuids(parametersUuid)); case PCC_MIN -> filterUuids.addAll(pccMinService.getFilterUuids(parametersUuid)); default -> { + return; } } } From 2d24d80be100d3081ddff452939a560ba78c96b1 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 22 Sep 2026 17:07:14 +0200 Subject: [PATCH 06/18] fix default case --- .../server/service/common/ComputationParametersService.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index fcd9f50019..1ffa07c5be 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -266,9 +266,7 @@ public void exportParameters(StudyEntity studyEntity, String userId, Path parame } case VOLTAGE_INITIALIZATION -> filterUuids.addAll(voltageInitService.getFilterUuids(parametersUuid)); case PCC_MIN -> filterUuids.addAll(pccMinService.getFilterUuids(parametersUuid)); - default -> { - return; - } + default -> { } } } } From 66775ee4b59bcba1c6354ed8405eb8fffff78859 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Wed, 23 Sep 2026 14:37:05 +0200 Subject: [PATCH 07/18] draft version to resolve the referenced filters from action and filters server side --- .../study/server/service/ActionsService.java | 28 +++--- .../study/server/service/FilterService.java | 20 +++-- .../server/service/StudyExportService.java | 85 +++++++++---------- .../studycontroller/TreeExportTest.java | 48 +++++------ 4 files changed, 88 insertions(+), 93 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/ActionsService.java b/src/main/java/org/gridsuite/study/server/service/ActionsService.java index 142e08588c..a919f82c8a 100644 --- a/src/main/java/org/gridsuite/study/server/service/ActionsService.java +++ b/src/main/java/org/gridsuite/study/server/service/ActionsService.java @@ -15,14 +15,13 @@ import org.apache.commons.lang3.StringUtils; import org.gridsuite.study.server.RemoteServicesProperties; import org.gridsuite.study.server.dto.ContingencyCount; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; -import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.UUID; @@ -31,7 +30,6 @@ @Service public class ActionsService { - private static final Logger LOGGER = LoggerFactory.getLogger(ActionsService.class); private final RestTemplate restTemplate; @@ -64,18 +62,14 @@ public ContingencyCount getContingencyCount(UUID networkUuid, String variantId, ).getBody(); } - public String getContingencyList(UUID id) { - try { - String metadataPath = DELIMITER + ACTIONS_API_VERSION + "/contingency-lists/metadata?ids={id}"; - Map[] metadata = restTemplate.getForObject(actionsServerBaseUri + metadataPath, Map[].class, id); - if (metadata == null || metadata.length == 0) { - LOGGER.warn("Contingency list {} is referenced but does not exist anymore: it is not exported", id); - return null; - } - String endpoint = "IDENTIFIERS".equals(metadata[0].get("type")) ? "identifier-contingency-lists" : "filters-contingency-lists"; - return restTemplate.getForObject(actionsServerBaseUri + DELIMITER + ACTIONS_API_VERSION + "/" + endpoint + "/{id}", String.class, id); - } catch (HttpClientErrorException.NotFound e) { - return null; - } + public List getReferencedFilterUuids(Collection contingencyListUuids) { + String path = UriComponentsBuilder.fromPath(DELIMITER + ACTIONS_API_VERSION + "/contingency-lists/filter-uuids") + .queryParam(CONTINGENCY_LIST_IDS, contingencyListUuids) + .toUriString(); + return restTemplate.exchange(actionsServerBaseUri + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); + } + + public String getContingencyLists(Collection ids) { + return restTemplate.postForObject(actionsServerBaseUri + DELIMITER + ACTIONS_API_VERSION + "/contingency-lists", ids, String.class); } } diff --git a/src/main/java/org/gridsuite/study/server/service/FilterService.java b/src/main/java/org/gridsuite/study/server/service/FilterService.java index 0330eaeece..87e97a9750 100644 --- a/src/main/java/org/gridsuite/study/server/service/FilterService.java +++ b/src/main/java/org/gridsuite/study/server/service/FilterService.java @@ -18,11 +18,11 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.*; import org.springframework.stereotype.Service; -import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -137,11 +137,17 @@ public String evaluateFilters(UUID networkUuid, String filters) { return restTemplate.postForObject(uriComponent.toUriString(), request, String.class); } - public String getFilter(UUID filterUuid) { - try { - return restTemplate.getForObject(getBaseUri() + DELIMITER + FILTER_API_VERSION + "/filters/{id}", String.class, filterUuid); - } catch (HttpClientErrorException.NotFound e) { - return null; - } + public List getReferencedFilterUuids(Collection filterUuids) { + String path = UriComponentsBuilder.fromPath(DELIMITER + FILTER_API_VERSION + "/filters/referenced-filter-uuids") + .queryParam(IDS, filterUuids) + .toUriString(); + return restTemplate.exchange(getBaseUri() + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); + } + + public String getFilters(Collection filterUuids) { + String path = UriComponentsBuilder.fromPath(DELIMITER + FILTER_API_VERSION + "/filters/metadata") + .queryParam(IDS, filterUuids) + .toUriString(); + return restTemplate.getForObject(getBaseUri() + path, String.class); } } diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index ec98e3703f..733c7d79b3 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -27,16 +27,16 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; -import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collection; import java.util.Comparator; -import java.util.Deque; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import java.util.stream.Stream; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; @@ -124,7 +124,7 @@ private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) thr Set filterUuids = new HashSet<>(); Set contingencyListUuids = new HashSet<>(); computationParametersService.exportParameters(studyService.getStudy(studyUuid), userId, parametersDir, filterUuids, contingencyListUuids); - exportDefinitions(parametersDir, filterUuids, contingencyListUuids); + exportContingencyListsAndFilters(parametersDir, filterUuids, contingencyListUuids); Path zipFile = createTempExportFile(studyUuid); try (OutputStream fos = Files.newOutputStream(zipFile); ZipOutputStream zipOut = new ZipOutputStream(fos)) { @@ -133,55 +133,52 @@ private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) thr return zipFile; } - private void exportDefinitions(Path parametersDir, Set filterUuids, Set contingencyListUuids) throws IOException { - Map contingencyLists = exportContingencyLists(contingencyListUuids, filterUuids); - Map filters = exportFilters(filterUuids); + private void exportContingencyListsAndFilters(Path parametersDir, Set filterUuids, Set contingencyListUuids) throws IOException { + // referenced filters are resolved by their owner servers + Set allFilterUuids = new HashSet<>(filterUuids); + if (!contingencyListUuids.isEmpty()) { + allFilterUuids.addAll(actionsService.getReferencedFilterUuids(contingencyListUuids)); + } + if (!allFilterUuids.isEmpty()) { + allFilterUuids.addAll(filterService.getReferencedFilterUuids(allFilterUuids)); + } + Map contingencyLists = fetchContingencyListsAndFilters(contingencyListUuids, actionsService::getContingencyLists); + Map filters = fetchContingencyListsAndFilters(allFilterUuids, filterService::getFilters); - Set definitionUuids = new HashSet<>(filters.keySet()); - definitionUuids.addAll(contingencyLists.keySet()); - Map names = directoryService.getElementNames(definitionUuids); + Set contingencyListsAndFiltersUuids = new HashSet<>(filters.keySet()); + contingencyListsAndFiltersUuids.addAll(contingencyLists.keySet()); + Map names = directoryService.getElementNames(contingencyListsAndFiltersUuids); - writeDefinitions(parametersDir.resolve("filterDefinitions.json"), filters, names); - writeDefinitions(parametersDir.resolve("contingencyListDefinitions.json"), contingencyLists, names); + writeJsonToFileDir(parametersDir.resolve("filters.json"), filters, names); + writeJsonToFileDir(parametersDir.resolve("contingencyList.json"), contingencyLists, names); } - private Map exportContingencyLists(Set contingencyListUuids, Set filterUuids) throws IOException { - Map contingencyLists = new LinkedHashMap<>(); - for (UUID contingencyListUuid : contingencyListUuids) { - String content = actionsService.getContingencyList(contingencyListUuid); - if (content != null) { - JsonNode contentNode = objectMapper.readTree(content); - contingencyLists.put(contingencyListUuid, contentNode); - contentNode.path("filters").findValues("id").forEach(id -> filterUuids.add(UUID.fromString(id.asText()))); - contentNode.path("selectedEquipmentTypesByFilter").findValues("filterId").forEach(id -> filterUuids.add(UUID.fromString(id.asText()))); - } + private Map fetchContingencyListsAndFilters(Set uuids, Function, String> fetcher) throws IOException { + Map result = new LinkedHashMap<>(); + if (uuids.isEmpty()) { + return result; } - return contingencyLists; - } - - private Map exportFilters(Set filterUuids) throws IOException { - Map filters = new LinkedHashMap<>(); - Deque filtersToExport = new ArrayDeque<>(filterUuids); - while (!filtersToExport.isEmpty()) { - UUID filterUuid = filtersToExport.poll(); - String content = filters.containsKey(filterUuid) ? null : filterService.getFilter(filterUuid); - if (content != null) { - JsonNode contentNode = objectMapper.readTree(content); - filters.put(filterUuid, contentNode); - for (JsonNode rule : contentNode.findParents("dataType")) { - if ("FILTER_UUID".equals(rule.get("dataType").asText())) { - rule.path("values").forEach(value -> filtersToExport.add(UUID.fromString(value.asText()))); - } - } - } + for (JsonNode contentNode : objectMapper.readTree(fetcher.apply(uuids))) { + result.put(UUID.fromString(contentNode.get("id").asText()), contentNode); } - return filters; + Set missingUuids = new HashSet<>(uuids); + missingUuids.removeAll(result.keySet()); + if (!missingUuids.isEmpty()) { + LOGGER.warn("Elements not found during study export, they will be missing from the archive: {}", missingUuids); + } + return result; } - private void writeDefinitions(Path file, Map contents, Map names) throws IOException { - List> definitions = new ArrayList<>(); - contents.forEach((uuid, content) -> definitions.add(Map.of("uuid", uuid, "name", names.getOrDefault(uuid, ""), "content", content))); - objectMapper.writeValue(file.toFile(), definitions); + private void writeJsonToFileDir(Path file, Map contents, Map names) throws IOException { + List> result = new ArrayList<>(); + contents.forEach((uuid, content) -> { + Map entry = new LinkedHashMap<>(); + entry.put("uuid", uuid); + entry.put("name", names.get(uuid)); + entry.put("content", content); + result.add(entry); + }); + objectMapper.writeValue(file.toFile(), result); } private Path createTempWorkDir(UUID studyUuid) { diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java index 2bb19a8945..e6bde68ec8 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java @@ -133,25 +133,28 @@ void testExportStudyWithFilterAndContingencyListDefinitions() throws Exception { UUID filterA = UUID.randomUUID(); UUID filterB = UUID.randomUUID(); UUID filterC = UUID.randomUUID(); + UUID deletedFilter = UUID.randomUUID(); UUID identifierList = UUID.randomUUID(); UUID filterBasedList = UUID.randomUUID(); wireMockStubs.directoryServer.stubCheckPermission(List.of(studyUuid), null, "testUser", PermissionType.READ, false, HttpStatus.OK.value()); wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo("/v1/cases/" + CASE_UUID)) .willReturn(WireMock.aResponse().withStatus(200).withHeader("Content-Type", "application/octet-stream").withBody("dummy case content".getBytes()))); computationServerStubs.stubGetParametersAny("{}"); - stubJsonGet("/v1/parameters/[^/]+/filter-uuids", true, "[\"" + filterA + "\"]"); - stubJsonGet("/v1/parameters/[^/]+/contingency-list-uuids", true, "[\"" + identifierList + "\",\"" + filterBasedList + "\"]"); - String filterAJson = "{\"type\":\"EXPERT\",\"rules\":{\"dataType\":\"COMBINATOR\",\"rules\":[" + stubJsonGet("/v1/parameters/[^/]+/filter-uuids", "[\"" + filterA + "\",\"" + deletedFilter + "\"]"); + stubJsonGet("/v1/parameters/[^/]+/contingency-list-uuids", "[\"" + identifierList + "\",\"" + filterBasedList + "\"]"); + String filterAJson = "{\"id\":\"" + filterA + "\",\"type\":\"EXPERT\",\"rules\":{\"dataType\":\"COMBINATOR\",\"rules\":[" + "{\"dataType\":\"FILTER_UUID\",\"field\":\"ID\",\"operator\":\"IS_PART_OF\",\"values\":[\"" + filterC + "\"]}]}}"; - stubJsonGet("/v1/filters/" + filterA, false, filterAJson); - stubJsonGet("/v1/filters/" + filterB, false, "{\"type\":\"IDENTIFIER_LIST\",\"name\":\"b\"}"); - stubJsonGet("/v1/filters/" + filterC, false, "{\"type\":\"IDENTIFIER_LIST\",\"name\":\"c\"}"); - stubJsonGet("/v1/contingency-lists/metadata?ids=" + identifierList, false, "[{\"id\":\"" + identifierList + "\",\"type\":\"IDENTIFIERS\"}]"); - stubJsonGet("/v1/contingency-lists/metadata?ids=" + filterBasedList, false, "[{\"id\":\"" + filterBasedList + "\",\"type\":\"FILTERS\"}]"); - stubJsonGet("/v1/identifier-contingency-lists/" + identifierList, false, "{\"identifierContingencyList\":{}}"); - String filterBasedListJson = "{\"filters\":[{\"id\":\"" + filterB + "\"}]," + String filterBJson = "{\"id\":\"" + filterB + "\",\"type\":\"IDENTIFIER_LIST\"}"; + String filterCJson = "{\"id\":\"" + filterC + "\",\"type\":\"IDENTIFIER_LIST\"}"; + // filterB is referenced by the filter based contingency list, filterC by filterA, deletedFilter does not exist anymore + stubJsonGet("/v1/contingency-lists/filter-uuids", "[\"" + filterB + "\"]"); + stubJsonGet("/v1/filters/referenced-filter-uuids", "[\"" + filterC + "\"]"); + stubJsonGet("/v1/filters/metadata", "[" + filterAJson + "," + filterBJson + "," + filterCJson + "]"); + String identifierListJson = "{\"id\":\"" + identifierList + "\",\"type\":\"IDENTIFIERS\",\"identifiersListByNetwork\":[]}"; + String filterBasedListJson = "{\"id\":\"" + filterBasedList + "\",\"type\":\"FILTERS\",\"filters\":[{\"id\":\"" + filterB + "\"}]," + "\"selectedEquipmentTypesByFilter\":[{\"filterId\":\"" + filterB + "\",\"equipmentTypes\":[\"LINE\"]}]}"; - stubJsonGet("/v1/filters-contingency-lists/" + filterBasedList, false, filterBasedListJson); + wireMockServer.stubFor(WireMock.post(WireMock.urlPathEqualTo("/v1/contingency-lists")) + .willReturn(WireMock.okJson("[" + identifierListJson + "," + filterBasedListJson + "]"))); Map names = Map.of(filterA, "nameA", filterB, "nameB", filterC, "nameC", identifierList, "nameI", filterBasedList, "nameF"); wireMockStubs.directoryServer.stubGetElementNames(objectMapper.writeValueAsString(names)); @@ -168,10 +171,10 @@ void testExportStudyWithFilterAndContingencyListDefinitions() throws Exception { } Set expectedFilters = Set.of( objectMapper.readTree("{\"uuid\":\"" + filterA + "\",\"name\":\"nameA\",\"content\":" + filterAJson + "}"), - objectMapper.readTree("{\"uuid\":\"" + filterB + "\",\"name\":\"nameB\",\"content\":{\"type\":\"IDENTIFIER_LIST\",\"name\":\"b\"}}"), - objectMapper.readTree("{\"uuid\":\"" + filterC + "\",\"name\":\"nameC\",\"content\":{\"type\":\"IDENTIFIER_LIST\",\"name\":\"c\"}}")); + objectMapper.readTree("{\"uuid\":\"" + filterB + "\",\"name\":\"nameB\",\"content\":" + filterBJson + "}"), + objectMapper.readTree("{\"uuid\":\"" + filterC + "\",\"name\":\"nameC\",\"content\":" + filterCJson + "}")); Set expectedContingencyLists = Set.of( - objectMapper.readTree("{\"uuid\":\"" + identifierList + "\",\"name\":\"nameI\",\"content\":{\"identifierContingencyList\":{}}}"), + objectMapper.readTree("{\"uuid\":\"" + identifierList + "\",\"name\":\"nameI\",\"content\":" + identifierListJson + "}"), objectMapper.readTree("{\"uuid\":\"" + filterBasedList + "\",\"name\":\"nameF\",\"content\":" + filterBasedListJson + "}")); assertEquals(expectedFilters, readDefinitions(zipContents.get("computationParameters/filterDefinitions.json"))); assertEquals(expectedContingencyLists, readDefinitions(zipContents.get("computationParameters/contingencyListDefinitions.json"))); @@ -180,20 +183,15 @@ void testExportStudyWithFilterAndContingencyListDefinitions() throws Exception { wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); computationServerStubs.verifyParametersGetAny(10); computationServerStubs.verifyReferencedUuidsGetAny(5); - for (UUID filterUuid : List.of(filterA, filterB, filterC)) { - WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters/" + filterUuid, false, Map.of(), 1); - } - for (UUID contingencyListUuid : List.of(identifierList, filterBasedList)) { - WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/contingency-lists/metadata", false, Map.of("ids", WireMock.equalTo(contingencyListUuid.toString())), 1); - } - WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/identifier-contingency-lists/" + identifierList, false, Map.of(), 1); - WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters-contingency-lists/" + filterBasedList, false, Map.of(), 1); + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/contingency-lists/filter-uuids", false, Map.of("ids", WireMock.matching(".*")), 1); + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters/referenced-filter-uuids", false, Map.of("ids", WireMock.matching(".*")), 1); + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters/metadata", false, Map.of("ids", WireMock.matching(".*")), 1); + WireMockUtilsCriteria.verifyPostRequest(wireMockServer, "/v1/contingency-lists", Map.of(), 1); WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/elements/names", false, Map.of("ids", WireMock.matching(".*"), "strictMode", WireMock.equalTo("false")), 1); } - private void stubJsonGet(String url, boolean regex, String body) { - var urlPattern = regex ? WireMock.urlPathMatching(url) : url.contains("?") ? WireMock.urlEqualTo(url) : WireMock.urlPathEqualTo(url); - wireMockServer.stubFor(WireMock.get(urlPattern) + private void stubJsonGet(String urlRegex, String body) { + wireMockServer.stubFor(WireMock.get(WireMock.urlPathMatching(urlRegex)) .willReturn(WireMock.ok().withHeader("Content-Type", "application/json").withBody(body))); } From 15d3ce3efa42b9b851ad447fb178850511f8b807 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Wed, 23 Sep 2026 14:54:42 +0200 Subject: [PATCH 08/18] fix TU --- .../study/server/studycontroller/TreeExportTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java index e6bde68ec8..d47d40c5b3 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java @@ -115,7 +115,7 @@ void testExportStudy() throws Exception { String rootNetworkCaseName = exportInfos.rootNetworks().getFirst().caseInfos().getCaseName(); String expectedCaseEntry = "cases/" + rootNetworkCaseUuid + "/" + rootNetworkCaseName; assertEquals(List.of(expectedCaseEntry), zipEntryNames.stream().filter(name -> name.startsWith("cases/")).toList()); - assertTrue(zipEntryNames.containsAll(List.of("computationParameters/filterDefinitions.json", "computationParameters/contingencyListDefinitions.json"))); + assertTrue(zipEntryNames.containsAll(List.of("computationParameters/filters.json", "computationParameters/contingencyList.json"))); // Verify the case content download call WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); @@ -176,8 +176,8 @@ void testExportStudyWithFilterAndContingencyListDefinitions() throws Exception { Set expectedContingencyLists = Set.of( objectMapper.readTree("{\"uuid\":\"" + identifierList + "\",\"name\":\"nameI\",\"content\":" + identifierListJson + "}"), objectMapper.readTree("{\"uuid\":\"" + filterBasedList + "\",\"name\":\"nameF\",\"content\":" + filterBasedListJson + "}")); - assertEquals(expectedFilters, readDefinitions(zipContents.get("computationParameters/filterDefinitions.json"))); - assertEquals(expectedContingencyLists, readDefinitions(zipContents.get("computationParameters/contingencyListDefinitions.json"))); + assertEquals(expectedFilters, readDefinitions(zipContents.get("computationParameters/filters.json"))); + assertEquals(expectedContingencyLists, readDefinitions(zipContents.get("computationParameters/contingencyList.json"))); WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); From e0de2b3c97551284c6a0c9bf2f56ebb7a2e324ea Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Fri, 25 Sep 2026 14:19:07 +0200 Subject: [PATCH 09/18] code review remarks --- .../parameters/ExportedElement.java | 23 +++++ .../ExportedParametersReferences.java | 36 ++++++++ .../parameters/PccMinExportedParameters.java | 28 ++++++ .../SecurityAnalysisExportedParameters.java | 31 +++++++ ...SensitivityAnalysisExportedParameters.java | 60 +++++++++++++ .../VoltageInitExportedParameters.java | 45 ++++++++++ .../server/service/StudyExportService.java | 87 ++++++++----------- .../common/ComputationParametersService.java | 33 ++----- .../service/pccmin/PccMinRestService.java | 6 -- .../SecurityAnalysisRestService.java | 6 -- .../SensitivityAnalysisRestService.java | 10 --- .../voltageinit/VoltageInitRestService.java | 6 -- .../ExportedParametersReferencesTest.java | 73 ++++++++++++++++ .../studycontroller/TreeExportTest.java | 16 ++-- .../wiremock/ComputationServerStubs.java | 9 -- 15 files changed, 348 insertions(+), 121 deletions(-) create mode 100644 src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElement.java create mode 100644 src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferences.java create mode 100644 src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/PccMinExportedParameters.java create mode 100644 src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SecurityAnalysisExportedParameters.java create mode 100644 src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java create mode 100644 src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/VoltageInitExportedParameters.java create mode 100644 src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferencesTest.java diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElement.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElement.java new file mode 100644 index 0000000000..6e404b9caa --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElement.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.Map; +import java.util.UUID; + +/** + * @author Ghazwa Rehili + */ +public record ExportedElement(UUID uuid, String name, JsonNode content) { + + public static ExportedElement of(JsonNode content, Map names) { + UUID uuid = UUID.fromString(content.get("id").asText()); + return new ExportedElement(uuid, names.get(uuid), content); + } +} diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferences.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferences.java new file mode 100644 index 0000000000..002e8b08fc --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferences.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.study.server.dto.studyexport.parameters; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Ghazwa Rehili + */ +public interface ExportedParametersReferences { + + default Set getFilterUuids() { + return Set.of(); + } + + default Set getContingencyListUuids() { + return Set.of(); + } + + static Stream nullSafe(List list) { + return list == null ? Stream.empty() : list.stream(); + } + + static Set toUuidSet(Stream uuids) { + return uuids.filter(Objects::nonNull).collect(Collectors.toSet()); + } +} diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/PccMinExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/PccMinExportedParameters.java new file mode 100644 index 0000000000..888d6a4c18 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/PccMinExportedParameters.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.toUuidSet; + +/** + * @author Ghazwa Rehili + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record PccMinExportedParameters(List filters) implements ExportedParametersReferences { + + @Override + public Set getFilterUuids() { + return toUuidSet(nullSafe(filters)); + } +} diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SecurityAnalysisExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SecurityAnalysisExportedParameters.java new file mode 100644 index 0000000000..c4827d752d --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SecurityAnalysisExportedParameters.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.toUuidSet; + +/** + * @author Ghazwa Rehili + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record SecurityAnalysisExportedParameters(List contingencyListsInfos) implements ExportedParametersReferences { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record ContingencyListsInfos(List contingencyLists) { } + + @Override + public Set getContingencyListUuids() { + return toUuidSet(nullSafe(contingencyListsInfos).flatMap(infos -> nullSafe(infos.contingencyLists()))); + } +} diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java new file mode 100644 index 0000000000..49cefd081a --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.toUuidSet; + +/** + * @author Ghazwa Rehili + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record SensitivityAnalysisExportedParameters( + List sensitivityInjectionsSet, + List sensitivityInjection, + List sensitivityHVDC, + List sensitivityPST, + List sensitivityNodes +) implements ExportedParametersReferences { + @JsonIgnoreProperties(ignoreUnknown = true) + public record SensitivityFactor( + List monitoredBranches, + List injections, + List hvdcs, + List psts, + List monitoredVoltageLevels, + List equipmentsInVoltageRegulation, + List contingencies + ) { + Stream filterUuids() { + return Stream.of(monitoredBranches, injections, hvdcs, psts, monitoredVoltageLevels, equipmentsInVoltageRegulation) + .flatMap(ExportedParametersReferences::nullSafe); + } + } + + private Stream allFactors() { + return Stream.of(sensitivityInjectionsSet, sensitivityInjection, sensitivityHVDC, sensitivityPST, sensitivityNodes) + .flatMap(ExportedParametersReferences::nullSafe); + } + + @Override + public Set getFilterUuids() { + return toUuidSet(allFactors().flatMap(SensitivityFactor::filterUuids)); + } + + @Override + public Set getContingencyListUuids() { + return toUuidSet(allFactors().flatMap(factor -> nullSafe(factor.contingencies()))); + } +} diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/VoltageInitExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/VoltageInitExportedParameters.java new file mode 100644 index 0000000000..19b54ef41c --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/VoltageInitExportedParameters.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.toUuidSet; + +/** + * @author Ghazwa Rehili + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record VoltageInitExportedParameters( + List voltageLimitsModification, + List voltageLimitsDefault, + List variableQGenerators, + List variableTwoWindingsTransformers, + List variableShuntCompensators +) implements ExportedParametersReferences { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record VoltageLimit(List filters) { } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record FilterEquipments(UUID filterId) { } + + @Override + public Set getFilterUuids() { + Stream limitFilters = Stream.concat(nullSafe(voltageLimitsModification), nullSafe(voltageLimitsDefault)) + .flatMap(voltageLimit -> nullSafe(voltageLimit.filters())); + Stream variableFilters = Stream.of(variableQGenerators, variableTwoWindingsTransformers, variableShuntCompensators) + .flatMap(ExportedParametersReferences::nullSafe); + return toUuidSet(Stream.concat(limitFilters, variableFilters).map(FilterEquipments::filterId)); + } +} diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index 733c7d79b3..52d4811f1e 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -6,11 +6,12 @@ */ package org.gridsuite.study.server.service; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.gridsuite.study.server.dto.ComputationType; import org.gridsuite.study.server.dto.networkexport.PermissionType; import org.gridsuite.study.server.dto.studyexport.RootNetworkExportInfos; import org.gridsuite.study.server.dto.studyexport.TreeExportInfos; +import org.gridsuite.study.server.dto.studyexport.parameters.*; import org.gridsuite.study.server.error.StudyException; import org.gridsuite.study.server.service.common.ComputationParametersService; import org.slf4j.Logger; @@ -27,17 +28,15 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; -import java.util.ArrayList; -import java.util.Collection; import java.util.Comparator; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -53,6 +52,8 @@ public class StudyExportService { public static final String TREE_JSON_FILE_NAME = "tree.json"; public static final String CASES_FOLDER = "cases"; public static final String PARAMETERS_FOLDER = "computationParameters"; + public static final String CONTINGENCY_LIST_JSON = "contingencyList.json"; + public static final String FILTERS_JSON = "filters.json"; private final StudyService studyService; private final CaseService caseService; @@ -120,11 +121,7 @@ private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) thr String caseName = rootNetworkInfos.caseInfos().getCaseName(); exportCaseFile(caseUuid, caseName, casesDir); } - Path parametersDir = tempDir.resolve(PARAMETERS_FOLDER); - Set filterUuids = new HashSet<>(); - Set contingencyListUuids = new HashSet<>(); - computationParametersService.exportParameters(studyService.getStudy(studyUuid), userId, parametersDir, filterUuids, contingencyListUuids); - exportContingencyListsAndFilters(parametersDir, filterUuids, contingencyListUuids); + exportParameters(studyUuid, userId, tempDir); Path zipFile = createTempExportFile(studyUuid); try (OutputStream fos = Files.newOutputStream(zipFile); ZipOutputStream zipOut = new ZipOutputStream(fos)) { @@ -133,52 +130,42 @@ private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) thr return zipFile; } - private void exportContingencyListsAndFilters(Path parametersDir, Set filterUuids, Set contingencyListUuids) throws IOException { - // referenced filters are resolved by their owner servers - Set allFilterUuids = new HashSet<>(filterUuids); - if (!contingencyListUuids.isEmpty()) { - allFilterUuids.addAll(actionsService.getReferencedFilterUuids(contingencyListUuids)); - } - if (!allFilterUuids.isEmpty()) { - allFilterUuids.addAll(filterService.getReferencedFilterUuids(allFilterUuids)); + private void exportParameters(UUID studyUuid, String userId, Path tempDir) throws IOException { + Path parametersDir = Files.createDirectories(tempDir.resolve(PARAMETERS_FOLDER)); + Set filterUuids = new HashSet<>(); + Set contingencyListUuids = new HashSet<>(); + for (Map.Entry parameters : computationParametersService.exportParameters(studyService.getStudy(studyUuid), userId).entrySet()) { + Files.writeString(parametersDir.resolve(parameters.getKey().name() + ".json"), parameters.getValue()); + ExportedParametersReferences references = readParametersReferences(parameters.getKey(), parameters.getValue()); + filterUuids.addAll(references.getFilterUuids()); + contingencyListUuids.addAll(references.getContingencyListUuids()); } - Map contingencyLists = fetchContingencyListsAndFilters(contingencyListUuids, actionsService::getContingencyLists); - Map filters = fetchContingencyListsAndFilters(allFilterUuids, filterService::getFilters); - - Set contingencyListsAndFiltersUuids = new HashSet<>(filters.keySet()); - contingencyListsAndFiltersUuids.addAll(contingencyLists.keySet()); - Map names = directoryService.getElementNames(contingencyListsAndFiltersUuids); - - writeJsonToFileDir(parametersDir.resolve("filters.json"), filters, names); - writeJsonToFileDir(parametersDir.resolve("contingencyList.json"), contingencyLists, names); - } - - private Map fetchContingencyListsAndFilters(Set uuids, Function, String> fetcher) throws IOException { - Map result = new LinkedHashMap<>(); - if (uuids.isEmpty()) { - return result; + if (!contingencyListUuids.isEmpty()) { + filterUuids.addAll(actionsService.getReferencedFilterUuids(contingencyListUuids)); } - for (JsonNode contentNode : objectMapper.readTree(fetcher.apply(uuids))) { - result.put(UUID.fromString(contentNode.get("id").asText()), contentNode); + if (!filterUuids.isEmpty()) { + filterUuids.addAll(filterService.getReferencedFilterUuids(filterUuids)); } - Set missingUuids = new HashSet<>(uuids); - missingUuids.removeAll(result.keySet()); - if (!missingUuids.isEmpty()) { - LOGGER.warn("Elements not found during study export, they will be missing from the archive: {}", missingUuids); + Map names = directoryService.getElementNames(Stream.concat(filterUuids.stream(), contingencyListUuids.stream()).collect(Collectors.toSet())); + for (Map.Entry contents : Map.of( + CONTINGENCY_LIST_JSON, contingencyListUuids.isEmpty() ? "[]" : actionsService.getContingencyLists(contingencyListUuids), + FILTERS_JSON, filterUuids.isEmpty() ? "[]" : filterService.getFilters(filterUuids)).entrySet()) { + objectMapper.writeValue(parametersDir.resolve(contents.getKey()).toFile(), + StreamSupport.stream(objectMapper.readTree(contents.getValue()).spliterator(), false) + .map(content -> ExportedElement.of(content, names)) + .toList()); } - return result; } - private void writeJsonToFileDir(Path file, Map contents, Map names) throws IOException { - List> result = new ArrayList<>(); - contents.forEach((uuid, content) -> { - Map entry = new LinkedHashMap<>(); - entry.put("uuid", uuid); - entry.put("name", names.get(uuid)); - entry.put("content", content); - result.add(entry); - }); - objectMapper.writeValue(file.toFile(), result); + private ExportedParametersReferences readParametersReferences(ComputationType type, String parametersJson) throws IOException { + Class referencesClass = switch (type) { + case SECURITY_ANALYSIS -> SecurityAnalysisExportedParameters.class; + case SENSITIVITY_ANALYSIS -> SensitivityAnalysisExportedParameters.class; + case VOLTAGE_INITIALIZATION -> VoltageInitExportedParameters.class; + case PCC_MIN -> PccMinExportedParameters.class; + default -> null; + }; + return referencesClass == null ? new ExportedParametersReferences() { } : objectMapper.readValue(parametersJson, referencesClass); } private Path createTempWorkDir(UUID studyUuid) { diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index 1ffa07c5be..7d9af8b633 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -6,6 +6,7 @@ */ package org.gridsuite.study.server.service.common; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.gridsuite.study.server.dto.ComputationType; import org.gridsuite.study.server.dto.UserProfileInfos; @@ -26,11 +27,9 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; +import java.util.EnumMap; import java.util.List; -import java.util.Set; +import java.util.Map; import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -47,10 +46,6 @@ public class ComputationParametersService { private final UserAdminService userAdminService; private final ObjectMapper objectMapper; - private final SecurityAnalysisRestService securityAnalysisService; - private final SensitivityAnalysisRestService sensitivityAnalysisService; - private final VoltageInitRestService voltageInitService; - private final PccMinRestService pccMinService; private final List computationParametersDefinitions; // this is useful to avoid repetitive calls when doing operation on all computation types (duplicate, delete, export) @@ -79,10 +74,6 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis this.userAdminService = userAdminService; this.objectMapper = objectMapper; - this.securityAnalysisService = securityAnalysisService; - this.sensitivityAnalysisService = sensitivityAnalysisService; - this.voltageInitService = voltageInitService; - this.pccMinService = pccMinService; this.computationParametersDefinitions = List.of( new ComputationParametersDefinition( ComputationType.LOAD_FLOW, @@ -248,26 +239,16 @@ public void createOrUpdateParameters( } } - public void exportParameters(StudyEntity studyEntity, String userId, Path parametersDir, Set filterUuids, Set contingencyListUuids) throws IOException { - Files.createDirectories(parametersDir); + public Map exportParameters(StudyEntity studyEntity, String userId) throws JsonProcessingException { + Map parametersByType = new EnumMap<>(ComputationType.class); for (ComputationParametersDefinition definition : computationParametersDefinitions) { UUID parametersUuid = definition.studyParameterGetter().apply(studyEntity); if (parametersUuid == null) { continue; } Object parameters = definition.parametersFetcher().apply(parametersUuid, userId); - String json = parameters instanceof String parametersJson ? parametersJson : objectMapper.writeValueAsString(parameters); - Files.writeString(parametersDir.resolve(definition.type().name() + ".json"), json); - switch (definition.type()) { - case SECURITY_ANALYSIS -> contingencyListUuids.addAll(securityAnalysisService.getContingencyListUuids(parametersUuid)); - case SENSITIVITY_ANALYSIS -> { - filterUuids.addAll(sensitivityAnalysisService.getFilterUuids(parametersUuid)); - contingencyListUuids.addAll(sensitivityAnalysisService.getContingencyListUuids(parametersUuid)); - } - case VOLTAGE_INITIALIZATION -> filterUuids.addAll(voltageInitService.getFilterUuids(parametersUuid)); - case PCC_MIN -> filterUuids.addAll(pccMinService.getFilterUuids(parametersUuid)); - default -> { } - } + parametersByType.put(definition.type(), parameters instanceof String parametersJson ? parametersJson : objectMapper.writeValueAsString(parameters)); } + return parametersByType; } } diff --git a/src/main/java/org/gridsuite/study/server/service/pccmin/PccMinRestService.java b/src/main/java/org/gridsuite/study/server/service/pccmin/PccMinRestService.java index 6179d0ad93..e3617838c0 100644 --- a/src/main/java/org/gridsuite/study/server/service/pccmin/PccMinRestService.java +++ b/src/main/java/org/gridsuite/study/server/service/pccmin/PccMinRestService.java @@ -19,7 +19,6 @@ import org.gridsuite.study.server.utils.ResultParameters; import org.gridsuite.study.server.utils.StudyUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.ParameterizedTypeReference; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.http.*; @@ -288,9 +287,4 @@ public String getParameters(UUID parameterUuid) { String path = UriComponentsBuilder.fromPath(DELIMITER + PCC_MIN_API_VERSION + "/parameters/{parameterUuid}").buildAndExpand(parameterUuid).toUriString(); return restTemplate.getForObject(getBaseUri() + path, String.class); } - - public List getFilterUuids(UUID parameterUuid) { - String path = UriComponentsBuilder.fromPath(DELIMITER + PCC_MIN_API_VERSION + "/parameters/{parameterUuid}/filter-uuids").buildAndExpand(parameterUuid).toUriString(); - return restTemplate.exchange(baseUri + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); - } } diff --git a/src/main/java/org/gridsuite/study/server/service/securityanalysis/SecurityAnalysisRestService.java b/src/main/java/org/gridsuite/study/server/service/securityanalysis/SecurityAnalysisRestService.java index fb0fe0c8e3..54a143c7d9 100644 --- a/src/main/java/org/gridsuite/study/server/service/securityanalysis/SecurityAnalysisRestService.java +++ b/src/main/java/org/gridsuite/study/server/service/securityanalysis/SecurityAnalysisRestService.java @@ -19,7 +19,6 @@ import org.gridsuite.study.server.service.common.AbstractComputationRestService; import org.gridsuite.study.server.service.common.ComputationParameters; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.ParameterizedTypeReference; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.http.*; @@ -308,11 +307,6 @@ public String getParameters(UUID parameterUuid) { return restTemplate.getForObject(getBaseUri() + path, String.class); } - public List getContingencyListUuids(UUID parameterUuid) { - String path = UriComponentsBuilder.fromPath(DELIMITER + SECURITY_ANALYSIS_API_VERSION + "/parameters/{parameterUuid}/contingency-list-uuids").buildAndExpand(parameterUuid).toUriString(); - return restTemplate.exchange(getBaseUri() + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); - } - public void updateParameters(UUID parameterUuid, @Nullable String parameters) { String path = UriComponentsBuilder.fromPath(DELIMITER + SECURITY_ANALYSIS_API_VERSION + "/parameters/{parameterUuid}").buildAndExpand(parameterUuid).toUriString(); HttpHeaders headers = new HttpHeaders(); diff --git a/src/main/java/org/gridsuite/study/server/service/sensitivityanalysis/SensitivityAnalysisRestService.java b/src/main/java/org/gridsuite/study/server/service/sensitivityanalysis/SensitivityAnalysisRestService.java index d4a9836ba7..a2e7356c2d 100644 --- a/src/main/java/org/gridsuite/study/server/service/sensitivityanalysis/SensitivityAnalysisRestService.java +++ b/src/main/java/org/gridsuite/study/server/service/sensitivityanalysis/SensitivityAnalysisRestService.java @@ -361,16 +361,6 @@ public String getParameters(UUID parameterUuid) { return getRestTemplate().getForObject(getBaseUri() + path, String.class); } - public List getFilterUuids(UUID parameterUuid) { - String path = UriComponentsBuilder.fromPath(DELIMITER + SENSITIVITY_ANALYSIS_API_VERSION + "/parameters/{parameterUuid}/filter-uuids").buildAndExpand(parameterUuid).toUriString(); - return getRestTemplate().exchange(getBaseUri() + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); - } - - public List getContingencyListUuids(UUID parameterUuid) { - String path = UriComponentsBuilder.fromPath(DELIMITER + SENSITIVITY_ANALYSIS_API_VERSION + "/parameters/{parameterUuid}/contingency-list-uuids").buildAndExpand(parameterUuid).toUriString(); - return getRestTemplate().exchange(getBaseUri() + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); - } - public void updateParameters(UUID parameterUuid, @Nullable String parameters) { String path = UriComponentsBuilder.fromPath(DELIMITER + SENSITIVITY_ANALYSIS_API_VERSION + "/parameters/{parameterUuid}").buildAndExpand(parameterUuid).toUriString(); HttpHeaders headers = new HttpHeaders(); diff --git a/src/main/java/org/gridsuite/study/server/service/voltageinit/VoltageInitRestService.java b/src/main/java/org/gridsuite/study/server/service/voltageinit/VoltageInitRestService.java index adfcca8882..c479a40597 100644 --- a/src/main/java/org/gridsuite/study/server/service/voltageinit/VoltageInitRestService.java +++ b/src/main/java/org/gridsuite/study/server/service/voltageinit/VoltageInitRestService.java @@ -19,7 +19,6 @@ import org.gridsuite.study.server.service.StudyService; import org.gridsuite.study.server.service.common.AbstractComputationRestService; import org.gridsuite.study.server.service.common.ComputationParameters; -import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.Resource; import org.springframework.http.*; import org.springframework.lang.Nullable; @@ -125,11 +124,6 @@ public VoltageInitParametersInfos getVoltageInitParameters(UUID parametersUuid) return restTemplate.getForObject(baseUri + path, VoltageInitParametersInfos.class); } - public List getFilterUuids(UUID parametersUuid) { - String path = UriComponentsBuilder.fromPath(DELIMITER + VOLTAGE_INIT_API_VERSION + PARAMETERS_URI + "/filter-uuids").buildAndExpand(parametersUuid).toUriString(); - return restTemplate.exchange(baseUri + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); - } - public UUID createVoltageInitParameters(@Nullable VoltageInitParametersInfos parameters) { var path = UriComponentsBuilder .fromPath(DELIMITER + VOLTAGE_INIT_API_VERSION + "/parameters") diff --git a/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferencesTest.java b/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferencesTest.java new file mode 100644 index 0000000000..cb729d1b41 --- /dev/null +++ b/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferencesTest.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.util.Set; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Ghazwa Rehili + */ +class ExportedParametersReferencesTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + private static final UUID ID_1 = UUID.randomUUID(); + private static final UUID ID_2 = UUID.randomUUID(); + private static final UUID ID_3 = UUID.randomUUID(); + private static final UUID ID_4 = UUID.randomUUID(); + private static final UUID ID_5 = UUID.randomUUID(); + + @Test + void testSecurityAnalysisReferences() throws Exception { + String json = "{\"provider\":\"OpenLoadFlow\",\"contingencyListsInfos\":[" + + "{\"contingencyLists\":[\"" + ID_1 + "\",\"" + ID_2 + "\"],\"description\":\"d\",\"activated\":true}," + + "{\"contingencyLists\":[\"" + ID_2 + "\"],\"activated\":false},{\"contingencyLists\":null}]}"; + SecurityAnalysisExportedParameters parameters = objectMapper.readValue(json, SecurityAnalysisExportedParameters.class); + assertEquals(Set.of(ID_1, ID_2), parameters.getContingencyListUuids()); + assertEquals(Set.of(), parameters.getFilterUuids()); + assertEquals(Set.of(), objectMapper.readValue("{}", SecurityAnalysisExportedParameters.class).getContingencyListUuids()); + } + + @Test + void testSensitivityAnalysisReferences() throws Exception { + String json = "{\"provider\":\"OpenLoadFlow\"," + + "\"sensitivityInjectionsSet\":[{\"monitoredBranches\":[\"" + ID_1 + "\"],\"injections\":[\"" + ID_2 + "\"],\"contingencies\":[\"" + ID_3 + "\"],\"activated\":true}]," + + "\"sensitivityInjection\":[{\"monitoredBranches\":[\"" + ID_1 + "\"],\"injections\":[],\"contingencies\":[]}]," + + "\"sensitivityHVDC\":[{\"monitoredBranches\":[],\"hvdcs\":[\"" + ID_4 + "\"],\"contingencies\":[\"" + ID_5 + "\"]}]," + + "\"sensitivityPST\":[{\"monitoredBranches\":[],\"psts\":[\"" + ID_2 + "\"],\"contingencies\":[]}]," + + "\"sensitivityNodes\":[{\"monitoredVoltageLevels\":[\"" + ID_5 + "\"],\"equipmentsInVoltageRegulation\":[\"" + ID_3 + "\"],\"contingencies\":null}]}"; + SensitivityAnalysisExportedParameters parameters = objectMapper.readValue(json, SensitivityAnalysisExportedParameters.class); + assertEquals(Set.of(ID_1, ID_2, ID_3, ID_4, ID_5), parameters.getFilterUuids()); + assertEquals(Set.of(ID_3, ID_5), parameters.getContingencyListUuids()); + } + + @Test + void testVoltageInitReferences() throws Exception { + String json = "{\"voltageLimitsModification\":[{\"priority\":0,\"filters\":[{\"filterId\":\"" + ID_1 + "\",\"filterName\":\"f1\"}]}]," + + "\"voltageLimitsDefault\":[{\"priority\":0,\"filters\":[{\"filterId\":\"" + ID_2 + "\"}]}]," + + "\"variableQGenerators\":[{\"filterId\":\"" + ID_3 + "\"}]," + + "\"variableTwoWindingsTransformers\":[{\"filterId\":\"" + ID_4 + "\"}]," + + "\"variableShuntCompensators\":[{\"filterId\":null}],\"updateBusVoltage\":true}"; + VoltageInitExportedParameters parameters = objectMapper.readValue(json, VoltageInitExportedParameters.class); + assertEquals(Set.of(ID_1, ID_2, ID_3, ID_4), parameters.getFilterUuids()); + assertEquals(Set.of(), parameters.getContingencyListUuids()); + } + + @Test + void testPccMinReferences() throws Exception { + String json = "{\"uuid\":\"" + ID_5 + "\",\"filters\":[\"" + ID_1 + "\"]}"; + PccMinExportedParameters parameters = objectMapper.readValue(json, PccMinExportedParameters.class); + assertEquals(Set.of(ID_1), parameters.getFilterUuids()); + assertEquals(Set.of(), objectMapper.readValue("{}", PccMinExportedParameters.class).getFilterUuids()); + } +} diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java index d47d40c5b3..035e00dd1e 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java @@ -77,7 +77,6 @@ void testExportStudy() throws Exception { .withBody("dummy case content".getBytes()))); // Stub the computation parameters fetches computationServerStubs.stubGetParametersAny("{}"); - computationServerStubs.stubGetReferencedUuidsAny(); // Export as zip MvcResult result = mockMvc.perform(get("/v1/studies/{studyUuid}/export/{studyName}", studyUuid, "studyName").header(HEADER_USER_ID, "testUser")) .andExpect(status().isOk()) @@ -121,7 +120,6 @@ void testExportStudy() throws Exception { wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); // Verify the computation parameters fetches computationServerStubs.verifyParametersGetAny(10); - computationServerStubs.verifyReferencedUuidsGetAny(5); } @Test @@ -139,9 +137,14 @@ void testExportStudyWithFilterAndContingencyListDefinitions() throws Exception { wireMockStubs.directoryServer.stubCheckPermission(List.of(studyUuid), null, "testUser", PermissionType.READ, false, HttpStatus.OK.value()); wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo("/v1/cases/" + CASE_UUID)) .willReturn(WireMock.aResponse().withStatus(200).withHeader("Content-Type", "application/octet-stream").withBody("dummy case content".getBytes()))); - computationServerStubs.stubGetParametersAny("{}"); - stubJsonGet("/v1/parameters/[^/]+/filter-uuids", "[\"" + filterA + "\",\"" + deletedFilter + "\"]"); - stubJsonGet("/v1/parameters/[^/]+/contingency-list-uuids", "[\"" + identifierList + "\",\"" + filterBasedList + "\"]"); + // all computation servers share the same stub: the references are extracted from the security analysis, + // sensitivity analysis, voltage init and pcc min parameters json + computationServerStubs.stubGetParametersAny("{" + + "\"contingencyListsInfos\":[{\"contingencyLists\":[\"" + identifierList + "\"],\"activated\":true}]," + + "\"sensitivityInjectionsSet\":[{\"monitoredBranches\":[\"" + filterA + "\"],\"injections\":[\"" + deletedFilter + "\"]," + + "\"contingencies\":[\"" + filterBasedList + "\"]}]," + + "\"variableQGenerators\":[{\"filterId\":\"" + filterA + "\",\"filterName\":\"nameA\"}]," + + "\"filters\":[\"" + filterA + "\"]}"); String filterAJson = "{\"id\":\"" + filterA + "\",\"type\":\"EXPERT\",\"rules\":{\"dataType\":\"COMBINATOR\",\"rules\":[" + "{\"dataType\":\"FILTER_UUID\",\"field\":\"ID\",\"operator\":\"IS_PART_OF\",\"values\":[\"" + filterC + "\"]}]}}"; String filterBJson = "{\"id\":\"" + filterB + "\",\"type\":\"IDENTIFIER_LIST\"}"; @@ -182,7 +185,6 @@ void testExportStudyWithFilterAndContingencyListDefinitions() throws Exception { WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); computationServerStubs.verifyParametersGetAny(10); - computationServerStubs.verifyReferencedUuidsGetAny(5); WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/contingency-lists/filter-uuids", false, Map.of("ids", WireMock.matching(".*")), 1); WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters/referenced-filter-uuids", false, Map.of("ids", WireMock.matching(".*")), 1); WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters/metadata", false, Map.of("ids", WireMock.matching(".*")), 1); @@ -222,7 +224,6 @@ void testExportStudyFailToDeleteTempZipFile() throws Exception { .willReturn(WireMock.aResponse().withStatus(200).withHeader("Content-Type", "application/octet-stream") .withBody("dummy case content".getBytes()))); computationServerStubs.stubGetParametersAny("{}"); - computationServerStubs.stubGetReferencedUuidsAny(); // Capture the real zip file path as it is matched, so the test can clean it up itself: // the service's own Files.deleteIfExists call on this path is mocked to fail below. AtomicReference capturedZipFile = new AtomicReference<>(); @@ -255,6 +256,5 @@ void testExportStudyFailToDeleteTempZipFile() throws Exception { WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); computationServerStubs.verifyParametersGetAny(10); - computationServerStubs.verifyReferencedUuidsGetAny(5); } } diff --git a/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java b/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java index 48b3179b45..e0291cbfc6 100644 --- a/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java +++ b/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java @@ -207,15 +207,6 @@ public void stubGetParametersAny(String responseBody) { .willReturn(WireMock.ok().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).withBody(responseBody))); } - public void stubGetReferencedUuidsAny() { - wireMock.stubFor(WireMock.get(WireMock.urlPathMatching("/v1/parameters/[^/]+/(filter|contingency-list)-uuids")) - .willReturn(WireMock.ok().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).withBody("[]"))); - } - - public void verifyReferencedUuidsGetAny(int nbRequests) { - WireMockUtilsCriteria.verifyGetRequest(wireMock, "/v1/parameters/[^/]+/(filter|contingency-list)-uuids", true, Map.of(), nbRequests); - } - public void verifyParametersGetAny(int nbRequests) { WireMockUtilsCriteria.verifyGetRequest(wireMock, "/v1/parameters/[^/]+", true, Map.of(), nbRequests); } From 49f837c454b9ac4d1a77eb9abd7d74d7fc201115 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Fri, 25 Sep 2026 14:24:43 +0200 Subject: [PATCH 10/18] update TU --- .../server/studycontroller/TreeExportTest.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java index 035e00dd1e..12d8360c84 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java @@ -123,7 +123,7 @@ void testExportStudy() throws Exception { } @Test - void testExportStudyWithFilterAndContingencyListDefinitions() throws Exception { + void testExportStudyWithFilterAndContingencyList() throws Exception { UUID studyUuid = createStudyWithStubs("testUser", CASE_UUID); ReflectionTestUtils.setField(caseService, "caseServerBaseUri", wireMockServer.baseUrl()); filterService.setBaseUri(wireMockServer.baseUrl()); @@ -137,8 +137,6 @@ void testExportStudyWithFilterAndContingencyListDefinitions() throws Exception { wireMockStubs.directoryServer.stubCheckPermission(List.of(studyUuid), null, "testUser", PermissionType.READ, false, HttpStatus.OK.value()); wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo("/v1/cases/" + CASE_UUID)) .willReturn(WireMock.aResponse().withStatus(200).withHeader("Content-Type", "application/octet-stream").withBody("dummy case content".getBytes()))); - // all computation servers share the same stub: the references are extracted from the security analysis, - // sensitivity analysis, voltage init and pcc min parameters json computationServerStubs.stubGetParametersAny("{" + "\"contingencyListsInfos\":[{\"contingencyLists\":[\"" + identifierList + "\"],\"activated\":true}]," + "\"sensitivityInjectionsSet\":[{\"monitoredBranches\":[\"" + filterA + "\"],\"injections\":[\"" + deletedFilter + "\"]," @@ -179,8 +177,8 @@ void testExportStudyWithFilterAndContingencyListDefinitions() throws Exception { Set expectedContingencyLists = Set.of( objectMapper.readTree("{\"uuid\":\"" + identifierList + "\",\"name\":\"nameI\",\"content\":" + identifierListJson + "}"), objectMapper.readTree("{\"uuid\":\"" + filterBasedList + "\",\"name\":\"nameF\",\"content\":" + filterBasedListJson + "}")); - assertEquals(expectedFilters, readDefinitions(zipContents.get("computationParameters/filters.json"))); - assertEquals(expectedContingencyLists, readDefinitions(zipContents.get("computationParameters/contingencyList.json"))); + assertEquals(expectedFilters, readJson(zipContents.get("computationParameters/filters.json"))); + assertEquals(expectedContingencyLists, readJson(zipContents.get("computationParameters/contingencyList.json"))); WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); @@ -197,10 +195,10 @@ private void stubJsonGet(String urlRegex, String body) { .willReturn(WireMock.ok().withHeader("Content-Type", "application/json").withBody(body))); } - private Set readDefinitions(String json) throws IOException { - Set definitions = new HashSet<>(); - objectMapper.readTree(json).forEach(definitions::add); - return definitions; + private Set readJson(String json) throws IOException { + Set jsonNodes = new HashSet<>(); + objectMapper.readTree(json).forEach(jsonNodes::add); + return jsonNodes; } @Test From 8ed67c1cc0f289d49233411fd63e3a2ae92b0aaa Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Fri, 25 Sep 2026 14:58:42 +0200 Subject: [PATCH 11/18] rename to ExportedElementInfos --- .../{ExportedElement.java => ExportedElementInfos.java} | 6 +++--- .../gridsuite/study/server/service/StudyExportService.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/{ExportedElement.java => ExportedElementInfos.java} (70%) diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElement.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElementInfos.java similarity index 70% rename from src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElement.java rename to src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElementInfos.java index 6e404b9caa..ae32492191 100644 --- a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElement.java +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElementInfos.java @@ -14,10 +14,10 @@ /** * @author Ghazwa Rehili */ -public record ExportedElement(UUID uuid, String name, JsonNode content) { +public record ExportedElementInfos(UUID uuid, String name, JsonNode content) { - public static ExportedElement of(JsonNode content, Map names) { + public static ExportedElementInfos of(JsonNode content, Map names) { UUID uuid = UUID.fromString(content.get("id").asText()); - return new ExportedElement(uuid, names.get(uuid), content); + return new ExportedElementInfos(uuid, names.get(uuid), content); } } diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index 52d4811f1e..6aadf7d026 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -152,7 +152,7 @@ private void exportParameters(UUID studyUuid, String userId, Path tempDir) throw FILTERS_JSON, filterUuids.isEmpty() ? "[]" : filterService.getFilters(filterUuids)).entrySet()) { objectMapper.writeValue(parametersDir.resolve(contents.getKey()).toFile(), StreamSupport.stream(objectMapper.readTree(contents.getValue()).spliterator(), false) - .map(content -> ExportedElement.of(content, names)) + .map(content -> ExportedElementInfos.of(content, names)) .toList()); } } From 42148733379a15b0e5158970120e064b6edaa429 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 29 Sep 2026 15:33:42 +0200 Subject: [PATCH 12/18] add export filteruuid short circuit ans sensi node --- ...SensitivityAnalysisExportedParameters.java | 24 +++++-- .../ShortCircuitExportedParameters.java | 64 +++++++++++++++++++ .../server/service/StudyExportService.java | 12 ++-- .../common/ComputationParametersService.java | 30 ++++----- .../ExportedParametersReferencesTest.java | 24 +++++++ .../studycontroller/TreeExportTest.java | 6 +- 6 files changed, 132 insertions(+), 28 deletions(-) create mode 100644 src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ShortCircuitExportedParameters.java diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java index 49cefd081a..935af4ed15 100644 --- a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java @@ -25,7 +25,7 @@ public record SensitivityAnalysisExportedParameters( List sensitivityInjection, List sensitivityHVDC, List sensitivityPST, - List sensitivityNodes + List sensitivityNodes ) implements ExportedParametersReferences { @JsonIgnoreProperties(ignoreUnknown = true) public record SensitivityFactor( @@ -33,28 +33,42 @@ public record SensitivityFactor( List injections, List hvdcs, List psts, + List contingencies + ) { + Stream filterUuids() { + return Stream.of(monitoredBranches, injections, hvdcs, psts) + .flatMap(ExportedParametersReferences::nullSafe); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record SensitivityNodes( List monitoredVoltageLevels, List equipmentsInVoltageRegulation, List contingencies ) { Stream filterUuids() { - return Stream.of(monitoredBranches, injections, hvdcs, psts, monitoredVoltageLevels, equipmentsInVoltageRegulation) + return Stream.of(monitoredVoltageLevels, equipmentsInVoltageRegulation) .flatMap(ExportedParametersReferences::nullSafe); } } private Stream allFactors() { - return Stream.of(sensitivityInjectionsSet, sensitivityInjection, sensitivityHVDC, sensitivityPST, sensitivityNodes) + return Stream.of(sensitivityInjectionsSet, sensitivityInjection, sensitivityHVDC, sensitivityPST) .flatMap(ExportedParametersReferences::nullSafe); } @Override public Set getFilterUuids() { - return toUuidSet(allFactors().flatMap(SensitivityFactor::filterUuids)); + return toUuidSet(Stream.concat( + allFactors().flatMap(SensitivityFactor::filterUuids), + nullSafe(sensitivityNodes).flatMap(SensitivityNodes::filterUuids))); } @Override public Set getContingencyListUuids() { - return toUuidSet(allFactors().flatMap(factor -> nullSafe(factor.contingencies()))); + return toUuidSet(Stream.concat( + allFactors().flatMap(factor -> nullSafe(factor.contingencies())), + nullSafe(sensitivityNodes).flatMap(nodes -> nullSafe(nodes.contingencies())))); } } diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ShortCircuitExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ShortCircuitExportedParameters.java new file mode 100644 index 0000000000..d8fdf8ad26 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ShortCircuitExportedParameters.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; + +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.toUuidSet; + +/** + * @author Ghazwa Rehili + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record ShortCircuitExportedParameters(Map> specificParametersPerProvider) implements ExportedParametersReferences { + + public static final String POWER_ELECTRONICS_CLUSTERS = "powerElectronicsClusters"; + public static final String NODE_CLUSTER_FILTER_IDS = "nodeClusterFilterIds"; + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + @JsonIgnoreProperties(ignoreUnknown = true) + public record FilterElements(UUID filterId) { } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record PowerElectronicsCluster(List filters) { } + + @Override + public Set getFilterUuids() { + Stream> specificParameters = specificParametersPerProvider == null ? Stream.empty() + : specificParametersPerProvider.values().stream().filter(Objects::nonNull); + return toUuidSet(specificParameters.flatMap(parameters -> Stream.concat( + parseList(parameters.get(POWER_ELECTRONICS_CLUSTERS), new TypeReference>() { }) + .flatMap(cluster -> nullSafe(cluster.filters())), + parseList(parameters.get(NODE_CLUSTER_FILTER_IDS), new TypeReference>() { }))) + .map(FilterElements::filterId)); + } + + private static Stream parseList(String json, TypeReference> type) { + if (StringUtils.isBlank(json)) { + return Stream.empty(); + } + try { + return nullSafe(OBJECT_MAPPER.readValue(json, type)).filter(Objects::nonNull); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index 6aadf7d026..34489c0b63 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -86,7 +86,7 @@ public InputStreamResource exportStudy(UUID studyUuid, String userId) { Path tempDir = createTempWorkDir(studyUuid); Path zipFile = null; try { - zipFile = compressStudyToZip(studyUuid, userId, tempDir); + zipFile = compressStudyToZip(studyUuid, tempDir); InputStream stream = Files.newInputStream(zipFile, StandardOpenOption.DELETE_ON_CLOSE); zipFile = null; return new InputStreamResource(stream); @@ -111,7 +111,7 @@ public InputStreamResource exportStudy(UUID studyUuid, String userId) { /** * Build tree.json and the case files under tempDir, then compress them into a temp zip file */ - private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) throws IOException { + private Path compressStudyToZip(UUID studyUuid, Path tempDir) throws IOException { TreeExportInfos treeExportInfos = studyService.buildTreeExport(studyUuid); Path studyJsonPath = tempDir.resolve(TREE_JSON_FILE_NAME); objectMapper.writerWithDefaultPrettyPrinter().writeValue(studyJsonPath.toFile(), treeExportInfos); @@ -121,7 +121,7 @@ private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) thr String caseName = rootNetworkInfos.caseInfos().getCaseName(); exportCaseFile(caseUuid, caseName, casesDir); } - exportParameters(studyUuid, userId, tempDir); + exportParameters(studyUuid, tempDir); Path zipFile = createTempExportFile(studyUuid); try (OutputStream fos = Files.newOutputStream(zipFile); ZipOutputStream zipOut = new ZipOutputStream(fos)) { @@ -130,11 +130,11 @@ private Path compressStudyToZip(UUID studyUuid, String userId, Path tempDir) thr return zipFile; } - private void exportParameters(UUID studyUuid, String userId, Path tempDir) throws IOException { + private void exportParameters(UUID studyUuid, Path tempDir) throws IOException { Path parametersDir = Files.createDirectories(tempDir.resolve(PARAMETERS_FOLDER)); Set filterUuids = new HashSet<>(); Set contingencyListUuids = new HashSet<>(); - for (Map.Entry parameters : computationParametersService.exportParameters(studyService.getStudy(studyUuid), userId).entrySet()) { + for (Map.Entry parameters : computationParametersService.exportParameters(studyService.getStudy(studyUuid)).entrySet()) { Files.writeString(parametersDir.resolve(parameters.getKey().name() + ".json"), parameters.getValue()); ExportedParametersReferences references = readParametersReferences(parameters.getKey(), parameters.getValue()); filterUuids.addAll(references.getFilterUuids()); @@ -160,9 +160,11 @@ private void exportParameters(UUID studyUuid, String userId, Path tempDir) throw private ExportedParametersReferences readParametersReferences(ComputationType type, String parametersJson) throws IOException { Class referencesClass = switch (type) { case SECURITY_ANALYSIS -> SecurityAnalysisExportedParameters.class; + case SHORT_CIRCUIT -> ShortCircuitExportedParameters.class; case SENSITIVITY_ANALYSIS -> SensitivityAnalysisExportedParameters.class; case VOLTAGE_INITIALIZATION -> VoltageInitExportedParameters.class; case PCC_MIN -> PccMinExportedParameters.class; + // LOAD_FLOW: the load flow parameters do not reference any filter or contingency list default -> null; }; return referencesClass == null ? new ExportedParametersReferences() { } : objectMapper.readValue(parametersJson, referencesClass); diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index 7d9af8b633..b010bf5d2d 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -32,7 +32,6 @@ import java.util.Map; import java.util.UUID; import java.util.function.BiConsumer; -import java.util.function.BiFunction; import java.util.function.Function; /** @@ -55,7 +54,8 @@ private record ComputationParametersDefinition( Function profileParameterGetter, ComputationParameters service, BiConsumer parametersSetter, - BiFunction parametersFetcher + // null for the computations whose parameters are not exported + Function parametersFetcher ) { } @@ -81,70 +81,70 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis UserProfileInfos::getLoadFlowParameterId, loadFlowRestService, ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::loadFlowParametersUuid, - (uuid, userId) -> loadFlowRestService.getParameters(uuid)), + loadFlowRestService::getParameters), new ComputationParametersDefinition( ComputationType.SHORT_CIRCUIT, StudyEntity::getShortCircuitParametersUuid, UserProfileInfos::getShortcircuitParameterId, shortCircuitService, ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::shortCircuitParametersUuid, - (uuid, userId) -> shortCircuitService.getParameters(uuid)), + shortCircuitService::getParameters), new ComputationParametersDefinition( ComputationType.DYNAMIC_SIMULATION, StudyEntity::getDynamicSimulationParametersUuid, UserProfileInfos::getDynamicSimulationParameterId, dynamicSimulationRestService, ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicSimulationParametersUuid, - (uuid, userId) -> dynamicSimulationRestService.getParameters(uuid)), + null), new ComputationParametersDefinition( ComputationType.VOLTAGE_INITIALIZATION, StudyEntity::getVoltageInitParametersUuid, UserProfileInfos::getVoltageInitParameterId, voltageInitService, ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::voltageInitParametersUuid, - (uuid, userId) -> voltageInitService.getParameters(uuid)), + voltageInitService::getParameters), new ComputationParametersDefinition( ComputationType.SECURITY_ANALYSIS, StudyEntity::getSecurityAnalysisParametersUuid, UserProfileInfos::getSecurityAnalysisParameterId, securityAnalysisService, ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::securityAnalysisParametersUuid, - (uuid, userId) -> securityAnalysisService.getParameters(uuid)), + securityAnalysisService::getParameters), new ComputationParametersDefinition( ComputationType.SENSITIVITY_ANALYSIS, StudyEntity::getSensitivityAnalysisParametersUuid, UserProfileInfos::getSensitivityAnalysisParameterId, sensitivityAnalysisService, ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::sensitivityAnalysisParametersUuid, - (uuid, userId) -> sensitivityAnalysisService.getParameters(uuid)), + sensitivityAnalysisService::getParameters), new ComputationParametersDefinition( ComputationType.DYNAMIC_SECURITY_ANALYSIS, StudyEntity::getDynamicSecurityAnalysisParametersUuid, UserProfileInfos::getDynamicSecurityAnalysisParameterId, dynamicSecurityAnalysisRestService, ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicSecurityAnalysisParametersUuid, - (uuid, userId) -> dynamicSecurityAnalysisRestService.getParameters(uuid)), + null), new ComputationParametersDefinition( ComputationType.DYNAMIC_MARGIN_CALCULATION, StudyEntity::getDynamicMarginCalculationParametersUuid, UserProfileInfos::getDynamicMarginCalculationParameterId, dynamicMarginCalculationRestService, ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicMarginCalculationParametersUuid, - dynamicMarginCalculationRestService::getParameters), + null), new ComputationParametersDefinition( ComputationType.STATE_ESTIMATION, StudyEntity::getStateEstimationParametersUuid, userProfileInfos -> null, stateEstimationService, ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::stateEstimationParametersUuid, - (uuid, userId) -> stateEstimationService.getStateEstimationParameters(uuid)), + null), new ComputationParametersDefinition( ComputationType.PCC_MIN, StudyEntity::getPccMinParametersUuid, UserProfileInfos::getPccMinParameterId, pccMinService, ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::pccMinParametersUuid, - (uuid, userId) -> pccMinService.getParameters(uuid)) + pccMinService::getParameters) ); } @@ -239,14 +239,14 @@ public void createOrUpdateParameters( } } - public Map exportParameters(StudyEntity studyEntity, String userId) throws JsonProcessingException { + public Map exportParameters(StudyEntity studyEntity) throws JsonProcessingException { Map parametersByType = new EnumMap<>(ComputationType.class); for (ComputationParametersDefinition definition : computationParametersDefinitions) { UUID parametersUuid = definition.studyParameterGetter().apply(studyEntity); - if (parametersUuid == null) { + if (parametersUuid == null || definition.parametersFetcher() == null) { continue; } - Object parameters = definition.parametersFetcher().apply(parametersUuid, userId); + Object parameters = definition.parametersFetcher().apply(parametersUuid); parametersByType.put(definition.type(), parameters instanceof String parametersJson ? parametersJson : objectMapper.writeValueAsString(parameters)); } return parametersByType; diff --git a/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferencesTest.java b/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferencesTest.java index cb729d1b41..6c35241f16 100644 --- a/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferencesTest.java +++ b/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferencesTest.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import java.util.Map; import java.util.Set; import java.util.UUID; @@ -51,6 +52,29 @@ void testSensitivityAnalysisReferences() throws Exception { assertEquals(Set.of(ID_3, ID_5), parameters.getContingencyListUuids()); } + @Test + void testSensitivityAnalysisNodesReferences() throws Exception { + String json = "{\"sensitivityNodes\":[{\"monitoredVoltageLevels\":[\"" + ID_1 + "\"],\"equipmentsInVoltageRegulation\":[\"" + ID_2 + "\"]," + + "\"contingencies\":[\"" + ID_3 + "\"],\"activated\":true},{\"monitoredVoltageLevels\":null}]}"; + SensitivityAnalysisExportedParameters parameters = objectMapper.readValue(json, SensitivityAnalysisExportedParameters.class); + assertEquals(Set.of(ID_1, ID_2), parameters.getFilterUuids()); + assertEquals(Set.of(ID_3), parameters.getContingencyListUuids()); + } + + @Test + void testShortCircuitReferences() throws Exception { + String clusters = "[{\"alpha\":0.9,\"filters\":[{\"filterId\":\"" + ID_1 + "\",\"filterName\":\"f1\"},{\"filterId\":\"" + ID_2 + "\"}],\"active\":true}," + + "{\"filters\":null}]"; + String nodeCluster = "[{\"filterId\":\"" + ID_3 + "\",\"filterName\":\"f3\"}]"; + String json = objectMapper.writeValueAsString(Map.of("provider", "Courcirc", "specificParametersPerProvider", Map.of( + "Courcirc", Map.of("powerElectronicsClusters", clusters, "nodeClusterFilterIds", nodeCluster, "other", "value"), + "OtherProvider", Map.of("nodeClusterFilterIds", "")))); + ShortCircuitExportedParameters parameters = objectMapper.readValue(json, ShortCircuitExportedParameters.class); + assertEquals(Set.of(ID_1, ID_2, ID_3), parameters.getFilterUuids()); + assertEquals(Set.of(), parameters.getContingencyListUuids()); + assertEquals(Set.of(), objectMapper.readValue("{}", ShortCircuitExportedParameters.class).getFilterUuids()); + } + @Test void testVoltageInitReferences() throws Exception { String json = "{\"voltageLimitsModification\":[{\"priority\":0,\"filters\":[{\"filterId\":\"" + ID_1 + "\",\"filterName\":\"f1\"}]}]," diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java index 12d8360c84..e75fbffc1b 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java @@ -119,7 +119,7 @@ void testExportStudy() throws Exception { WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); // Verify the computation parameters fetches - computationServerStubs.verifyParametersGetAny(10); + computationServerStubs.verifyParametersGetAny(6); } @Test @@ -182,7 +182,7 @@ void testExportStudyWithFilterAndContingencyList() throws Exception { WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); - computationServerStubs.verifyParametersGetAny(10); + computationServerStubs.verifyParametersGetAny(6); WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/contingency-lists/filter-uuids", false, Map.of("ids", WireMock.matching(".*")), 1); WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters/referenced-filter-uuids", false, Map.of("ids", WireMock.matching(".*")), 1); WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters/metadata", false, Map.of("ids", WireMock.matching(".*")), 1); @@ -253,6 +253,6 @@ void testExportStudyFailToDeleteTempZipFile() throws Exception { } WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); - computationServerStubs.verifyParametersGetAny(10); + computationServerStubs.verifyParametersGetAny(6); } } From 6a02f2e75689c11849d91de7c9f1c5c7f23bb142 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Thu, 1 Oct 2026 11:02:08 +0200 Subject: [PATCH 13/18] code review remarks --- .../parameters/ExportedElementInfos.java | 9 +---- ...eferences.java => ExportedParameters.java} | 2 +- .../parameters/PccMinExportedParameters.java | 6 ++-- .../SecurityAnalysisExportedParameters.java | 6 ++-- ...SensitivityAnalysisExportedParameters.java | 12 +++---- .../ShortCircuitExportedParameters.java | 6 ++-- .../VoltageInitExportedParameters.java | 8 ++--- .../server/service/StudyExportService.java | 34 +++++++++++-------- ...sTest.java => ExportedParametersTest.java} | 2 +- 9 files changed, 42 insertions(+), 43 deletions(-) rename src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/{ExportedParametersReferences.java => ExportedParameters.java} (95%) rename src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/{ExportedParametersReferencesTest.java => ExportedParametersTest.java} (99%) diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElementInfos.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElementInfos.java index ae32492191..96356e9994 100644 --- a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElementInfos.java +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElementInfos.java @@ -8,16 +8,9 @@ import com.fasterxml.jackson.databind.JsonNode; -import java.util.Map; import java.util.UUID; /** * @author Ghazwa Rehili */ -public record ExportedElementInfos(UUID uuid, String name, JsonNode content) { - - public static ExportedElementInfos of(JsonNode content, Map names) { - UUID uuid = UUID.fromString(content.get("id").asText()); - return new ExportedElementInfos(uuid, names.get(uuid), content); - } -} +public record ExportedElementInfos(UUID uuid, String name, JsonNode content) { } diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferences.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParameters.java similarity index 95% rename from src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferences.java rename to src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParameters.java index 002e8b08fc..3cbddefb37 100644 --- a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferences.java +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParameters.java @@ -16,7 +16,7 @@ /** * @author Ghazwa Rehili */ -public interface ExportedParametersReferences { +public interface ExportedParameters { default Set getFilterUuids() { return Set.of(); diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/PccMinExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/PccMinExportedParameters.java index 888d6a4c18..e129998f99 100644 --- a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/PccMinExportedParameters.java +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/PccMinExportedParameters.java @@ -12,14 +12,14 @@ import java.util.Set; import java.util.UUID; -import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.nullSafe; -import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.toUuidSet; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.toUuidSet; /** * @author Ghazwa Rehili */ @JsonIgnoreProperties(ignoreUnknown = true) -public record PccMinExportedParameters(List filters) implements ExportedParametersReferences { +public record PccMinExportedParameters(List filters) implements ExportedParameters { @Override public Set getFilterUuids() { diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SecurityAnalysisExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SecurityAnalysisExportedParameters.java index c4827d752d..aad2268a09 100644 --- a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SecurityAnalysisExportedParameters.java +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SecurityAnalysisExportedParameters.java @@ -12,14 +12,14 @@ import java.util.Set; import java.util.UUID; -import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.nullSafe; -import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.toUuidSet; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.toUuidSet; /** * @author Ghazwa Rehili */ @JsonIgnoreProperties(ignoreUnknown = true) -public record SecurityAnalysisExportedParameters(List contingencyListsInfos) implements ExportedParametersReferences { +public record SecurityAnalysisExportedParameters(List contingencyListsInfos) implements ExportedParameters { @JsonIgnoreProperties(ignoreUnknown = true) public record ContingencyListsInfos(List contingencyLists) { } diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java index 935af4ed15..0758cd1571 100644 --- a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java @@ -13,8 +13,8 @@ import java.util.UUID; import java.util.stream.Stream; -import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.nullSafe; -import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.toUuidSet; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.toUuidSet; /** * @author Ghazwa Rehili @@ -26,7 +26,7 @@ public record SensitivityAnalysisExportedParameters( List sensitivityHVDC, List sensitivityPST, List sensitivityNodes -) implements ExportedParametersReferences { +) implements ExportedParameters { @JsonIgnoreProperties(ignoreUnknown = true) public record SensitivityFactor( List monitoredBranches, @@ -37,7 +37,7 @@ public record SensitivityFactor( ) { Stream filterUuids() { return Stream.of(monitoredBranches, injections, hvdcs, psts) - .flatMap(ExportedParametersReferences::nullSafe); + .flatMap(ExportedParameters::nullSafe); } } @@ -49,13 +49,13 @@ public record SensitivityNodes( ) { Stream filterUuids() { return Stream.of(monitoredVoltageLevels, equipmentsInVoltageRegulation) - .flatMap(ExportedParametersReferences::nullSafe); + .flatMap(ExportedParameters::nullSafe); } } private Stream allFactors() { return Stream.of(sensitivityInjectionsSet, sensitivityInjection, sensitivityHVDC, sensitivityPST) - .flatMap(ExportedParametersReferences::nullSafe); + .flatMap(ExportedParameters::nullSafe); } @Override diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ShortCircuitExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ShortCircuitExportedParameters.java index d8fdf8ad26..b2cc584635 100644 --- a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ShortCircuitExportedParameters.java +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ShortCircuitExportedParameters.java @@ -20,14 +20,14 @@ import java.util.UUID; import java.util.stream.Stream; -import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.nullSafe; -import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.toUuidSet; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.toUuidSet; /** * @author Ghazwa Rehili */ @JsonIgnoreProperties(ignoreUnknown = true) -public record ShortCircuitExportedParameters(Map> specificParametersPerProvider) implements ExportedParametersReferences { +public record ShortCircuitExportedParameters(Map> specificParametersPerProvider) implements ExportedParameters { public static final String POWER_ELECTRONICS_CLUSTERS = "powerElectronicsClusters"; public static final String NODE_CLUSTER_FILTER_IDS = "nodeClusterFilterIds"; diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/VoltageInitExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/VoltageInitExportedParameters.java index 19b54ef41c..7de9ca7b7f 100644 --- a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/VoltageInitExportedParameters.java +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/VoltageInitExportedParameters.java @@ -13,8 +13,8 @@ import java.util.UUID; import java.util.stream.Stream; -import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.nullSafe; -import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParametersReferences.toUuidSet; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.toUuidSet; /** * @author Ghazwa Rehili @@ -26,7 +26,7 @@ public record VoltageInitExportedParameters( List variableQGenerators, List variableTwoWindingsTransformers, List variableShuntCompensators -) implements ExportedParametersReferences { +) implements ExportedParameters { @JsonIgnoreProperties(ignoreUnknown = true) public record VoltageLimit(List filters) { } @@ -39,7 +39,7 @@ public Set getFilterUuids() { Stream limitFilters = Stream.concat(nullSafe(voltageLimitsModification), nullSafe(voltageLimitsDefault)) .flatMap(voltageLimit -> nullSafe(voltageLimit.filters())); Stream variableFilters = Stream.of(variableQGenerators, variableTwoWindingsTransformers, variableShuntCompensators) - .flatMap(ExportedParametersReferences::nullSafe); + .flatMap(ExportedParameters::nullSafe); return toUuidSet(Stream.concat(limitFilters, variableFilters).map(FilterEquipments::filterId)); } } diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index 34489c0b63..069b97b3cd 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -6,6 +6,7 @@ */ package org.gridsuite.study.server.service; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.gridsuite.study.server.dto.ComputationType; import org.gridsuite.study.server.dto.networkexport.PermissionType; @@ -28,6 +29,7 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; +import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; @@ -36,7 +38,6 @@ import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.stream.StreamSupport; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -136,9 +137,9 @@ private void exportParameters(UUID studyUuid, Path tempDir) throws IOException { Set contingencyListUuids = new HashSet<>(); for (Map.Entry parameters : computationParametersService.exportParameters(studyService.getStudy(studyUuid)).entrySet()) { Files.writeString(parametersDir.resolve(parameters.getKey().name() + ".json"), parameters.getValue()); - ExportedParametersReferences references = readParametersReferences(parameters.getKey(), parameters.getValue()); - filterUuids.addAll(references.getFilterUuids()); - contingencyListUuids.addAll(references.getContingencyListUuids()); + ExportedParameters exportedParameters = readParametersReferences(parameters.getKey(), parameters.getValue()); + filterUuids.addAll(exportedParameters.getFilterUuids()); + contingencyListUuids.addAll(exportedParameters.getContingencyListUuids()); } if (!contingencyListUuids.isEmpty()) { filterUuids.addAll(actionsService.getReferencedFilterUuids(contingencyListUuids)); @@ -147,18 +148,23 @@ private void exportParameters(UUID studyUuid, Path tempDir) throws IOException { filterUuids.addAll(filterService.getReferencedFilterUuids(filterUuids)); } Map names = directoryService.getElementNames(Stream.concat(filterUuids.stream(), contingencyListUuids.stream()).collect(Collectors.toSet())); - for (Map.Entry contents : Map.of( - CONTINGENCY_LIST_JSON, contingencyListUuids.isEmpty() ? "[]" : actionsService.getContingencyLists(contingencyListUuids), - FILTERS_JSON, filterUuids.isEmpty() ? "[]" : filterService.getFilters(filterUuids)).entrySet()) { - objectMapper.writeValue(parametersDir.resolve(contents.getKey()).toFile(), - StreamSupport.stream(objectMapper.readTree(contents.getValue()).spliterator(), false) - .map(content -> ExportedElementInfos.of(content, names)) - .toList()); + List contingencyLists = contingencyListUuids.isEmpty() ? List.of() : toExportedElements(actionsService.getContingencyLists(contingencyListUuids), names); + List filters = filterUuids.isEmpty() ? List.of() : toExportedElements(filterService.getFilters(filterUuids), names); + objectMapper.writeValue(parametersDir.resolve(CONTINGENCY_LIST_JSON).toFile(), contingencyLists); + objectMapper.writeValue(parametersDir.resolve(FILTERS_JSON).toFile(), filters); + } + + private List toExportedElements(String content, Map names) throws IOException { + List elements = new ArrayList<>(); + for (JsonNode jsonNodeContent : objectMapper.readTree(content)) { + UUID uuid = UUID.fromString(jsonNodeContent.get("id").asText()); + elements.add(new ExportedElementInfos(uuid, names.get(uuid), jsonNodeContent)); } + return elements; } - private ExportedParametersReferences readParametersReferences(ComputationType type, String parametersJson) throws IOException { - Class referencesClass = switch (type) { + private ExportedParameters readParametersReferences(ComputationType type, String parametersJson) throws IOException { + Class referencesClass = switch (type) { case SECURITY_ANALYSIS -> SecurityAnalysisExportedParameters.class; case SHORT_CIRCUIT -> ShortCircuitExportedParameters.class; case SENSITIVITY_ANALYSIS -> SensitivityAnalysisExportedParameters.class; @@ -167,7 +173,7 @@ private ExportedParametersReferences readParametersReferences(ComputationType ty // LOAD_FLOW: the load flow parameters do not reference any filter or contingency list default -> null; }; - return referencesClass == null ? new ExportedParametersReferences() { } : objectMapper.readValue(parametersJson, referencesClass); + return referencesClass == null ? new ExportedParameters() { } : objectMapper.readValue(parametersJson, referencesClass); } private Path createTempWorkDir(UUID studyUuid) { diff --git a/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferencesTest.java b/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersTest.java similarity index 99% rename from src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferencesTest.java rename to src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersTest.java index 6c35241f16..39c0577262 100644 --- a/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersReferencesTest.java +++ b/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersTest.java @@ -18,7 +18,7 @@ /** * @author Ghazwa Rehili */ -class ExportedParametersReferencesTest { +class ExportedParametersTest { private final ObjectMapper objectMapper = new ObjectMapper(); From cb101c8f667a5af024e2d9df2709480433a4d3cd Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Thu, 1 Oct 2026 11:15:26 +0200 Subject: [PATCH 14/18] clean code --- .../gridsuite/study/server/service/ActionsService.java | 4 ++++ .../gridsuite/study/server/service/FilterService.java | 4 ++++ .../study/server/service/StudyExportService.java | 10 ++++++---- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/ActionsService.java b/src/main/java/org/gridsuite/study/server/service/ActionsService.java index a919f82c8a..08d4608c3a 100644 --- a/src/main/java/org/gridsuite/study/server/service/ActionsService.java +++ b/src/main/java/org/gridsuite/study/server/service/ActionsService.java @@ -35,6 +35,7 @@ public class ActionsService { private static final String NETWORK_UUID = "networkUuid"; private static final String CONTINGENCY_LIST_IDS = "ids"; + private static final String EMPTY_JSON_ARRAY = "[]"; public static final ContingencyCount EMPTY_CONTINGENCY_COUNT = new ContingencyCount(Map.of()); @@ -70,6 +71,9 @@ public List getReferencedFilterUuids(Collection contingencyListUuids } public String getContingencyLists(Collection ids) { + if (ids.isEmpty()) { + return EMPTY_JSON_ARRAY; + } return restTemplate.postForObject(actionsServerBaseUri + DELIMITER + ACTIONS_API_VERSION + "/contingency-lists", ids, String.class); } } diff --git a/src/main/java/org/gridsuite/study/server/service/FilterService.java b/src/main/java/org/gridsuite/study/server/service/FilterService.java index 87e97a9750..4e62e89e74 100644 --- a/src/main/java/org/gridsuite/study/server/service/FilterService.java +++ b/src/main/java/org/gridsuite/study/server/service/FilterService.java @@ -44,6 +44,7 @@ public class FilterService { public static final String FILTER_END_POINT_EVALUATE_IDS = "/filters/evaluate/identifiables"; public static final String FILTER_END_POINT_EXPORT = "/filters/{id}/export"; public static final String FILTERS_END_POINT_EXPORT = "/filters/export"; + private static final String EMPTY_JSON_ARRAY = "[]"; private final RestTemplate restTemplate; @@ -145,6 +146,9 @@ public List getReferencedFilterUuids(Collection filterUuids) { } public String getFilters(Collection filterUuids) { + if (filterUuids.isEmpty()) { + return EMPTY_JSON_ARRAY; + } String path = UriComponentsBuilder.fromPath(DELIMITER + FILTER_API_VERSION + "/filters/metadata") .queryParam(IDS, filterUuids) .toUriString(); diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index 069b97b3cd..953c58d588 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -55,6 +55,8 @@ public class StudyExportService { public static final String PARAMETERS_FOLDER = "computationParameters"; public static final String CONTINGENCY_LIST_JSON = "contingencyList.json"; public static final String FILTERS_JSON = "filters.json"; + public static final String ID = "id"; + public static final String JSON = ".json"; private final StudyService studyService; private final CaseService caseService; @@ -136,7 +138,7 @@ private void exportParameters(UUID studyUuid, Path tempDir) throws IOException { Set filterUuids = new HashSet<>(); Set contingencyListUuids = new HashSet<>(); for (Map.Entry parameters : computationParametersService.exportParameters(studyService.getStudy(studyUuid)).entrySet()) { - Files.writeString(parametersDir.resolve(parameters.getKey().name() + ".json"), parameters.getValue()); + Files.writeString(parametersDir.resolve(parameters.getKey().name() + JSON), parameters.getValue()); ExportedParameters exportedParameters = readParametersReferences(parameters.getKey(), parameters.getValue()); filterUuids.addAll(exportedParameters.getFilterUuids()); contingencyListUuids.addAll(exportedParameters.getContingencyListUuids()); @@ -148,8 +150,8 @@ private void exportParameters(UUID studyUuid, Path tempDir) throws IOException { filterUuids.addAll(filterService.getReferencedFilterUuids(filterUuids)); } Map names = directoryService.getElementNames(Stream.concat(filterUuids.stream(), contingencyListUuids.stream()).collect(Collectors.toSet())); - List contingencyLists = contingencyListUuids.isEmpty() ? List.of() : toExportedElements(actionsService.getContingencyLists(contingencyListUuids), names); - List filters = filterUuids.isEmpty() ? List.of() : toExportedElements(filterService.getFilters(filterUuids), names); + List contingencyLists = toExportedElements(actionsService.getContingencyLists(contingencyListUuids), names); + List filters = toExportedElements(filterService.getFilters(filterUuids), names); objectMapper.writeValue(parametersDir.resolve(CONTINGENCY_LIST_JSON).toFile(), contingencyLists); objectMapper.writeValue(parametersDir.resolve(FILTERS_JSON).toFile(), filters); } @@ -157,7 +159,7 @@ private void exportParameters(UUID studyUuid, Path tempDir) throws IOException { private List toExportedElements(String content, Map names) throws IOException { List elements = new ArrayList<>(); for (JsonNode jsonNodeContent : objectMapper.readTree(content)) { - UUID uuid = UUID.fromString(jsonNodeContent.get("id").asText()); + UUID uuid = UUID.fromString(jsonNodeContent.get(ID).asText()); elements.add(new ExportedElementInfos(uuid, names.get(uuid), jsonNodeContent)); } return elements; From 5afbbaed84338c9aad2f1414cb07cf4f0c21bb6d Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Mon, 5 Oct 2026 16:36:41 +0200 Subject: [PATCH 15/18] add service name to ComputationParametersService --- .../common/ComputationParametersService.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index b010bf5d2d..719f28526d 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -9,6 +9,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.gridsuite.study.server.dto.ComputationType; +import org.gridsuite.study.server.dto.ServiceStatusInfos; +import org.gridsuite.study.server.dto.ServiceStatusInfos.ServiceStatus; import org.gridsuite.study.server.dto.UserProfileInfos; import org.gridsuite.study.server.dto.computation.ComputationParameterUUIDs; import org.gridsuite.study.server.repository.StudyEntity; @@ -30,9 +32,11 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.Function; +import java.util.stream.Collectors; /** * @author Abdelsalem HEDHILI @@ -44,12 +48,14 @@ public class ComputationParametersService { private static final Logger LOGGER = LoggerFactory.getLogger(ComputationParametersService.class); private final UserAdminService userAdminService; + private final RemoteServicesInspector remoteServicesInspector; private final ObjectMapper objectMapper; private final List computationParametersDefinitions; // this is useful to avoid repetitive calls when doing operation on all computation types (duplicate, delete, export) private record ComputationParametersDefinition( ComputationType type, + String serviceName, Function studyParameterGetter, Function profileParameterGetter, ComputationParameters service, @@ -70,13 +76,16 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis StateEstimationRestService stateEstimationService, PccMinRestService pccMinService, UserAdminService userAdminService, + RemoteServicesInspector remoteServicesInspector, ObjectMapper objectMapper) { this.userAdminService = userAdminService; + this.remoteServicesInspector = remoteServicesInspector; this.objectMapper = objectMapper; this.computationParametersDefinitions = List.of( new ComputationParametersDefinition( ComputationType.LOAD_FLOW, + "loadflow-server", StudyEntity::getLoadFlowParametersUuid, UserProfileInfos::getLoadFlowParameterId, loadFlowRestService, @@ -84,6 +93,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis loadFlowRestService::getParameters), new ComputationParametersDefinition( ComputationType.SHORT_CIRCUIT, + "shortcircuit-server", StudyEntity::getShortCircuitParametersUuid, UserProfileInfos::getShortcircuitParameterId, shortCircuitService, @@ -91,6 +101,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis shortCircuitService::getParameters), new ComputationParametersDefinition( ComputationType.DYNAMIC_SIMULATION, + "dynamic-simulation-server", StudyEntity::getDynamicSimulationParametersUuid, UserProfileInfos::getDynamicSimulationParameterId, dynamicSimulationRestService, @@ -98,6 +109,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis null), new ComputationParametersDefinition( ComputationType.VOLTAGE_INITIALIZATION, + "voltage-init-server", StudyEntity::getVoltageInitParametersUuid, UserProfileInfos::getVoltageInitParameterId, voltageInitService, @@ -105,6 +117,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis voltageInitService::getParameters), new ComputationParametersDefinition( ComputationType.SECURITY_ANALYSIS, + "security-analysis-server", StudyEntity::getSecurityAnalysisParametersUuid, UserProfileInfos::getSecurityAnalysisParameterId, securityAnalysisService, @@ -112,6 +125,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis securityAnalysisService::getParameters), new ComputationParametersDefinition( ComputationType.SENSITIVITY_ANALYSIS, + "sensitivity-analysis-server", StudyEntity::getSensitivityAnalysisParametersUuid, UserProfileInfos::getSensitivityAnalysisParameterId, sensitivityAnalysisService, @@ -119,6 +133,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis sensitivityAnalysisService::getParameters), new ComputationParametersDefinition( ComputationType.DYNAMIC_SECURITY_ANALYSIS, + "dynamic-security-analysis-server", StudyEntity::getDynamicSecurityAnalysisParametersUuid, UserProfileInfos::getDynamicSecurityAnalysisParameterId, dynamicSecurityAnalysisRestService, @@ -126,6 +141,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis null), new ComputationParametersDefinition( ComputationType.DYNAMIC_MARGIN_CALCULATION, + "dynamic-margin-calculation-server", StudyEntity::getDynamicMarginCalculationParametersUuid, UserProfileInfos::getDynamicMarginCalculationParameterId, dynamicMarginCalculationRestService, @@ -133,6 +149,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis null), new ComputationParametersDefinition( ComputationType.STATE_ESTIMATION, + "state-estimation-server", StudyEntity::getStateEstimationParametersUuid, userProfileInfos -> null, stateEstimationService, @@ -140,6 +157,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis null), new ComputationParametersDefinition( ComputationType.PCC_MIN, + "pcc-min-server", StudyEntity::getPccMinParametersUuid, UserProfileInfos::getPccMinParameterId, pccMinService, @@ -241,9 +259,16 @@ public void createOrUpdateParameters( public Map exportParameters(StudyEntity studyEntity) throws JsonProcessingException { Map parametersByType = new EnumMap<>(ComputationType.class); + Set downServices = remoteServicesInspector.getOptionalServices().stream() + .filter(serviceStatus -> serviceStatus.status() == ServiceStatus.DOWN) + .map(ServiceStatusInfos::name) + .collect(Collectors.toSet()); for (ComputationParametersDefinition definition : computationParametersDefinitions) { + if (definition.parametersFetcher() == null || downServices.contains(definition.serviceName())) { + continue; + } UUID parametersUuid = definition.studyParameterGetter().apply(studyEntity); - if (parametersUuid == null || definition.parametersFetcher() == null) { + if (parametersUuid == null) { continue; } Object parameters = definition.parametersFetcher().apply(parametersUuid); From 4ee4027a3251f9496b40f3aafd716fcbc2ab18bc Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Mon, 5 Oct 2026 17:05:03 +0200 Subject: [PATCH 16/18] code review remarks --- .../service/client/RemoteServiceName.java | 4 ++- .../common/ComputationParametersService.java | 25 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/client/RemoteServiceName.java b/src/main/java/org/gridsuite/study/server/service/client/RemoteServiceName.java index fc63d66a9c..80906d2117 100644 --- a/src/main/java/org/gridsuite/study/server/service/client/RemoteServiceName.java +++ b/src/main/java/org/gridsuite/study/server/service/client/RemoteServiceName.java @@ -28,6 +28,7 @@ public enum RemoteServiceName { DYNAMIC_MAPPING_SERVER, DYNAMIC_SECURITY_ANALYSIS_SERVER, DYNAMIC_SIMULATION_SERVER, + DYNAMIC_MARGIN_CALCULATION_SERVER, EXPLORE_SERVER, FILTER_SERVER, GATEWAY, @@ -49,7 +50,8 @@ public enum RemoteServiceName { TIMESERIES_SERVER, USER_ADMIN_SERVER, USER_IDENTITY_SERVER, - VOLTAGE_INIT_SERVER; + VOLTAGE_INIT_SERVER, + STATE_ESTIMATION_SERVER; /** * Service name in the format found in {@link RemoteServicesProperties.Service#getName() Service.name} diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index 719f28526d..154555ef37 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -15,6 +15,7 @@ import org.gridsuite.study.server.dto.computation.ComputationParameterUUIDs; import org.gridsuite.study.server.repository.StudyEntity; import org.gridsuite.study.server.service.*; +import org.gridsuite.study.server.service.client.RemoteServiceName; import org.gridsuite.study.server.service.dynamicmargincalculation.DynamicMarginCalculationRestService; import org.gridsuite.study.server.service.dynamicsecurityanalysis.DynamicSecurityAnalysisRestService; import org.gridsuite.study.server.service.dynamicsimulation.DynamicSimulationRestService; @@ -55,7 +56,7 @@ public class ComputationParametersService { // this is useful to avoid repetitive calls when doing operation on all computation types (duplicate, delete, export) private record ComputationParametersDefinition( ComputationType type, - String serviceName, + RemoteServiceName serviceName, Function studyParameterGetter, Function profileParameterGetter, ComputationParameters service, @@ -85,7 +86,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis this.computationParametersDefinitions = List.of( new ComputationParametersDefinition( ComputationType.LOAD_FLOW, - "loadflow-server", + RemoteServiceName.LOADFLOW_SERVER, StudyEntity::getLoadFlowParametersUuid, UserProfileInfos::getLoadFlowParameterId, loadFlowRestService, @@ -93,7 +94,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis loadFlowRestService::getParameters), new ComputationParametersDefinition( ComputationType.SHORT_CIRCUIT, - "shortcircuit-server", + RemoteServiceName.SHORTCIRCUIT_SERVER, StudyEntity::getShortCircuitParametersUuid, UserProfileInfos::getShortcircuitParameterId, shortCircuitService, @@ -101,7 +102,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis shortCircuitService::getParameters), new ComputationParametersDefinition( ComputationType.DYNAMIC_SIMULATION, - "dynamic-simulation-server", + RemoteServiceName.DYNAMIC_SIMULATION_SERVER, StudyEntity::getDynamicSimulationParametersUuid, UserProfileInfos::getDynamicSimulationParameterId, dynamicSimulationRestService, @@ -109,7 +110,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis null), new ComputationParametersDefinition( ComputationType.VOLTAGE_INITIALIZATION, - "voltage-init-server", + RemoteServiceName.VOLTAGE_INIT_SERVER, StudyEntity::getVoltageInitParametersUuid, UserProfileInfos::getVoltageInitParameterId, voltageInitService, @@ -117,7 +118,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis voltageInitService::getParameters), new ComputationParametersDefinition( ComputationType.SECURITY_ANALYSIS, - "security-analysis-server", + RemoteServiceName.SECURITY_ANALYSIS_SERVER, StudyEntity::getSecurityAnalysisParametersUuid, UserProfileInfos::getSecurityAnalysisParameterId, securityAnalysisService, @@ -125,7 +126,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis securityAnalysisService::getParameters), new ComputationParametersDefinition( ComputationType.SENSITIVITY_ANALYSIS, - "sensitivity-analysis-server", + RemoteServiceName.SENSITIVITY_ANALYSIS_SERVER, StudyEntity::getSensitivityAnalysisParametersUuid, UserProfileInfos::getSensitivityAnalysisParameterId, sensitivityAnalysisService, @@ -133,7 +134,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis sensitivityAnalysisService::getParameters), new ComputationParametersDefinition( ComputationType.DYNAMIC_SECURITY_ANALYSIS, - "dynamic-security-analysis-server", + RemoteServiceName.DYNAMIC_SECURITY_ANALYSIS_SERVER, StudyEntity::getDynamicSecurityAnalysisParametersUuid, UserProfileInfos::getDynamicSecurityAnalysisParameterId, dynamicSecurityAnalysisRestService, @@ -141,7 +142,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis null), new ComputationParametersDefinition( ComputationType.DYNAMIC_MARGIN_CALCULATION, - "dynamic-margin-calculation-server", + RemoteServiceName.DYNAMIC_MARGIN_CALCULATION_SERVER, StudyEntity::getDynamicMarginCalculationParametersUuid, UserProfileInfos::getDynamicMarginCalculationParameterId, dynamicMarginCalculationRestService, @@ -149,7 +150,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis null), new ComputationParametersDefinition( ComputationType.STATE_ESTIMATION, - "state-estimation-server", + RemoteServiceName.STATE_ESTIMATION_SERVER, StudyEntity::getStateEstimationParametersUuid, userProfileInfos -> null, stateEstimationService, @@ -157,7 +158,7 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis null), new ComputationParametersDefinition( ComputationType.PCC_MIN, - "pcc-min-server", + RemoteServiceName.PCC_MIN_SERVER, StudyEntity::getPccMinParametersUuid, UserProfileInfos::getPccMinParameterId, pccMinService, @@ -264,7 +265,7 @@ public Map exportParameters(StudyEntity studyEntity) th .map(ServiceStatusInfos::name) .collect(Collectors.toSet()); for (ComputationParametersDefinition definition : computationParametersDefinitions) { - if (definition.parametersFetcher() == null || downServices.contains(definition.serviceName())) { + if (definition.parametersFetcher() == null || downServices.contains(definition.serviceName().name())) { continue; } UUID parametersUuid = definition.studyParameterGetter().apply(studyEntity); From 84860bed1b006736b8e632675cb2578ff0618782 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 6 Oct 2026 10:46:11 +0200 Subject: [PATCH 17/18] fix service name --- .../server/service/common/ComputationParametersService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index 154555ef37..b067dd58a0 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -265,7 +265,7 @@ public Map exportParameters(StudyEntity studyEntity) th .map(ServiceStatusInfos::name) .collect(Collectors.toSet()); for (ComputationParametersDefinition definition : computationParametersDefinitions) { - if (definition.parametersFetcher() == null || downServices.contains(definition.serviceName().name())) { + if (definition.parametersFetcher() == null || downServices.contains(definition.serviceName().serviceName())) { continue; } UUID parametersUuid = definition.studyParameterGetter().apply(studyEntity); From fdc01e92aed9188e4714abf9a400f1da87917959 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 6 Oct 2026 10:50:54 +0200 Subject: [PATCH 18/18] fix sonar issue --- .../service/common/ComputationParametersService.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index b067dd58a0..d246d509cf 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -269,11 +269,10 @@ public Map exportParameters(StudyEntity studyEntity) th continue; } UUID parametersUuid = definition.studyParameterGetter().apply(studyEntity); - if (parametersUuid == null) { - continue; + if (parametersUuid != null) { + Object parameters = definition.parametersFetcher().apply(parametersUuid); + parametersByType.put(definition.type(), parameters instanceof String parametersJson ? parametersJson : objectMapper.writeValueAsString(parameters)); } - Object parameters = definition.parametersFetcher().apply(parametersUuid); - parametersByType.put(definition.type(), parameters instanceof String parametersJson ? parametersJson : objectMapper.writeValueAsString(parameters)); } return parametersByType; }