Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import java.util.Map;
import java.util.UUID;

import static org.gridsuite.study.server.utils.JsonUtils.serializeImportParameters;

@Builder
@Getter
@Setter
Expand All @@ -28,7 +30,7 @@ public class RootNetworkInfos {
// reportUuid of network import, root node one
private UUID reportUuid;

private Map<String, String> importParameters;
private Map<String, Object> importParameters;

private Map<String, Object> importParametersRaw;

Expand All @@ -46,7 +48,7 @@ public RootNetworkEntity toEntity() {
.caseName(caseInfos.getCaseName())
.caseFormat(caseInfos.getCaseFormat())
.reportUuid(reportUuid)
.importParameters(importParameters)
.importParameters(serializeImportParameters(importParameters))
.tag(tag);

if (rootNetworkNodeInfos != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public enum StudyBusinessErrorCode implements BusinessErrorCode {
NETWORK_EXPORT_FAILED("study.networkExportFailed"),
TOO_MANY_NAD_CONFIGS("study.tooManyNadConfigs"),
TOO_MANY_MAP_CARDS("study.tooManyMapCards"),
ELEMENT_ALREADY_EXISTS("study.elementAlreadyExists");
ELEMENT_ALREADY_EXISTS("study.elementAlreadyExists"),
UNPROCESSABLE_IMPORT_PARAMETER("study.unprocessableImportParameter");

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@
import org.gridsuite.study.server.networkmodificationtree.entities.RootNetworkNodeInfoEntity;
import org.gridsuite.study.server.repository.StudyEntity;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;

import static org.gridsuite.study.server.utils.JsonUtils.deserializeImportParameters;

/**
* @author Le Saulnier Kevin <lesaulnier.kevin at rte-france.com>
*/
Expand Down Expand Up @@ -93,7 +92,7 @@ public RootNetworkInfos toDto() {
rootNetworkInfosBuilder.id(this.id)
.name(this.name)
.networkInfos(new NetworkInfos(this.networkUuid, this.networkId))
.importParameters(this.importParameters)
.importParameters(deserializeImportParameters(this.importParameters))
.caseInfos(new CaseInfos(this.caseUuid, this.originalCaseUuid, this.caseName, this.caseFormat))
.reportUuid(this.reportUuid)
.tag(tag)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,6 @@ public Consumer<Message<String>> consumeCaseImportSucceeded() {
String caseFormat = message.getHeaders().get(HEADER_CASE_FORMAT, String.class);
String caseName = message.getHeaders().get(HEADER_CASE_NAME, String.class);
Map<String, Object> rawParameters = message.getHeaders().get(HEADER_IMPORT_PARAMETERS, Map.class);
// String longer than 1024 bytes are converted to com.rabbitmq.client.LongString (https://docs.spring.io/spring-amqp/docs/3.0.0/reference/html/#message-properties-converters)
Map<String, String> importParameters = new HashMap<>();
if (rawParameters != null) {
rawParameters.forEach((key, value) -> importParameters.put(key, value.toString()));
}

if (receiverString != null) {
CaseImportReceiver receiver;
Expand All @@ -237,12 +232,12 @@ public Consumer<Message<String>> consumeCaseImportSucceeded() {
return;
}

handleConsumeCaseImportSucceeded(receiver, networkUuid, networkId, caseName, caseFormat, importParameters);
handleConsumeCaseImportSucceeded(receiver, networkUuid, networkId, caseName, caseFormat, rawParameters);
}
};
}

private void handleConsumeCaseImportSucceeded(CaseImportReceiver receiver, UUID networkUuid, String networkId, String caseName, String caseFormat, Map<String, String> importParameters) {
private void handleConsumeCaseImportSucceeded(CaseImportReceiver receiver, UUID networkUuid, String networkId, String caseName, String caseFormat, Map<String, Object> importParameters) {
UUID caseUuid = receiver.getCaseUuid();
UUID studyUuid = receiver.getStudyUuid();
String userId = receiver.getUserId();
Expand Down Expand Up @@ -291,7 +286,7 @@ private void handleConsumeCaseImportSucceeded(CaseImportReceiver receiver, UUID
}

private void insertStudy(UUID studyUuid, String userId, NetworkInfos networkInfos, CaseInfos caseInfos,
Map<String, String> importParameters, UUID importReportUuid) {
Map<String, Object> importParameters, UUID importReportUuid) {
UserProfileInfos userProfileInfos = studyService.getUserProfile(userId);

UUID loadFlowParametersUuid = createDefaultLoadFlowParameters(userId, userProfileInfos);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public NetworkModificationsResult createModification(UUID groupUuid,
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

HttpEntity<String> httpEntity = new HttpEntity<>(getModificationContextJsonString(objectMapper, modificationContextInfos), headers);
HttpEntity<String> httpEntity = new HttpEntity<>(getModificationContextJsonString(modificationContextInfos), headers);
return restTemplate.exchange(path, HttpMethod.POST, httpEntity, NetworkModificationsResult.class).getBody();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.gridsuite.study.server.repository.rootnetwork.RootNetworkRequestRepository;
import org.gridsuite.study.server.repository.rootnetwork.RootNetworkEntity;
import org.gridsuite.study.server.repository.rootnetwork.RootNetworkRepository;
import org.gridsuite.study.server.utils.JsonUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -114,7 +115,7 @@ private void updateRootNetworkInfos(RootNetworkEntity rootNetworkEntity, RootNet

updateCaseInfos(rootNetworkEntity, rootNetworkInfos.getCaseInfos());
updateNetworkInfos(rootNetworkEntity, rootNetworkInfos.getNetworkInfos());
rootNetworkEntity.setImportParameters(rootNetworkInfos.getImportParameters());
rootNetworkEntity.setImportParameters(JsonUtils.serializeImportParameters(rootNetworkInfos.getImportParameters()));
rootNetworkEntity.setReportUuid(rootNetworkInfos.getReportUuid());
}

Expand Down Expand Up @@ -164,8 +165,8 @@ public String getCaseName(UUID rootNetworkUuid) {
return getRootNetwork(rootNetworkUuid).map(RootNetworkEntity::getCaseName).orElseThrow(() -> new StudyException(NOT_FOUND, "Root network not found"));
}

public Map<String, String> getImportParameters(UUID rootNetworkUuid) {
return rootNetworkRepository.findWithImportParametersById(rootNetworkUuid).map(RootNetworkEntity::getImportParameters).orElseThrow(() -> new StudyException(NOT_FOUND, "Root network not found"));
public Map<String, Object> getImportParameters(UUID rootNetworkUuid) {
return rootNetworkRepository.findWithImportParametersById(rootNetworkUuid).map(RootNetworkEntity::getImportParameters).map(JsonUtils::deserializeImportParameters).orElseThrow(() -> new StudyException(NOT_FOUND, "Root network not found"));
}

public List<RootNetworkInfos> getRootNetworkInfosWithLinksInfos(UUID studyUuid) {
Expand All @@ -183,7 +184,7 @@ public void duplicateStudyRootNetworks(StudyEntity newStudyEntity, StudyEntity s
UUID clonedNetworkUuid = networkService.getNetworkUuid(clonedNetwork);

UUID clonedCaseUuid = caseService.duplicateCase(rootNetworkEntityToDuplicate.getCaseUuid(), false);
Map<String, String> newImportParameters = Map.copyOf(rootNetworkEntityToDuplicate.getImportParameters());
Map<String, Object> newImportParameters = JsonUtils.deserializeImportParameters(rootNetworkEntityToDuplicate.getImportParameters());

UUID clonedRootNodeReportUuid = reportService.duplicateReport(rootNetworkEntityToDuplicate.getReportUuid());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ public CreatedStudyBasicInfos insertStudy(UUID studyUuid, String userId, Network
UUID voltageInitParametersUuid, UUID securityAnalysisParametersUuid, UUID sensitivityAnalysisParametersUuid,
UUID networkVisualizationParametersUuid, UUID dynamicSecurityAnalysisParametersUuid, UUID dynamicMarginCalculationParametersUuid,
UUID stateEstimationParametersUuid, UUID pccMinParametersUuid,
UUID spreadsheetConfigCollectionUuid, UUID workspacesConfigUuid, Map<String, String> importParameters, UUID importReportUuid) {
UUID spreadsheetConfigCollectionUuid, UUID workspacesConfigUuid, Map<String, Object> importParameters, UUID importReportUuid) {
Objects.requireNonNull(studyUuid);
Objects.requireNonNull(userId);
Objects.requireNonNull(networkInfos.getNetworkUuid());
Expand Down Expand Up @@ -1544,7 +1544,7 @@ private StudyEntity saveStudyThenCreateBasicTree(UUID studyUuid, NetworkInfos ne
UUID voltageInitParametersUuid, UUID securityAnalysisParametersUuid, UUID sensitivityAnalysisParametersUuid,
UUID networkVisualizationParametersUuid, UUID dynamicSecurityAnalysisParametersUuid, UUID dynamicMarginCalculationParametersUuid,
UUID stateEstimationParametersUuid, UUID pccMinParametersUuid,
UUID spreadsheetConfigCollectionUuid, UUID workspacesConfigUuid, Map<String, String> importParameters, UUID importReportUuid) {
UUID spreadsheetConfigCollectionUuid, UUID workspacesConfigUuid, Map<String, Object> importParameters, UUID importReportUuid) {

StudyEntity studyEntity = StudyEntity.builder()
.id(studyUuid)
Expand Down
49 changes: 46 additions & 3 deletions src/main/java/org/gridsuite/study/server/utils/JsonUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,19 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.NonNull;
import org.gridsuite.study.server.dto.modification.ModificationApplicationContext;
import org.gridsuite.study.server.error.StudyException;
import org.springframework.data.util.Pair;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import static org.gridsuite.study.server.error.StudyBusinessErrorCode.UNPROCESSABLE_IMPORT_PARAMETER;

public final class JsonUtils {
private static final ObjectMapper MAPPER = new ObjectMapper();

private JsonUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
Expand Down Expand Up @@ -55,14 +62,50 @@ public static JsonNode nodeAt(@NonNull final JsonNode node, @NonNull final JsonP
return nodeAt(node, jsonNode -> !jsonNode.isMissingNode(), pointers);
}

public static String getModificationContextJsonString(ObjectMapper objectMapper, Pair<String, List<ModificationApplicationContext>> modificationContextInfos) {
public static String getModificationContextJsonString(Pair<String, List<ModificationApplicationContext>> modificationContextInfos) {
try {
ObjectNode modificationJson = (ObjectNode) objectMapper.readTree(modificationContextInfos.getFirst());
ObjectNode modificationContextJson = objectMapper.valueToTree(modificationContextInfos);
ObjectNode modificationJson = (ObjectNode) MAPPER.readTree(modificationContextInfos.getFirst());
ObjectNode modificationContextJson = MAPPER.valueToTree(modificationContextInfos);
modificationContextJson.set(Pair.class.getDeclaredField("first").getName(), modificationJson);
return modificationContextJson.toString();
} catch (JsonProcessingException | NoSuchFieldException e) {
throw new IllegalStateException("Impossible to parse modification context", e);
}
}

public static Map<String, Object> deserializeImportParameters(Map<String, String> rawParams) {
Map<String, Object> result = new HashMap<>();
if (rawParams == null) {
return result;
}

rawParams.forEach((key, value) -> {
if (value == null) {
result.put(key, null);
return;
}
try {
result.put(key, MAPPER.readValue(value, Object.class));
} catch (JsonProcessingException e) {
throw new StudyException(UNPROCESSABLE_IMPORT_PARAMETER, "Import parameter '" + key + " => " + value + "' is not valid JSON: " + e.getMessage());
}
});
return result;
}

public static Map<String, String> serializeImportParameters(Map<String, Object> params) {
Map<String, String> result = new HashMap<>();
if (params == null) {
return result;
}

params.forEach((key, value) -> {
try {
result.put(key, MAPPER.writeValueAsString(value));
} catch (JsonProcessingException e) {
throw new StudyException(UNPROCESSABLE_IMPORT_PARAMETER, "Import parameter '" + key + " => " + value + "' is not serializable: " + e.getMessage());
}
});
return result;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet author="homereti" id="1747612800000-1">
<update tableName="import_parameters">
<column name="import_parameters" value="NULL"/>
<where>import_parameters=''</where>
</update>
</changeSet>
</databaseChangeLog>
88 changes: 88 additions & 0 deletions src/test/java/org/gridsuite/study/server/JsonUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* 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;

import org.gridsuite.study.server.error.StudyBusinessErrorCode;
import org.gridsuite.study.server.error.StudyException;
import org.gridsuite.study.server.utils.JsonUtils;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

/**
* @author Etienne HOMER <etiennehomer@gmail.com>
*/
class JsonUtilsTest {

@Test
void shouldSerializeImportParameters() {
Map<String, Object> params = new HashMap<>();
params.put("string", "value");
params.put("int", 123);
params.put("bool", true);
params.put("list", List.of("a", "b"));
params.put("map", Map.of("k", "v"));
params.put("null", null);

Map<String, String> result = JsonUtils.serializeImportParameters(params);

assertEquals("\"value\"", result.get("string"));
assertEquals("123", result.get("int"));
assertEquals("true", result.get("bool"));
assertEquals("[\"a\",\"b\"]", result.get("list"));
assertEquals("{\"k\":\"v\"}", result.get("map"));
assertEquals("null", result.get("null"));
}

@Test
void shouldReturnEmptyMapWhenSerializeNull() {
Map<String, String> result = JsonUtils.serializeImportParameters(null);
assertNotNull(result);
assertTrue(result.isEmpty());
}

@Test
void shouldDeserializeImportParameters() {
Map<String, String> rawParams = new HashMap<>();
rawParams.put("string", "\"value\"");
rawParams.put("int", "123");
rawParams.put("bool", "true");
rawParams.put("list", "[\"a\",\"b\"]");
rawParams.put("map", "{\"k\":\"v\"}");
rawParams.put("null", "null");
rawParams.put("realNull", null);

Map<String, Object> result = JsonUtils.deserializeImportParameters(rawParams);

assertEquals("value", result.get("string"));
assertEquals(123, result.get("int"));
assertEquals(true, result.get("bool"));
assertEquals(List.of("a", "b"), result.get("list"));
assertEquals(Map.of("k", "v"), result.get("map"));
assertNull(result.get("null"));
assertNull(result.get("realNull"));
}

@Test
void shouldReturnEmptyMapWhenDeserializeNull() {
Map<String, Object> result = JsonUtils.deserializeImportParameters(null);
assertNotNull(result);
assertTrue(result.isEmpty());
}

@Test
void shouldThrowExceptionWhenDeserializeInvalidJson() {
Map<String, String> rawParams = Map.of("invalid", "{notJson}");
StudyException exception = assertThrows(StudyException.class, () -> JsonUtils.deserializeImportParameters(rawParams));
assertEquals(StudyBusinessErrorCode.UNPROCESSABLE_IMPORT_PARAMETER, exception.getBusinessErrorCode());
assertTrue(exception.getMessage().contains("Import parameter 'invalid => {notJson}' is not valid JSON"));
}
}
Loading
Loading