From e567724bb3009c60d93cc1aef920fc68295bce8e Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 23 Dec 2025 23:23:00 +0100 Subject: [PATCH 1/7] Adapt and export shared parameters for DMC - Functional implementation Signed-off-by: Thang PHAM --- .../DynamicSimulationController.java | 2 +- ...DynamicSimulationParametersController.java | 49 ++++++ .../DynamicSimulationParametersValues.java | 32 ++++ .../service/DynamicSimulationService.java | 30 +--- .../server/service/client/FilterClient.java | 50 ++++++ .../service/parameters/ParametersService.java | 6 + .../impl/ParametersServiceImpl.java | 152 +++++++++++++----- 7 files changed, 248 insertions(+), 73 deletions(-) create mode 100644 src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersController.java create mode 100644 src/main/java/org/gridsuite/ds/server/dto/DynamicSimulationParametersValues.java create mode 100644 src/main/java/org/gridsuite/ds/server/service/client/FilterClient.java diff --git a/src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationController.java b/src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationController.java index 4f29af2..65f0a37 100644 --- a/src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationController.java +++ b/src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationController.java @@ -205,7 +205,7 @@ public ResponseEntity downloadDebugFile(@Parameter(description = "Resu public ResponseEntity> exportDynamicModel(@PathVariable("networkUuid") UUID networkUuid, @RequestParam(name = "variantId", required = false) String variantId, @RequestParam(name = "mappingName") String mappingName) { - List dynamicModelConfigList = dynamicSimulationService.exportDynamicModel(networkUuid, variantId, mappingName); + List dynamicModelConfigList = parametersService.getDynamicModel(mappingName, networkUuid, variantId); return CollectionUtils.isNotEmpty(dynamicModelConfigList) ? ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(dynamicModelConfigList) : ResponseEntity.noContent().build(); diff --git a/src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersController.java b/src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersController.java new file mode 100644 index 0000000..dabd26c --- /dev/null +++ b/src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersController.java @@ -0,0 +1,49 @@ +/** + * 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.ds.server.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.gridsuite.ds.server.DynamicSimulationApi; +import org.gridsuite.ds.server.dto.DynamicSimulationParametersInfos; +import org.gridsuite.ds.server.dto.DynamicSimulationParametersValues; +import org.gridsuite.ds.server.service.parameters.ParametersService; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; +import java.util.UUID; + + +/** + * @author Thang PHAM + */ +@RestController +@RequestMapping(value = "/" + DynamicSimulationApi.API_VERSION + "/parameters") +@Tag(name = "Dynamic simulation server - Parameters") +public class DynamicSimulationParametersController { + + private final ParametersService parametersService; + + public DynamicSimulationParametersController(ParametersService parametersService) { + this.parametersService = parametersService; + } + + @PostMapping(value = "/values", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + @Operation(summary = "Get the dynamic simulation parameters values") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic simulation parameters values"), + @ApiResponse(responseCode = "404", description = "The dynamic simulation parameters has not been found")}) + public ResponseEntity getParametersValues(@RequestParam("networkUuid") UUID networkUuid, + @RequestParam(name = "variantId", required = false) String variantId, + @RequestBody DynamicSimulationParametersInfos parameters) { + DynamicSimulationParametersValues parametersValues = parametersService.getParametersValues(parameters, networkUuid, variantId); + return ResponseEntity.of(Optional.ofNullable(parametersValues)); + } +} diff --git a/src/main/java/org/gridsuite/ds/server/dto/DynamicSimulationParametersValues.java b/src/main/java/org/gridsuite/ds/server/dto/DynamicSimulationParametersValues.java new file mode 100644 index 0000000..7e220da --- /dev/null +++ b/src/main/java/org/gridsuite/ds/server/dto/DynamicSimulationParametersValues.java @@ -0,0 +1,32 @@ +/* + * 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.ds.server.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.powsybl.dynawo.DynawoSimulationParameters; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; +import lombok.*; + +import java.util.List; + +/** + * @author Thang PHAM + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class DynamicSimulationParametersValues { + @JsonInclude(JsonInclude.Include.NON_EMPTY) + List dynamicModel; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + DynawoSimulationParameters dynawoParameters; +} diff --git a/src/main/java/org/gridsuite/ds/server/service/DynamicSimulationService.java b/src/main/java/org/gridsuite/ds/server/service/DynamicSimulationService.java index ababde4..85d8298 100644 --- a/src/main/java/org/gridsuite/ds/server/service/DynamicSimulationService.java +++ b/src/main/java/org/gridsuite/ds/server/service/DynamicSimulationService.java @@ -8,22 +8,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.powsybl.dynamicsimulation.DynamicSimulationProvider; -import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; -import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.VariantManagerConstants; -import com.powsybl.network.store.client.NetworkStoreService; -import com.powsybl.network.store.client.PreloadingStrategy; -import org.apache.commons.lang3.StringUtils; import org.gridsuite.computation.s3.ComputationS3Service; import org.gridsuite.computation.service.AbstractComputationService; import org.gridsuite.computation.service.NotificationService; import org.gridsuite.computation.service.UuidGeneratorService; import org.gridsuite.ds.server.dto.DynamicSimulationStatus; -import org.gridsuite.ds.server.dto.dynamicmapping.InputMapping; -import org.gridsuite.ds.server.service.client.dynamicmapping.DynamicMappingClient; import org.gridsuite.ds.server.service.contexts.DynamicSimulationResultContext; import org.gridsuite.ds.server.service.contexts.DynamicSimulationRunContext; -import org.gridsuite.ds.server.service.parameters.ParametersService; import org.springframework.beans.factory.annotation.Value; import org.springframework.messaging.Message; import org.springframework.stereotype.Service; @@ -38,24 +29,14 @@ public class DynamicSimulationService extends AbstractComputationService { public static final String COMPUTATION_TYPE = "dynamic simulation"; - private final ParametersService parametersService; - private final DynamicMappingClient dynamicMappingClient; - private final NetworkStoreService networkStoreService; - public DynamicSimulationService( NotificationService notificationService, ObjectMapper objectMapper, UuidGeneratorService uuidGeneratorService, DynamicSimulationResultService dynamicSimulationResultService, ComputationS3Service computationS3Service, - @Value("${dynamic-simulation.default-provider}") String defaultProvider, - ParametersService parametersService, - DynamicMappingClient dynamicMappingClient, - NetworkStoreService networkStoreService) { + @Value("${dynamic-simulation.default-provider}") String defaultProvider) { super(notificationService, dynamicSimulationResultService, computationS3Service, objectMapper, uuidGeneratorService, defaultProvider); - this.parametersService = parametersService; - this.dynamicMappingClient = dynamicMappingClient; - this.networkStoreService = networkStoreService; } @Override @@ -76,13 +57,4 @@ public List getProviders() { .toList(); } - public List exportDynamicModel(UUID networkUuid, String variantId, String mappingName) { - - InputMapping inputMapping = dynamicMappingClient.getMapping(mappingName); - Network network = networkStoreService.getNetwork(networkUuid, PreloadingStrategy.COLLECTION); - String variant = StringUtils.isBlank(variantId) ? VariantManagerConstants.INITIAL_VARIANT_ID : variantId; - network.getVariantManager().setWorkingVariant(variant); - - return parametersService.getDynamicModel(inputMapping, network); - } } diff --git a/src/main/java/org/gridsuite/ds/server/service/client/FilterClient.java b/src/main/java/org/gridsuite/ds/server/service/client/FilterClient.java new file mode 100644 index 0000000..d61012e --- /dev/null +++ b/src/main/java/org/gridsuite/ds/server/service/client/FilterClient.java @@ -0,0 +1,50 @@ +package org.gridsuite.ds.server.service.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections4.CollectionUtils; +import org.gridsuite.filter.AbstractFilter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.gridsuite.ds.server.service.client.utils.UrlUtils.buildEndPointUrl; + +@Service +public class FilterClient extends AbstractRestClient { + + public static final String API_VERSION = "v1"; + public static final String FILTERS_BASE_ENDPOINT = "filters"; + public static final String FILTERS_GET_ENDPOINT = FILTERS_BASE_ENDPOINT + URL_DELIMITER + "metadata"; + + protected FilterClient( + @Value("${gridsuite.services.filter-server.base-uri:http://filter-server/}") String baseUri, + RestTemplate restTemplate, ObjectMapper objectMapper) { + super(baseUri, restTemplate, objectMapper); + } + + public List getFilters(List filterUuids) { + if (CollectionUtils.isEmpty(filterUuids)) { + return Collections.emptyList(); + } + + String endPointUrl = buildEndPointUrl(getBaseUri(), API_VERSION, FILTERS_GET_ENDPOINT); + + UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(endPointUrl); + uriComponentsBuilder.queryParam("ids", filterUuids); + + // call filter server Rest API + return getRestTemplate().exchange( + uriComponentsBuilder.build().toUriString(), + HttpMethod.GET, + null, + new ParameterizedTypeReference>() { + }).getBody(); + } +} diff --git a/src/main/java/org/gridsuite/ds/server/service/parameters/ParametersService.java b/src/main/java/org/gridsuite/ds/server/service/parameters/ParametersService.java index 800311a..a9dd6a0 100644 --- a/src/main/java/org/gridsuite/ds/server/service/parameters/ParametersService.java +++ b/src/main/java/org/gridsuite/ds/server/service/parameters/ParametersService.java @@ -12,6 +12,7 @@ import com.powsybl.iidm.network.Network; import org.gridsuite.computation.dto.ReportInfos; import org.gridsuite.ds.server.dto.DynamicSimulationParametersInfos; +import org.gridsuite.ds.server.dto.DynamicSimulationParametersValues; import org.gridsuite.ds.server.dto.curve.CurveInfos; import org.gridsuite.ds.server.dto.dynamicmapping.InputMapping; import org.gridsuite.ds.server.dto.event.EventInfos; @@ -35,4 +36,9 @@ DynamicSimulationRunContext createRunContext(UUID networkUuid, String variantId, ReportInfos reportContext, String userId, DynamicSimulationParametersInfos parameters, boolean debug); List getDynamicModel(InputMapping inputMapping, Network network); + + List getDynamicModel(String mappingName, UUID networkUuid, String variantId); + + DynamicSimulationParametersValues getParametersValues(DynamicSimulationParametersInfos parameters, UUID networkUuid, String variantId); + } diff --git a/src/main/java/org/gridsuite/ds/server/service/parameters/impl/ParametersServiceImpl.java b/src/main/java/org/gridsuite/ds/server/service/parameters/impl/ParametersServiceImpl.java index bc77c71..448a657 100644 --- a/src/main/java/org/gridsuite/ds/server/service/parameters/impl/ParametersServiceImpl.java +++ b/src/main/java/org/gridsuite/ds/server/service/parameters/impl/ParametersServiceImpl.java @@ -6,6 +6,7 @@ */ package org.gridsuite.ds.server.service.parameters.impl; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.exceptions.UncheckedXmlStreamException; import com.powsybl.dynamicsimulation.DynamicSimulationParameters; import com.powsybl.dynamicsimulation.DynamicSimulationProvider; @@ -20,19 +21,27 @@ import com.powsybl.dynawo.xml.ParametersXml; import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VariantManagerConstants; +import com.powsybl.network.store.client.NetworkStoreService; +import com.powsybl.network.store.client.PreloadingStrategy; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.gridsuite.computation.dto.ReportInfos; -import org.gridsuite.ds.server.error.DynamicSimulationException; import org.gridsuite.ds.server.dto.DynamicSimulationParametersInfos; +import org.gridsuite.ds.server.dto.DynamicSimulationParametersValues; import org.gridsuite.ds.server.dto.XmlSerializableParameter; import org.gridsuite.ds.server.dto.curve.CurveInfos; import org.gridsuite.ds.server.dto.dynamicmapping.InputMapping; +import org.gridsuite.ds.server.dto.dynamicmapping.ParameterFile; import org.gridsuite.ds.server.dto.dynamicmapping.Rule; import org.gridsuite.ds.server.dto.dynamicmapping.automata.Automaton; import org.gridsuite.ds.server.dto.event.EventInfos; import org.gridsuite.ds.server.dto.network.NetworkInfos; import org.gridsuite.ds.server.dto.solver.SolverInfos; +import org.gridsuite.ds.server.error.DynamicSimulationException; +import org.gridsuite.ds.server.service.client.FilterClient; +import org.gridsuite.ds.server.service.client.dynamicmapping.DynamicMappingClient; import org.gridsuite.ds.server.service.contexts.DynamicSimulationRunContext; import org.gridsuite.ds.server.service.parameters.CurveGroovyGeneratorService; import org.gridsuite.ds.server.service.parameters.ParametersService; @@ -46,17 +55,18 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; import javax.xml.stream.XMLStreamException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; -import static org.gridsuite.ds.server.error.DynamicSimulationBusinessErrorCode.MAPPING_NOT_LAST_RULE_WITH_EMPTY_FILTER_ERROR; -import static org.gridsuite.ds.server.error.DynamicSimulationBusinessErrorCode.MAPPING_NOT_PROVIDED; -import static org.gridsuite.ds.server.error.DynamicSimulationBusinessErrorCode.PROVIDER_NOT_FOUND; +import static org.gridsuite.ds.server.error.DynamicSimulationBusinessErrorCode.*; /** * @author Thang PHAM @@ -67,14 +77,23 @@ public class ParametersServiceImpl implements ParametersService { private static final Logger LOGGER = LoggerFactory.getLogger(ParametersServiceImpl.class); public static final String FIELD_STATIC_ID = "staticId"; + private final NetworkStoreService networkStoreService; private final CurveGroovyGeneratorService curveGroovyGeneratorService; + private final DynamicMappingClient dynamicMappingClient; + private final FilterClient filterClient; private final String defaultProvider; @Autowired - public ParametersServiceImpl(CurveGroovyGeneratorService curveGroovyGeneratorService, + public ParametersServiceImpl(NetworkStoreService networkStoreService, + CurveGroovyGeneratorService curveGroovyGeneratorService, + DynamicMappingClient dynamicMappingClient, + FilterClient filterClient, @Value("${dynamic-simulation.default-provider}") String defaultProvider) { + this.networkStoreService = networkStoreService; this.curveGroovyGeneratorService = curveGroovyGeneratorService; + this.dynamicMappingClient = dynamicMappingClient; + this.filterClient = filterClient; this.defaultProvider = defaultProvider; } @@ -98,52 +117,57 @@ public String getCurveModel(List curves) { return generatedGroovyCurves; } - @Override - public DynamicSimulationParameters getDynamicSimulationParameters(byte[] dynamicParams, String provider, DynamicSimulationParametersInfos inputParameters) { + private DynawoSimulationParameters getDynawoSimulationParameters(byte[] dynamicParams, DynamicSimulationParametersInfos inputParameters) { try { - DynamicSimulationParameters parameters = new DynamicSimulationParameters(); - - // TODO: Powsybl side - create an explicit dependency to Dynawo class and keep dynamic simulation abstraction all over this micro service - if (DynawoSimulationProvider.NAME.equals(provider)) { - // --- MODEL PAR --- // - List modelsParameters = !ArrayUtils.isEmpty(dynamicParams) ? ParametersXml.load(new ByteArrayInputStream(dynamicParams)) : List.of(); - - DynawoSimulationParameters dynawoSimulationParameters = new DynawoSimulationParameters(); - dynawoSimulationParameters.setModelsParameters(modelsParameters); - parameters.addExtension(DynawoSimulationParameters.class, dynawoSimulationParameters); - - // TODO : a bug in powsybl-dynawo while deserializing in dynamic security analysis server, TO REMOVE - Set specificLogs = EnumSet.of(DynawoSimulationParameters.SpecificLog.NETWORK); - dynawoSimulationParameters.setSpecificLogs(specificLogs); - - // --- SOLVER PAR --- // - // solver from input parameter - SolverInfos inputSolver = inputParameters.getSolvers().stream().filter(elem -> elem.getId().equals(inputParameters.getSolverId())).findFirst().orElse(null); - if (inputSolver != null) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - XmlSerializableParameter.writeParameter(os, XmlSerializableParameter.PARAMETER_SET, inputSolver); - ParametersSet solverParameters = ParametersXml.load(new ByteArrayInputStream(os.toByteArray()), inputSolver.getId()); - dynawoSimulationParameters.setSolverType(inputSolver.getType().toSolverType()); - dynawoSimulationParameters.setSolverParameters(solverParameters); - } + DynawoSimulationParameters dynawoSimulationParameters = new DynawoSimulationParameters(); + // --- MODEL PAR --- // + List modelsParameters = !ArrayUtils.isEmpty(dynamicParams) ? ParametersXml.load(new ByteArrayInputStream(dynamicParams)) : List.of(); + dynawoSimulationParameters.setModelsParameters(modelsParameters); + + // --- SOLVER PAR --- // + // solver from input parameter + SolverInfos inputSolver = inputParameters.getSolvers().stream().filter(elem -> elem.getId().equals(inputParameters.getSolverId())).findFirst().orElse(null); + if (inputSolver != null) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + XmlSerializableParameter.writeParameter(os, XmlSerializableParameter.PARAMETER_SET, inputSolver); + ParametersSet solverParameters = ParametersXml.load(new ByteArrayInputStream(os.toByteArray()), inputSolver.getId()); + dynawoSimulationParameters.setSolverType(inputSolver.getType().toSolverType()); + dynawoSimulationParameters.setSolverParameters(solverParameters); + } - // --- NETWORK PAR --- // - // network from input parameters - NetworkInfos network = inputParameters.getNetwork(); - if (network != null) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - XmlSerializableParameter.writeParameter(os, XmlSerializableParameter.PARAMETER_SET, network); - ParametersSet networkParameters = ParametersXml.load(new ByteArrayInputStream(os.toByteArray()), network.getId()); - dynawoSimulationParameters.setNetworkParameters(networkParameters); - } + // --- NETWORK PAR --- // + // network from input parameters + NetworkInfos network = inputParameters.getNetwork(); + if (network != null) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + XmlSerializableParameter.writeParameter(os, XmlSerializableParameter.PARAMETER_SET, network); + ParametersSet networkParameters = ParametersXml.load(new ByteArrayInputStream(os.toByteArray()), network.getId()); + dynawoSimulationParameters.setNetworkParameters(networkParameters); } - return parameters; + return dynawoSimulationParameters; } catch (XMLStreamException e) { throw new UncheckedXmlStreamException(e); } } + @Override + public DynamicSimulationParameters getDynamicSimulationParameters(byte[] dynamicParams, String provider, DynamicSimulationParametersInfos inputParameters) { + DynamicSimulationParameters parameters = new DynamicSimulationParameters(); + + // TODO: Powsybl side - create an explicit dependency to Dynawo class and keep dynamic simulation abstraction all over this micro service + if (DynawoSimulationProvider.NAME.equals(provider)) { + DynawoSimulationParameters dynawoSimulationParameters = getDynawoSimulationParameters(dynamicParams, inputParameters); + + // TODO : a bug in powsybl-dynawo while deserializing in dynamic security analysis server, TO REMOVE + Set specificLogs = EnumSet.of(DynawoSimulationParameters.SpecificLog.NETWORK); + dynawoSimulationParameters.setSpecificLogs(specificLogs); + + parameters.addExtension(DynawoSimulationParameters.class, dynawoSimulationParameters); + } + return parameters; + } + @Override public DynamicSimulationRunContext createRunContext(UUID networkUuid, String variantId, String receiver, String provider, String mapping, ReportInfos reportInfos, String userId, DynamicSimulationParametersInfos parameters, boolean debug) { @@ -224,7 +248,7 @@ public List getDynamicModel(InputMapping inputMapping, Netwo .build(); } - List> matchedEquipmentsOfCurrentRule = FiltersUtils.getIdentifiables(filter, network, null); + List> matchedEquipmentsOfCurrentRule = FiltersUtils.getIdentifiables(filter, network, filterClient::getFilters); // eliminate already matched equipments to avoid duplication if (!matchedEquipmentIdsOfCurrentType.isEmpty()) { @@ -256,4 +280,46 @@ public List getDynamicModel(InputMapping inputMapping, Netwo return dynamicModel; } + + private DynamicSimulationParametersValues getParametersValues(DynamicSimulationParametersInfos parametersInfos, Network network) { + // get parameters file from dynamic mapping server + ParameterFile parameterFile = dynamicMappingClient.exportParameters(parametersInfos.getMapping()); + + // get dynawo simulation parameters + String parameterFileContent = parameterFile.fileContent(); + DynawoSimulationParameters dynawoSimulationParameters = getDynawoSimulationParameters( + parameterFileContent.getBytes(StandardCharsets.UTF_8), parametersInfos); + + // get mapping then generate dynamic model configs + InputMapping inputMapping = dynamicMappingClient.getMapping(parametersInfos.getMapping()); + List dynamicModel = getDynamicModel(inputMapping, network); + + return new DynamicSimulationParametersValues(dynamicModel, dynawoSimulationParameters); + } + + private Network getNetwork(UUID networkUuid, String variantId) { + try { + Network network = networkStoreService.getNetwork(networkUuid, PreloadingStrategy.COLLECTION); + String variant = StringUtils.isBlank(variantId) ? VariantManagerConstants.INITIAL_VARIANT_ID : variantId; + network.getVariantManager().setWorkingVariant(variant); + return network; + } catch (PowsyblException e) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage()); + } + } + + @Override + public List getDynamicModel(String mappingName, UUID networkUuid, String variantId) { + InputMapping inputMapping = dynamicMappingClient.getMapping(mappingName); + Network network = getNetwork(networkUuid, variantId); + + return getDynamicModel(inputMapping, network); + } + + @Override + public DynamicSimulationParametersValues getParametersValues(DynamicSimulationParametersInfos parametersInfos, UUID networkUuid, String variantId) { + Network network = getNetwork(networkUuid, variantId); + + return getParametersValues(parametersInfos, network); + } } From 2f52c0d710fdc1af036cdb08992060091f2c06b8 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Thu, 29 Jan 2026 20:17:13 +0100 Subject: [PATCH 2/7] adapt dynamic model config serializer/deserializer Signed-off-by: Thang PHAM --- .../DynamicModelConfigJsonUtils.java | 35 ++++ .../DynamicModelConfigsJsonSerializer.java | 168 ++++++++++++++++++ ...DynamicSimulationParametersController.java | 4 +- .../DynamicSimulationParametersValues.java | 6 + .../DynamicSimulationWorkerService.java | 4 +- ...DynamicSimulationControllerIEEE14Test.java | 6 +- 6 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigJsonUtils.java create mode 100644 src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java diff --git a/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigJsonUtils.java b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigJsonUtils.java new file mode 100644 index 0000000..f157391 --- /dev/null +++ b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigJsonUtils.java @@ -0,0 +1,35 @@ +/** + * 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 com.powsybl.dynawo.suppliers.dynamicmodels; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.powsybl.commons.json.JsonUtil; + +import java.util.List; + +/** + * @author Thang PHAM + */ +public final class DynamicModelConfigJsonUtils { + + private DynamicModelConfigJsonUtils() { + throw new AssertionError("Utility class should not be instantiated"); + } + + public static ObjectMapper createObjectMapper() { + ObjectMapper mapper = JsonUtil.createObjectMapper(); + + SimpleModule module = new SimpleModule("dynamic-model-configs"); + module.addSerializer(new DynamicModelConfigsJsonSerializer()); + module.addDeserializer(List.class, new DynamicModelConfigsJsonDeserializer()); + + mapper.registerModule(module); + return mapper; + } +} diff --git a/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java new file mode 100644 index 0000000..ad0add7 --- /dev/null +++ b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java @@ -0,0 +1,168 @@ +/** + * 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 com.powsybl.dynawo.suppliers.dynamicmodels; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.powsybl.dynawo.suppliers.Property; +import com.powsybl.dynawo.suppliers.PropertyType; +import com.powsybl.iidm.network.TwoSides; + +import java.io.IOException; +import java.util.List; + +/** + * Serialize a List with "models" as root: + * + * { + * "models": [ + * { + * "model": "...", + * "group": "...", + * "groupType": "FIXED", + * "properties": [ ... ] + * } + * ] + * } + * + * This matches {@link DynamicModelConfigsJsonDeserializer}. + * + * @author Thang PHAM + */ +public class DynamicModelConfigsJsonSerializer extends StdSerializer> { + + public DynamicModelConfigsJsonSerializer() { + super((Class>) (Class) List.class); + } + + @Override + public void serialize(List configs, + JsonGenerator gen, + SerializerProvider provider) throws IOException { + + gen.writeStartObject(); + + gen.writeFieldName("models"); + writeDynamicModelConfigs(configs, gen); + + gen.writeEndObject(); + } + + private static void writeDynamicModelConfigs(List configs, JsonGenerator gen) throws IOException { + gen.writeStartArray(); + if (configs != null) { + for (DynamicModelConfig cfg : configs) { + writeDynamicModelConfig(cfg, gen); + } + } + gen.writeEndArray(); + } + + private static void writeDynamicModelConfig(DynamicModelConfig cfg, + JsonGenerator gen) throws IOException { + + gen.writeStartObject(); + + gen.writeStringField("model", cfg.model()); + gen.writeStringField("group", cfg.group()); + + if (cfg.groupType() != null) { + gen.writeStringField("groupType", cfg.groupType().name()); + } + + gen.writeFieldName("properties"); + writeProperties(cfg.properties(), gen); + + gen.writeEndObject(); + } + + private static void writeProperties(List properties, JsonGenerator gen) throws IOException { + gen.writeStartArray(); + if (properties != null) { + for (Property p : properties) { + writeProperty(p, gen); + } + } + gen.writeEndArray(); + } + + /** + * See {@code PropertyParserUtils.parseProperty}: + * - always writes "name" + * - writes exactly one of: "value" (string value), "values" (string array), "arrays" (array of string arrays) + * - writes "type" when it can be inferred from propertyClass + */ + private static void writeProperty(Property property, JsonGenerator gen) throws IOException { + gen.writeStartObject(); + + gen.writeStringField("name", property.name()); + + Object value = property.value(); + if (value instanceof List list) { + if (list.isEmpty() || !(list.getFirst() instanceof List)) { + // lists: List + gen.writeFieldName("values"); + gen.writeStartArray(); + for (Object v : list) { + gen.writeString(String.valueOf(v)); + } + gen.writeEndArray(); + writeOptionalType(gen, property); + } else if (list.getFirst() instanceof List) { + // arrays: List> + gen.writeFieldName("arrays"); + gen.writeStartArray(); + for (Object row : list) { + gen.writeStartArray(); + for (Object v : (List) row) { + gen.writeString(String.valueOf(v)); + } + gen.writeEndArray(); + } + gen.writeEndArray(); + writeOptionalType(gen, property); + } + } else { + if (value instanceof TwoSides ts) { + gen.writeStringField("value", ts.name()); + writeOptionalType(gen, property); + } else if (value != null) { + gen.writeStringField("value", String.valueOf(value)); + writeOptionalType(gen, property); + } + } + + gen.writeEndObject(); + } + + /** + * Writes the "type" field if we can (or if Property already carries an explicit type via propertyClass()). + * + * This keeps compatibility with PropertyParserUtils: + * case "type" -> builder.type(PropertyType.valueOf(parser.nextTextValue())); + */ + private static void writeOptionalType(JsonGenerator gen, Property property) throws IOException { + PropertyType inferredType = PropertyType.STRING; + + Class propertyClass = property.propertyClass(); + if (propertyClass != null) { + if (propertyClass == double.class) { + inferredType = PropertyType.DOUBLE; + } else if (propertyClass == int.class) { + inferredType = PropertyType.INTEGER; + } else if (propertyClass == boolean.class) { + inferredType = PropertyType.BOOLEAN; + } else if (propertyClass == TwoSides.class) { + inferredType = PropertyType.TWO_SIDES; + } + } + + gen.writeStringField("type", inferredType.name()); + } +} diff --git a/src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersController.java b/src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersController.java index dabd26c..4e6c1e5 100644 --- a/src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersController.java +++ b/src/main/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersController.java @@ -36,11 +36,11 @@ public DynamicSimulationParametersController(ParametersService parametersService this.parametersService = parametersService; } - @PostMapping(value = "/values", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + @PostMapping(value = "/values", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Get the dynamic simulation parameters values") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic simulation parameters values"), @ApiResponse(responseCode = "404", description = "The dynamic simulation parameters has not been found")}) - public ResponseEntity getParametersValues(@RequestParam("networkUuid") UUID networkUuid, + public ResponseEntity getParametersValues(@RequestParam(name = "networkUuid") UUID networkUuid, @RequestParam(name = "variantId", required = false) String variantId, @RequestBody DynamicSimulationParametersInfos parameters) { DynamicSimulationParametersValues parametersValues = parametersService.getParametersValues(parameters, networkUuid, variantId); diff --git a/src/main/java/org/gridsuite/ds/server/dto/DynamicSimulationParametersValues.java b/src/main/java/org/gridsuite/ds/server/dto/DynamicSimulationParametersValues.java index 7e220da..105d1a3 100644 --- a/src/main/java/org/gridsuite/ds/server/dto/DynamicSimulationParametersValues.java +++ b/src/main/java/org/gridsuite/ds/server/dto/DynamicSimulationParametersValues.java @@ -8,8 +8,12 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.powsybl.dynawo.DynawoSimulationParameters; import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfigsJsonDeserializer; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfigsJsonSerializer; import lombok.*; import java.util.List; @@ -24,6 +28,8 @@ @Builder @JsonIgnoreProperties(ignoreUnknown = true) public class DynamicSimulationParametersValues { + @JsonSerialize(using = DynamicModelConfigsJsonSerializer.class) + @JsonDeserialize(using = DynamicModelConfigsJsonDeserializer.class) @JsonInclude(JsonInclude.Include.NON_EMPTY) List dynamicModel; diff --git a/src/main/java/org/gridsuite/ds/server/service/DynamicSimulationWorkerService.java b/src/main/java/org/gridsuite/ds/server/service/DynamicSimulationWorkerService.java index 283216d..1df0e50 100644 --- a/src/main/java/org/gridsuite/ds/server/service/DynamicSimulationWorkerService.java +++ b/src/main/java/org/gridsuite/ds/server/service/DynamicSimulationWorkerService.java @@ -20,6 +20,7 @@ import com.powsybl.dynawo.DynawoSimulationParameters; import com.powsybl.dynawo.DynawoSimulationProvider; import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfigJsonUtils; import com.powsybl.dynawo.suppliers.dynamicmodels.DynawoModelsSupplier; import com.powsybl.dynawo.suppliers.events.DynawoEventModelsSupplier; import com.powsybl.dynawo.suppliers.events.EventModelConfig; @@ -331,7 +332,8 @@ private byte[] zipParameters(DynamicSimulationParameters parameters) { private byte[] zipDynamicModel(List dynamicModelContent) { byte[] zippedJsonDynamicModelContent; try { - String jsonDynamicModelContent = objectMapper.writeValueAsString(dynamicModelContent); + // cannot use global shared object mapper for List because the Deserializer/Serializer write as an object with "models" root + String jsonDynamicModelContent = DynamicModelConfigJsonUtils.createObjectMapper().writeValueAsString(dynamicModelContent); zippedJsonDynamicModelContent = Utils.zip(jsonDynamicModelContent); } catch (IOException e) { throw new UncheckedIOException("Error occurred while zipping the dynamic model", e); diff --git a/src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationControllerIEEE14Test.java b/src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationControllerIEEE14Test.java index 3cdcde5..213f5a7 100644 --- a/src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationControllerIEEE14Test.java +++ b/src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationControllerIEEE14Test.java @@ -16,6 +16,7 @@ import com.powsybl.dynamicsimulation.DynamicSimulationResult; import com.powsybl.dynamicsimulation.json.DynamicSimulationResultDeserializer; import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig; +import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfigJsonUtils; import com.powsybl.iidm.network.Importers; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.VariantManagerConstants; @@ -273,8 +274,9 @@ public void test01GivenCurvesAndEvents() throws Exception { logger.info("Size of zipped dynamic model = {} B ", zippedDynamicModel.length); // export dynamic model in json and dump files to manual check - List dynamicModel = Utils.unzip(zippedDynamicModel, objectMapper, new TypeReference<>() { }); - String jsonDynamicModel = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(dynamicModel); + ObjectMapper dynamicModelConfigObjectMapper = DynamicModelConfigJsonUtils.createObjectMapper(); + List dynamicModel = Utils.unzip(zippedDynamicModel, dynamicModelConfigObjectMapper, new TypeReference<>() { }); + String jsonDynamicModel = dynamicModelConfigObjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(dynamicModel); FileUtils.writeBytesToFile(this, outputDir + RESOURCE_PATH_DELIMITER + "dynamicModel.json", jsonDynamicModel.getBytes()); file = new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource(".")).getFile() + From 1b5c72eeb3a4996ec687f1ae55dbc8284b54deaa Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Wed, 11 Feb 2026 18:14:33 +0100 Subject: [PATCH 3/7] Add tests Signed-off-by: Thang PHAM --- .../DynamicModelConfigsJsonSerializer.java | 2 + .../server/service/client/FilterClient.java | 9 + ...ulationParametersControllerIEEE14Test.java | 159 ++++++++++++++++++ .../service/client/FilterClientTest.java | 101 +++++++++++ .../server/utils/assertions/Assertions.java | 6 + 5 files changed, 277 insertions(+) create mode 100644 src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersControllerIEEE14Test.java create mode 100644 src/test/java/org/gridsuite/ds/server/service/client/FilterClientTest.java diff --git a/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java index ad0add7..687515c 100644 --- a/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java +++ b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java @@ -33,6 +33,8 @@ * * This matches {@link DynamicModelConfigsJsonDeserializer}. * + * TODO : to remove when available at powsybl-dynawo + * * @author Thang PHAM */ public class DynamicModelConfigsJsonSerializer extends StdSerializer> { diff --git a/src/main/java/org/gridsuite/ds/server/service/client/FilterClient.java b/src/main/java/org/gridsuite/ds/server/service/client/FilterClient.java index d61012e..9662793 100644 --- a/src/main/java/org/gridsuite/ds/server/service/client/FilterClient.java +++ b/src/main/java/org/gridsuite/ds/server/service/client/FilterClient.java @@ -1,3 +1,9 @@ +/** + * 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.ds.server.service.client; import com.fasterxml.jackson.databind.ObjectMapper; @@ -16,6 +22,9 @@ import static org.gridsuite.ds.server.service.client.utils.UrlUtils.buildEndPointUrl; +/** + * @author Thang PHAM + */ @Service public class FilterClient extends AbstractRestClient { diff --git a/src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersControllerIEEE14Test.java b/src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersControllerIEEE14Test.java new file mode 100644 index 0000000..cbaa172 --- /dev/null +++ b/src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersControllerIEEE14Test.java @@ -0,0 +1,159 @@ +/** + * 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.ds.server.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.datasource.ReadOnlyDataSource; +import com.powsybl.commons.datasource.ResourceDataSource; +import com.powsybl.commons.datasource.ResourceSet; +import com.powsybl.iidm.network.Importers; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VariantManagerConstants; +import com.powsybl.network.store.client.NetworkStoreService; +import com.powsybl.network.store.client.PreloadingStrategy; +import org.gridsuite.ds.server.DynamicSimulationApplication; +import org.gridsuite.ds.server.controller.utils.ParameterUtils; +import org.gridsuite.ds.server.dto.DynamicSimulationParametersInfos; +import org.gridsuite.ds.server.dto.DynamicSimulationParametersValues; +import org.gridsuite.ds.server.dto.dynamicmapping.InputMapping; +import org.gridsuite.ds.server.dto.dynamicmapping.ParameterFile; +import org.gridsuite.ds.server.service.client.dynamicmapping.DynamicMappingClient; +import org.gridsuite.ds.server.utils.assertions.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.util.StreamUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +import static org.gridsuite.ds.server.utils.Utils.RESOURCE_PATH_DELIMITER; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Thang PHAM + */ +@AutoConfigureMockMvc +@SpringBootTest +@ContextConfiguration(classes = {DynamicSimulationApplication.class}) +public class DynamicSimulationParametersControllerIEEE14Test { + // mapping names + public static final String MAPPING_NAME_01 = "_01"; + + // directories + public static final String DATA_IEEE14_BASE_DIR = RESOURCE_PATH_DELIMITER + "data" + RESOURCE_PATH_DELIMITER + "ieee14"; + public static final String INPUT = "input"; + public static final String MAPPING_FILE = "mapping.json"; + public static final String MODELS_PAR = "models.par"; + + private static final String NETWORK_UUID_STRING = "46661c4e-f948-475f-8be9-eed0c9d4da23"; + private static final String NETWORK_UUID_NOT_FOUND_STRING = "6ac83015-4e9b-48c0-be96-1b5b241fb725"; + private static final String VARIANT_1_ID = "variant_1"; + private static final String NETWORK_FILE = "IEEE14.iidm"; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + protected DynamicMappingClient dynamicMappingClient; + + @MockitoBean + protected NetworkStoreService networkStoreClient; + + private void initNetworkStoreServiceMock() { + ReadOnlyDataSource dataSource = new ResourceDataSource("IEEE14", + new ResourceSet(DATA_IEEE14_BASE_DIR, NETWORK_FILE)); + Network network = Importers.importData("XIIDM", dataSource, null); + network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_1_ID); + given(networkStoreClient.getNetwork(UUID.fromString(NETWORK_UUID_STRING), PreloadingStrategy.COLLECTION)).willReturn(network); + given(networkStoreClient.getNetwork(UUID.fromString(NETWORK_UUID_NOT_FOUND_STRING), PreloadingStrategy.COLLECTION)).willThrow(new PowsyblException()); + } + + private void initDynamicMappingServiceMock() { + try { + String inputDir = DATA_IEEE14_BASE_DIR + + RESOURCE_PATH_DELIMITER + MAPPING_NAME_01 + + RESOURCE_PATH_DELIMITER + INPUT; + + // load models.par + String parametersFilePath = inputDir + RESOURCE_PATH_DELIMITER + MODELS_PAR; + InputStream parametersFileIS = getClass().getResourceAsStream(parametersFilePath); + byte[] parametersFileBytes; + parametersFileBytes = StreamUtils.copyToByteArray(parametersFileIS); + String parametersFile = new String(parametersFileBytes, StandardCharsets.UTF_8); + + ParameterFile parameterFile = new ParameterFile( + MAPPING_NAME_01, + parametersFile); + given(dynamicMappingClient.exportParameters(MAPPING_NAME_01)).willReturn(parameterFile); + + // load mapping.json + String mappingPath = inputDir + RESOURCE_PATH_DELIMITER + MAPPING_FILE; + InputMapping inputMapping = objectMapper.readValue(getClass().getResourceAsStream(mappingPath), InputMapping.class); + given(dynamicMappingClient.getMapping(MAPPING_NAME_01)).willReturn(inputMapping); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @BeforeEach + void setUp() { + initNetworkStoreServiceMock(); + initDynamicMappingServiceMock(); + } + + @Test + void testGetParametersValues() throws Exception { + // --- Setup --- // + DynamicSimulationParametersInfos parameters = ParameterUtils.getDefaultDynamicSimulationParameters(); + parameters.setSolverId("SIM"); + parameters.setMapping(MAPPING_NAME_01); + + String parametersJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(parameters); + + // --- Execute --- // + MvcResult result = mockMvc.perform(post("/v1/parameters/values") + .param("networkUuid", NETWORK_UUID_STRING) + .param("variantId", VARIANT_1_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(parametersJson)) + .andExpect(status().isOk()) + .andReturn(); + + String resultParametersJson = result.getResponse().getContentAsString(); + DynamicSimulationParametersValues parametersValues = objectMapper.readValue(resultParametersJson, DynamicSimulationParametersValues.class); + + // --- Verify --- // + Assertions.assertThat(parametersValues.getDynamicModel()).isNotEmpty(); + + Assertions.assertThat(parametersValues.getDynawoParameters().getModelParameters()).isNotEmpty(); + Assertions.assertThat(parametersValues.getDynawoParameters().getSolverParameters().getParameters()).isNotEmpty(); + Assertions.assertThat(parametersValues.getDynawoParameters().getNetworkParameters().getParameters()).isNotEmpty(); + + verify(dynamicMappingClient, times(1)).getMapping(MAPPING_NAME_01); + verify(dynamicMappingClient, times(1)).exportParameters(MAPPING_NAME_01); + } +} diff --git a/src/test/java/org/gridsuite/ds/server/service/client/FilterClientTest.java b/src/test/java/org/gridsuite/ds/server/service/client/FilterClientTest.java new file mode 100644 index 0000000..183e257 --- /dev/null +++ b/src/test/java/org/gridsuite/ds/server/service/client/FilterClientTest.java @@ -0,0 +1,101 @@ +/* + * 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.ds.server.service.client; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import org.gridsuite.ds.server.service.client.utils.UrlUtils; +import org.gridsuite.ds.server.utils.assertions.Assertions; +import org.gridsuite.filter.AbstractFilter; +import org.gridsuite.filter.expertfilter.ExpertFilter; +import org.gridsuite.filter.expertfilter.expertrule.CombinatorExpertRule; +import org.gridsuite.filter.expertfilter.expertrule.NumberExpertRule; +import org.gridsuite.filter.expertfilter.expertrule.StringExpertRule; +import org.gridsuite.filter.utils.EquipmentType; +import org.gridsuite.filter.utils.expertfilter.CombinatorType; +import org.gridsuite.filter.utils.expertfilter.FieldType; +import org.gridsuite.filter.utils.expertfilter.OperatorType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.UUID; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +/** + * @author Thang PHAM + */ +class FilterClientTest extends AbstractWireMockRestClientTest { + + private FilterClient filterClient; + + @Autowired + RestTemplate restTemplate; + + @Autowired + private ObjectMapper objectMapper; + + @BeforeEach + @Override + public void setup() { + filterClient = new FilterClient( + // use new WireMockServer(ACTIONS_PORT) to test with local server if needed + initMockWebServer(new WireMockServer(wireMockConfig().dynamicPort())), + restTemplate, + objectMapper + ); + } + + @Test + void testGetFilters() throws JsonProcessingException { + // --- Setup --- // + String url = UrlUtils.buildEndPointUrl("", FilterClient.API_VERSION, + FilterClient.FILTERS_GET_ENDPOINT); + // prepare filters to return + UUID expertFilterId1 = UUID.fromString("ecc0b070-1f24-4c7e-892e-65e2f994b7b0"); + ExpertFilter expertFilter1 = new ExpertFilter(expertFilterId1, null, EquipmentType.LOAD, CombinatorExpertRule.builder() + .combinator(CombinatorType.AND) + .rules(List.of(StringExpertRule.builder().field(FieldType.ID).operator(OperatorType.IS).value("load1").build())) + .build()); + + UUID expertFilterId2 = UUID.fromString("4c9bd0e7-9da5-442a-bd37-8989082f9389"); + ExpertFilter expertFilter2 = new ExpertFilter(expertFilterId2, null, EquipmentType.GENERATOR, CombinatorExpertRule.builder() + .combinator(CombinatorType.OR) + .rules(List.of(NumberExpertRule.builder().field(FieldType.NOMINAL_VOLTAGE).operator(OperatorType.GREATER).value(225d).build())) + .build()); + + List returnedFilters = List.of(expertFilter1, expertFilter2); + + // stub method to return filters + String responseBody = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(returnedFilters); + wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo(url)) + .withQueryParam("ids", WireMock.equalTo(expertFilterId1.toString())) + .withQueryParam("ids", WireMock.equalTo(expertFilterId2.toString())) + .willReturn(WireMock.ok() + .withHeader("Content-Type", APPLICATION_JSON_VALUE) + .withBody(responseBody)) + ); + + // --- Invoke method to test --- // + List resultFilters = filterClient.getFilters(List.of(expertFilterId1, expertFilterId2)); + + // --- Verify result --- // + Assertions.assertThat(resultFilters).isEqualTo(returnedFilters); + wireMockServer.verify(1, + WireMock.getRequestedFor(WireMock.urlPathEqualTo(url)) + .withQueryParam("ids", WireMock.equalTo(expertFilterId1.toString())) + .withQueryParam("ids", WireMock.equalTo(expertFilterId2.toString())) + ); + } +} diff --git a/src/test/java/org/gridsuite/ds/server/utils/assertions/Assertions.java b/src/test/java/org/gridsuite/ds/server/utils/assertions/Assertions.java index 615e91f..b5568c4 100644 --- a/src/test/java/org/gridsuite/ds/server/utils/assertions/Assertions.java +++ b/src/test/java/org/gridsuite/ds/server/utils/assertions/Assertions.java @@ -8,6 +8,7 @@ import org.assertj.core.util.CheckReturnValue; import org.gridsuite.ds.server.dto.dynamicmapping.InputMapping; +import org.gridsuite.filter.AbstractFilter; /** * @author Tristan Chuine @@ -18,4 +19,9 @@ public class Assertions extends org.assertj.core.api.Assertions { public static DTOAssert assertThat(T actual) { return new DTOAssert<>(actual); } + + @CheckReturnValue + public static DTOAssert assertThat(T actual) { + return new DTOAssert<>(actual); + } } From b97b6d4932e4fc8d32cdce68bf7180f65cdc8894 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Wed, 11 Feb 2026 19:46:46 +0100 Subject: [PATCH 4/7] Test serialize dynamic model Signed-off-by: Thang PHAM --- .../DynamicModelConfigsJsonSerializer.java | 22 +++++------ ...DynamicModelConfigsJsonSerializerTest.java | 39 +++++++++++++++++++ src/test/resources/data/dynamicModels.json | 37 ++++++++++++++++++ 3 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 src/test/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializerTest.java create mode 100644 src/test/resources/data/dynamicModels.json diff --git a/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java index 687515c..57bae78 100644 --- a/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java +++ b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java @@ -107,7 +107,7 @@ private static void writeProperty(Property property, JsonGenerator gen) throws I Object value = property.value(); if (value instanceof List list) { - if (list.isEmpty() || !(list.getFirst() instanceof List)) { + if (!list.isEmpty()) { // lists: List gen.writeFieldName("values"); gen.writeStartArray(); @@ -116,20 +116,20 @@ private static void writeProperty(Property property, JsonGenerator gen) throws I } gen.writeEndArray(); writeOptionalType(gen, property); - } else if (list.getFirst() instanceof List) { - // arrays: List> - gen.writeFieldName("arrays"); + } + } else if (value.getClass().isArray()) { + // arrays: List> + gen.writeFieldName("arrays"); + gen.writeStartArray(); + for (List row : (List[]) value) { gen.writeStartArray(); - for (Object row : list) { - gen.writeStartArray(); - for (Object v : (List) row) { - gen.writeString(String.valueOf(v)); - } - gen.writeEndArray(); + for (Object v : row) { + gen.writeString(String.valueOf(v)); } gen.writeEndArray(); - writeOptionalType(gen, property); } + gen.writeEndArray(); + writeOptionalType(gen, property); } else { if (value instanceof TwoSides ts) { gen.writeStringField("value", ts.name()); diff --git a/src/test/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializerTest.java b/src/test/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializerTest.java new file mode 100644 index 0000000..02530a4 --- /dev/null +++ b/src/test/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializerTest.java @@ -0,0 +1,39 @@ +/** + * 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 com.powsybl.dynawo.suppliers.dynamicmodels; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.assertj.core.api.Assertions; +import org.gridsuite.ds.server.controller.utils.FileUtils; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Thang PHAM + */ +class DynamicModelConfigsJsonSerializerTest { + + @Test + void testModelConfigDeserializerSerializer() throws IOException { + + ObjectMapper objectMapper = DynamicModelConfigJsonUtils.createObjectMapper(); + List dynamicModelConfigs = objectMapper.readValue(getClass().getResourceAsStream("/data/dynamicModels.json"), new TypeReference<>() { }); + assertEquals(2, dynamicModelConfigs.size()); + + String dynamicModelConfigsJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(dynamicModelConfigs); + List dynamicModelConfigs2 = objectMapper.readValue(dynamicModelConfigsJson, new TypeReference<>() { }); + FileUtils.writeBytesToFile(this, "/data/dynamicModels_exported.json", dynamicModelConfigsJson.getBytes()); + + Assertions.assertThat(dynamicModelConfigs2).usingRecursiveComparison().isEqualTo(dynamicModelConfigs); + } +} diff --git a/src/test/resources/data/dynamicModels.json b/src/test/resources/data/dynamicModels.json new file mode 100644 index 0000000..71605fc --- /dev/null +++ b/src/test/resources/data/dynamicModels.json @@ -0,0 +1,37 @@ +{ + "models":[ + { + "model":"LoadAlphaBeta", + "group": "_DM", + "groupType": "SUFFIX", + "properties":[ + { + "name":"staticId", + "value":"LOAD", + "type":"STRING" + } + ] + }, + { + "model": "TapChangerBlockingAutomationSystem", + "group": "tcb_par", + "properties":[ + { + "name": "dynamicModelId", + "value": "TCB1", + "type": "STRING" + }, + { + "name": "transformers", + "values": ["NGEN_NHV1", "NHV2_NLOAD"], + "type": "STRING" + }, + { + "name": "uMeasurements", + "arrays": [["OldNGen", "NGEN"], ["NHV1", "NHV2"]], + "type": "STRING" + } + ] + } + ] +} \ No newline at end of file From f18e4db1ff3028c190a8e42996082f7600bb8273 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Wed, 11 Feb 2026 19:58:02 +0100 Subject: [PATCH 5/7] rectify serialize dynamic model Signed-off-by: Thang PHAM --- .../dynamicmodels/DynamicModelConfigsJsonSerializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java index 57bae78..8bdc326 100644 --- a/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java +++ b/src/main/java/com/powsybl/dynawo/suppliers/dynamicmodels/DynamicModelConfigsJsonSerializer.java @@ -117,7 +117,7 @@ private static void writeProperty(Property property, JsonGenerator gen) throws I gen.writeEndArray(); writeOptionalType(gen, property); } - } else if (value.getClass().isArray()) { + } else if (value != null && value.getClass().isArray()) { // arrays: List> gen.writeFieldName("arrays"); gen.writeStartArray(); From 3cda8f8cd25596a4449288b7969060f31689f23d Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Wed, 11 Feb 2026 23:05:30 +0100 Subject: [PATCH 6/7] Test evaluate parameters Signed-off-by: Thang PHAM --- .../DynamicSimulationParametersControllerIEEE14Test.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersControllerIEEE14Test.java b/src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersControllerIEEE14Test.java index cbaa172..933bd0f 100644 --- a/src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersControllerIEEE14Test.java +++ b/src/test/java/org/gridsuite/ds/server/controller/DynamicSimulationParametersControllerIEEE14Test.java @@ -43,6 +43,7 @@ import java.nio.charset.StandardCharsets; import java.util.UUID; +import static org.gridsuite.computation.service.AbstractResultContext.VARIANT_ID_HEADER; import static org.gridsuite.ds.server.utils.Utils.RESOURCE_PATH_DELIMITER; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.times; @@ -137,7 +138,7 @@ void testGetParametersValues() throws Exception { // --- Execute --- // MvcResult result = mockMvc.perform(post("/v1/parameters/values") .param("networkUuid", NETWORK_UUID_STRING) - .param("variantId", VARIANT_1_ID) + .param(VARIANT_ID_HEADER, VARIANT_1_ID) .contentType(MediaType.APPLICATION_JSON) .content(parametersJson)) .andExpect(status().isOk()) From d477405110132ef88e56b5208375e0d002a30a49 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 13 Feb 2026 16:24:55 +0100 Subject: [PATCH 7/7] overide testcontainers 1.21.4 to fix bug github docker client Signed-off-by: Thang PHAM --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index eb832f6..fa50a40 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,8 @@ 47.0.0 powsybl/java-dynawo:3.1.0 + + 1.21.4 1.4.200 org.gridsuite.ds.server 4.3.1 @@ -88,6 +90,11 @@ + + org.testcontainers + testcontainers + ${testcontainers.version} +