Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,9 +30,7 @@ public class RootNetworkInfos {
// reportUuid of network import, root node one
private UUID reportUuid;

private Map<String, String> importParameters;

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

private String tag;

Expand All @@ -46,7 +46,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 @@ -333,7 +333,7 @@ public RootNetworkRequestInfos createRootNetworkRequest(UUID studyUuid, RootNetw
try {
UUID clonedCaseUuid = caseService.duplicateCase(rootNetworkInfos.getCaseInfos().getOriginalCaseUuid(), true);
rootNetworkInfos.getCaseInfos().setCaseUuid(clonedCaseUuid);
persistNetwork(rootNetworkInfos, studyUuid, null, userId, rootNetworkInfos.getImportParametersRaw(), CaseImportAction.ROOT_NETWORK_CREATION);
persistNetwork(rootNetworkInfos, studyUuid, null, userId, rootNetworkInfos.getImportParameters(), CaseImportAction.ROOT_NETWORK_CREATION);
} catch (Exception e) {
rootNetworkService.deleteRootNetworkRequest(rootNetworkCreationRequestEntity);
throw e;
Expand Down Expand Up @@ -396,7 +396,7 @@ private void updateRootNetworkCaseInfos(UUID studyUuid, RootNetworkInfos rootNet
UUID clonedCaseUuid = caseService.duplicateCase(rootNetworkInfos.getCaseInfos().getOriginalCaseUuid(), true);
rootNetworkInfos.getCaseInfos().setCaseUuid(clonedCaseUuid);
try {
persistNetwork(rootNetworkInfos, studyUuid, null, userId, rootNetworkInfos.getImportParametersRaw(), CaseImportAction.ROOT_NETWORK_MODIFICATION);
persistNetwork(rootNetworkInfos, studyUuid, null, userId, rootNetworkInfos.getImportParameters(), CaseImportAction.ROOT_NETWORK_MODIFICATION);
} catch (Exception e) {
rootNetworkService.deleteRootNetworkRequest(rootNetworkModificationRequestEntity);
throw e;
Expand Down Expand Up @@ -627,7 +627,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 @@ -1543,7 +1543,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>
3 changes: 3 additions & 0 deletions src/main/resources/db/changelog/db.changelog-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,6 @@ databaseChangeLog:
- include:
file: changesets/changelog_20260319T184344Z.xml
relativeToChangelogFile: true
- include:
file: changesets/changelog_20260519T112945Z.xml
relativeToChangelogFile: true
99 changes: 99 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,99 @@
/**
* 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 testSerializeImportParameters() {
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 testSerializeNull() {
Map<String, String> result = JsonUtils.serializeImportParameters(null);
assertNotNull(result);
assertTrue(result.isEmpty());
}

@Test
void testNonSerializableParameter() {
class NonSerializable {
}

Map<String, Object> nonSerializableParams = Map.of("nonSerializable", new NonSerializable());
StudyException exception = assertThrows(StudyException.class, () -> JsonUtils.serializeImportParameters(nonSerializableParams));
assertEquals(StudyBusinessErrorCode.UNPROCESSABLE_IMPORT_PARAMETER, exception.getBusinessErrorCode());
assertTrue(exception.getMessage().contains("Import parameter 'nonSerializable =>"));
}

@Test
void testDeserializeImportParameters() {
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 testDeserializeNull() {
Map<String, Object> result = JsonUtils.deserializeImportParameters(null);
assertNotNull(result);
assertTrue(result.isEmpty());
}

@Test
void testDeserializeInvalidJson() {
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