From b04a6a27e985841bf96128a48f1da79790bd9c64 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Thu, 4 Jun 2026 17:53:24 +0200 Subject: [PATCH 01/19] transmit CompositesToBeInserted Signed-off-by: Mathieu DEHARBE --- .../study/server/controller/StudyController.java | 10 +++++----- .../server/service/NetworkModificationService.java | 4 ++-- .../gridsuite/study/server/service/StudyService.java | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index c01516898..54e9b2786 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -691,7 +691,7 @@ public ResponseEntity moveOrCopyModifications(@PathVariable("studyUuid") U } /** - * @param modificationsToInsert pair of the composite uuid and its name + * @param compositesToBeInserted is a List but there is no need to create a specific dto here, the data is only useful in network-modification-server */ @PutMapping(value = "/studies/{studyUuid}/nodes/{nodeUuid}/composite-modifications", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "For a list of composite network modifications passed in body, insert them into the target node") @@ -699,19 +699,19 @@ public ResponseEntity moveOrCopyModifications(@PathVariable("studyUuid") U public ResponseEntity insertCompositeModifications(@PathVariable("studyUuid") UUID studyUuid, @PathVariable("nodeUuid") UUID nodeUuid, @RequestParam("action") CompositeModificationsActionType action, - @RequestBody List> modificationsToInsert, + @RequestBody List compositesToBeInserted, @RequestHeader(HEADER_USER_ID) String userId) { studyService.assertIsStudyAndNodeExist(studyUuid, nodeUuid); studyService.assertCanUpdateNodeInStudy(studyUuid, nodeUuid); - handleInsertCompositeNetworkModifications(studyUuid, nodeUuid, modificationsToInsert, userId, action); + handleInsertCompositeNetworkModifications(studyUuid, nodeUuid, compositesToBeInserted, userId, action); return ResponseEntity.ok().build(); } - private void handleInsertCompositeNetworkModifications(UUID targetStudyUuid, UUID targetNodeUuid, List> modificationsToCopy, String userId, CompositeModificationsActionType action) { + private void handleInsertCompositeNetworkModifications(UUID targetStudyUuid, UUID targetNodeUuid, List compositesToBeInserted, String userId, CompositeModificationsActionType action) { studyService.assertNoBlockedNodeInStudy(targetStudyUuid, targetNodeUuid); studyService.invalidateNodeTreeWithLF(targetStudyUuid, targetNodeUuid); try { - studyService.insertCompositeNetworkModifications(targetStudyUuid, targetNodeUuid, modificationsToCopy, userId, action); + studyService.insertCompositeNetworkModifications(targetStudyUuid, targetNodeUuid, compositesToBeInserted, userId, action); } finally { studyService.unblockNodeTree(targetStudyUuid, targetNodeUuid); } diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java index ddb37da6f..f55047adf 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java @@ -284,13 +284,13 @@ public NetworkModificationsResult duplicateModifications(UUID groupUuid, public NetworkModificationsResult insertCompositeModifications(UUID groupUuid, CompositeModificationsActionType action, - Pair>, List> modificationContextInfos) { + Pair, List> modificationContextInfos) { var path = UriComponentsBuilder.fromPath(COMPOSITE_PATH + GROUP_PATH) .queryParam(QUERY_PARAM_ACTION, action.name()); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity>, List>> httpEntity = new HttpEntity<>(modificationContextInfos, headers); + HttpEntity, List>> httpEntity = new HttpEntity<>(modificationContextInfos, headers); return restTemplate.exchange( getNetworkModificationServerURI(false) + path.buildAndExpand(groupUuid).toUriString(), diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 4fa16f7a3..288f9ccb6 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -2337,7 +2337,7 @@ public void duplicateNetworkModifications( public void insertCompositeNetworkModifications( UUID targetStudyUuid, UUID targetNodeUuid, - List> compositesInfos, + List compositesInfos, String userId, StudyConstants.CompositeModificationsActionType action) { duplicateModificationsOrInsertComposites(targetStudyUuid, targetNodeUuid, From a86102771e0e4380fad163ff7f1d7d9d33e56dcc Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 12 Jun 2026 11:40:48 +0200 Subject: [PATCH 02/19] addReferencesToNewSharedComposites in directory service Signed-off-by: Mathieu DEHARBE --- .../server/controller/StudyController.java | 5 ++- .../study/server/dto/ReferenceAttributes.java | 37 +++++++++++++++++++ .../modification/CompositesToBeInserted.java | 15 ++++++++ .../server/service/DirectoryService.java | 27 ++++++++++++++ .../service/NetworkModificationService.java | 5 ++- .../study/server/service/StudyService.java | 18 ++++++--- 6 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/gridsuite/study/server/dto/ReferenceAttributes.java create mode 100644 src/main/java/org/gridsuite/study/server/dto/modification/CompositesToBeInserted.java diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 54e9b2786..086cc88c7 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -27,6 +27,7 @@ import org.gridsuite.study.server.dto.dynamicsimulation.DynamicSimulationStatus; import org.gridsuite.study.server.dto.dynamicsimulation.event.EventInfos; import org.gridsuite.study.server.dto.elasticsearch.EquipmentInfos; +import org.gridsuite.study.server.dto.modification.CompositesToBeInserted; import org.gridsuite.study.server.dto.modification.ModificationType; import org.gridsuite.study.server.dto.modification.ModificationsSearchResultByNode; import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; @@ -699,7 +700,7 @@ public ResponseEntity moveOrCopyModifications(@PathVariable("studyUuid") U public ResponseEntity insertCompositeModifications(@PathVariable("studyUuid") UUID studyUuid, @PathVariable("nodeUuid") UUID nodeUuid, @RequestParam("action") CompositeModificationsActionType action, - @RequestBody List compositesToBeInserted, + @RequestBody List compositesToBeInserted, @RequestHeader(HEADER_USER_ID) String userId) { studyService.assertIsStudyAndNodeExist(studyUuid, nodeUuid); studyService.assertCanUpdateNodeInStudy(studyUuid, nodeUuid); @@ -707,7 +708,7 @@ public ResponseEntity insertCompositeModifications(@PathVariable("studyUui return ResponseEntity.ok().build(); } - private void handleInsertCompositeNetworkModifications(UUID targetStudyUuid, UUID targetNodeUuid, List compositesToBeInserted, String userId, CompositeModificationsActionType action) { + private void handleInsertCompositeNetworkModifications(UUID targetStudyUuid, UUID targetNodeUuid, List compositesToBeInserted, String userId, CompositeModificationsActionType action) { studyService.assertNoBlockedNodeInStudy(targetStudyUuid, targetNodeUuid); studyService.invalidateNodeTreeWithLF(targetStudyUuid, targetNodeUuid); try { diff --git a/src/main/java/org/gridsuite/study/server/dto/ReferenceAttributes.java b/src/main/java/org/gridsuite/study/server/dto/ReferenceAttributes.java new file mode 100644 index 000000000..7e853e547 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/ReferenceAttributes.java @@ -0,0 +1,37 @@ +/* + Copyright (c) 2026, RTE (http://www.rte-france.com) + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.study.server.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +import java.util.UUID; + +/** + * @author Mathieu Deharbe + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@SuperBuilder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ReferenceAttributes { + public enum ReferenceType { + STUDY_NODE, + NETWORK_MODIFICATION, + DIRECTORY_ELEMENT + } + + private UUID referenceId; + private ReferenceType referenceType; +} + diff --git a/src/main/java/org/gridsuite/study/server/dto/modification/CompositesToBeInserted.java b/src/main/java/org/gridsuite/study/server/dto/modification/CompositesToBeInserted.java new file mode 100644 index 000000000..132854e13 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/modification/CompositesToBeInserted.java @@ -0,0 +1,15 @@ +/* + Copyright (c) 2026, RTE (http://www.rte-france.com) + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.study.server.dto.modification; + +import java.util.UUID; + +/** + * @author Mathieu Deharbe + */ +public record CompositesToBeInserted(UUID id, String name, boolean isShared) { } + diff --git a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java index 7512e5e24..cc82c014e 100644 --- a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java +++ b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java @@ -10,6 +10,8 @@ import lombok.Setter; import org.gridsuite.study.server.RemoteServicesProperties; import org.gridsuite.study.server.dto.ElementAttributes; +import org.gridsuite.study.server.dto.ReferenceAttributes; +import org.gridsuite.study.server.dto.modification.CompositesToBeInserted; import org.gridsuite.study.server.dto.networkexport.PermissionType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; @@ -27,6 +29,7 @@ import java.util.*; import static org.gridsuite.study.server.StudyConstants.*; +import static org.gridsuite.study.server.dto.ReferenceAttributes.ReferenceType.STUDY_NODE; /** * @author David Braquart @@ -99,6 +102,30 @@ public boolean elementExists(UUID directoryUuid, String elementName, String type return response.getStatusCode() == HttpStatus.OK; } + /** + * + * @param compositesInfos all the shared composites that have been inserted and need to be referenced in directory server + * @param userId id of the user who started the insertion + * @param targetNodeUuid where the new reference will point + */ + public void addReferencesToNewSharedComposites(List compositesInfos, String userId, UUID targetNodeUuid) { + compositesInfos.forEach(composite -> { + var path = UriComponentsBuilder.fromPath( + DELIMITER + DIRECTORY_API_VERSION + DELIMITER + "elements/{elementUuid}/references") + .buildAndExpand(composite.id()) + .toUriString(); + + HttpHeaders headers = new HttpHeaders(); + headers.set("userId", userId); + headers.setContentType(MediaType.APPLICATION_JSON); + + ReferenceAttributes referenceAttributes = new ReferenceAttributes(targetNodeUuid, STUDY_NODE); + + HttpEntity requestEntity = new HttpEntity<>(referenceAttributes, headers); + restTemplate.exchange(getDirectoryServerServerBaseUri() + path, HttpMethod.POST, requestEntity, ElementAttributes.class); + }); + } + public void createElement(UUID directoryUuid, String description, UUID elementUuid, String elementName, String type, String userId) { UriComponentsBuilder pathBuilder = UriComponentsBuilder.fromPath(DELIMITER + DIRECTORY_API_VERSION + "/directories/{directoryUuid}/elements"); ElementAttributes elementAttributes = new ElementAttributes(elementUuid, elementName, type, userId, 0, description); diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java index f55047adf..e0b2f4162 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java @@ -13,6 +13,7 @@ import org.gridsuite.study.server.StudyConstants; import org.gridsuite.study.server.dto.BuildInfos; import org.gridsuite.study.server.dto.NodeReceiver; +import org.gridsuite.study.server.dto.modification.CompositesToBeInserted; import org.gridsuite.study.server.dto.modification.ModificationApplicationContext; import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; import org.gridsuite.study.server.dto.modification.NetworkModificationsResult; @@ -284,13 +285,13 @@ public NetworkModificationsResult duplicateModifications(UUID groupUuid, public NetworkModificationsResult insertCompositeModifications(UUID groupUuid, CompositeModificationsActionType action, - Pair, List> modificationContextInfos) { + Pair, List> modificationContextInfos) { var path = UriComponentsBuilder.fromPath(COMPOSITE_PATH + GROUP_PATH) .queryParam(QUERY_PARAM_ACTION, action.name()); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity, List>> httpEntity = new HttpEntity<>(modificationContextInfos, headers); + HttpEntity, List>> httpEntity = new HttpEntity<>(modificationContextInfos, headers); return restTemplate.exchange( getNetworkModificationServerURI(false) + path.buildAndExpand(groupUuid).toUriString(), diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 288f9ccb6..886b20a39 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -2337,13 +2337,21 @@ public void duplicateNetworkModifications( public void insertCompositeNetworkModifications( UUID targetStudyUuid, UUID targetNodeUuid, - List compositesInfos, + List compositesInfos, String userId, StudyConstants.CompositeModificationsActionType action) { - duplicateModificationsOrInsertComposites(targetStudyUuid, targetNodeUuid, - (groupUuid, modificationApplicationContexts) -> - networkModificationService.insertCompositeModifications(groupUuid, action, Pair.of(compositesInfos, modificationApplicationContexts)), - userId); + // is some of the inserted modifications are shared, references have to be created in directory server + if (action == StudyConstants.CompositeModificationsActionType.INSERT && !compositesInfos.stream().filter(CompositesToBeInserted::isShared).toList().isEmpty()) { + directoryService.addReferencesToNewSharedComposites(compositesInfos, userId, targetNodeUuid); + } + + duplicateModificationsOrInsertComposites( + targetStudyUuid, + targetNodeUuid, + (groupUuid, modificationApplicationContexts) -> + networkModificationService.insertCompositeModifications(groupUuid, action, Pair.of(compositesInfos, modificationApplicationContexts)), + userId + ); } private void duplicateModificationsOrInsertComposites( From 00bb4ae5e2eb345290bb6e72ae37f4502bd4dbf0 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 12 Jun 2026 17:13:55 +0200 Subject: [PATCH 03/19] stash / unstash Signed-off-by: Mathieu DEHARBE --- .../server/controller/StudyController.java | 2 +- .../server/service/DirectoryService.java | 26 ++++++++++++++----- .../service/NetworkModificationService.java | 18 ++++++++++--- .../study/server/service/StudyService.java | 17 +++++++++--- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 9897bd256..0eb52b56c 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -1422,7 +1422,7 @@ public ResponseEntity deleteNetworkModifications(@Parameter(description = } @PutMapping(value = "/studies/{studyUuid}/nodes/{nodeUuid}/network-modifications", params = "stashed") - @Operation(summary = "Stash network modifications for a node") + @Operation(summary = "Stash or restore network modifications for a node") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The network modifications were stashed / restored "), @ApiResponse(responseCode = "404", description = "The study/node is not found")}) public ResponseEntity stashNetworkModifications(@Parameter(description = "Study UUID") @PathVariable("studyUuid") UUID studyUuid, @Parameter(description = "Node UUID") @PathVariable("nodeUuid") UUID nodeUuid, diff --git a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java index cc82c014e..bbc6806b7 100644 --- a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java +++ b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java @@ -11,7 +11,6 @@ import org.gridsuite.study.server.RemoteServicesProperties; import org.gridsuite.study.server.dto.ElementAttributes; import org.gridsuite.study.server.dto.ReferenceAttributes; -import org.gridsuite.study.server.dto.modification.CompositesToBeInserted; import org.gridsuite.study.server.dto.networkexport.PermissionType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; @@ -104,15 +103,15 @@ public boolean elementExists(UUID directoryUuid, String elementName, String type /** * - * @param compositesInfos all the shared composites that have been inserted and need to be referenced in directory server + * @param elementsUuids all the element uuids of the shared composites that need to be referenced in directory server * @param userId id of the user who started the insertion - * @param targetNodeUuid where the new reference will point + * @param targetNodeUuid where the new references will point */ - public void addReferencesToNewSharedComposites(List compositesInfos, String userId, UUID targetNodeUuid) { - compositesInfos.forEach(composite -> { + public void addReferencesToSharedComposites(List elementsUuids, String userId, UUID targetNodeUuid) { + elementsUuids.forEach(elementUuid -> { var path = UriComponentsBuilder.fromPath( DELIMITER + DIRECTORY_API_VERSION + DELIMITER + "elements/{elementUuid}/references") - .buildAndExpand(composite.id()) + .buildAndExpand(elementUuid) .toUriString(); HttpHeaders headers = new HttpHeaders(); @@ -126,6 +125,21 @@ public void addReferencesToNewSharedComposites(List comp }); } + public void removeReference(UUID referenceUuid, String userId, UUID sharedElementUuid) { + var path = UriComponentsBuilder.fromPath( + DELIMITER + DIRECTORY_API_VERSION + DELIMITER + "elements/{elementUuid}/references") + .queryParam("referenceUuid", referenceUuid) + .buildAndExpand(sharedElementUuid) + .toUriString(); + + HttpHeaders headers = new HttpHeaders(); + headers.set("userId", userId); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity requestEntity = new HttpEntity<>(headers); + restTemplate.exchange(getDirectoryServerServerBaseUri() + path, HttpMethod.DELETE, requestEntity, ElementAttributes.class); + } + public void createElement(UUID directoryUuid, String description, UUID elementUuid, String elementName, String type, String userId) { UriComponentsBuilder pathBuilder = UriComponentsBuilder.fromPath(DELIMITER + DIRECTORY_API_VERSION + "/directories/{directoryUuid}/elements"); ElementAttributes elementAttributes = new ElementAttributes(elementUuid, elementName, type, userId, 0, description); diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java index e5e9cdcd4..0c10ca32d 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java @@ -170,7 +170,7 @@ public void updateModification(String createEquipmentAttributes, UUID modificati restTemplate.exchange(path, HttpMethod.PUT, httpEntity, Void.class); } - public void stashModifications(UUID groupUUid, List modificationsUuids) { + public Map stashModifications(UUID groupUUid, List modificationsUuids) { Objects.requireNonNull(groupUUid); Objects.requireNonNull(modificationsUuids); var path = UriComponentsBuilder @@ -185,7 +185,12 @@ public void stashModifications(UUID groupUUid, List modificationsUuids) { headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity httpEntity = new HttpEntity<>(headers); - restTemplate.exchange(path, HttpMethod.PUT, httpEntity, Void.class); + return restTemplate.exchange( + path, + HttpMethod.PUT, + httpEntity, + new ParameterizedTypeReference>() { } + ).getBody(); } public void updateModificationsMetadata(UUID groupUUid, List modificationsUuids, NetworkModificationMetadata metadata) { @@ -205,7 +210,7 @@ public void updateModificationsMetadata(UUID groupUUid, List modifications restTemplate.exchange(path, HttpMethod.PUT, httpEntity, Void.class); } - public void restoreModifications(UUID groupUUid, List modificationsUuids) { + public Map restoreModifications(UUID groupUUid, List modificationsUuids) { Objects.requireNonNull(groupUUid); Objects.requireNonNull(modificationsUuids); var path = UriComponentsBuilder @@ -221,7 +226,12 @@ public void restoreModifications(UUID groupUUid, List modificationsUuids) HttpEntity httpEntity = new HttpEntity<>(headers); - restTemplate.exchange(path, HttpMethod.PUT, httpEntity, Void.class); + return restTemplate.exchange( + path, + HttpMethod.PUT, + httpEntity, + new ParameterizedTypeReference>() { } + ).getBody(); } public void buildNode(@NonNull UUID nodeUuid, @NonNull UUID rootNetworkUuid, @NonNull BuildInfos buildInfos, AbstractWorkflowInfos workflowInfos) { diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 76a452880..703a8062e 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -2011,7 +2011,12 @@ public void stashNetworkModifications(UUID studyUuid, UUID nodeUuid, List throw new StudyException(NOT_ALLOWED); } UUID groupId = networkModificationTreeService.getModificationGroupUuid(nodeUuid); - networkModificationService.stashModifications(groupId, modificationsUuids); + + Map referenceToBeDeleted = networkModificationService.stashModifications(groupId, modificationsUuids); + // if there are references modifications in the stashed netmods, those references have to be removed from directory server + referenceToBeDeleted.forEach((modUuid, refUuid) -> { + directoryService.removeReference(refUuid != null ? refUuid : nodeUuid, userId, modUuid); + }); invalidateNodeTree(studyUuid, nodeUuid); } finally { notificationService.emitEndModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids); @@ -2066,7 +2071,13 @@ public void restoreNetworkModifications(UUID studyUuid, UUID nodeUuid, List referenceToBeRecreated = networkModificationService.restoreModifications(groupId, modificationsUuids); + // if there are references modifications in the unstashed netmods, those references had been removed and must be recreated in directory server + directoryService.addReferencesToSharedComposites( + referenceToBeRecreated.keySet().stream().toList(), + userId, + nodeUuid + ); invalidateNodeTree(studyUuid, nodeUuid); } finally { notificationService.emitEndModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids); @@ -2361,7 +2372,7 @@ public void insertCompositeNetworkModifications( StudyConstants.CompositeModificationsActionType action) { // is some of the inserted modifications are shared, references have to be created in directory server if (action == StudyConstants.CompositeModificationsActionType.INSERT && !compositesInfos.stream().filter(CompositesToBeInserted::isShared).toList().isEmpty()) { - directoryService.addReferencesToNewSharedComposites(compositesInfos, userId, targetNodeUuid); + directoryService.addReferencesToSharedComposites(compositesInfos.stream().map(CompositesToBeInserted::id).toList(), userId, targetNodeUuid); } duplicateModificationsOrInsertComposites( From e97e24de2fed032f15327c70f591b470b5f4f008 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Tue, 16 Jun 2026 18:16:20 +0200 Subject: [PATCH 04/19] getReferencesData Signed-off-by: Mathieu DEHARBE --- .../server/service/DirectoryService.java | 5 ++-- .../service/NetworkModificationService.java | 29 +++++++++++++------ .../study/server/service/StudyService.java | 6 ++-- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java index bbc6806b7..e06626aed 100644 --- a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java +++ b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java @@ -127,9 +127,8 @@ public void addReferencesToSharedComposites(List elementsUuids, String use public void removeReference(UUID referenceUuid, String userId, UUID sharedElementUuid) { var path = UriComponentsBuilder.fromPath( - DELIMITER + DIRECTORY_API_VERSION + DELIMITER + "elements/{elementUuid}/references") - .queryParam("referenceUuid", referenceUuid) - .buildAndExpand(sharedElementUuid) + DELIMITER + DIRECTORY_API_VERSION + DELIMITER + "elements/{elementUuid}/references/{referenceUuid}") + .buildAndExpand(sharedElementUuid, referenceUuid) .toUriString(); HttpHeaders headers = new HttpHeaders(); diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java index 0c10ca32d..5a6da5b73 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java @@ -170,7 +170,7 @@ public void updateModification(String createEquipmentAttributes, UUID modificati restTemplate.exchange(path, HttpMethod.PUT, httpEntity, Void.class); } - public Map stashModifications(UUID groupUUid, List modificationsUuids) { + public void stashModifications(UUID groupUUid, List modificationsUuids) { Objects.requireNonNull(groupUUid); Objects.requireNonNull(modificationsUuids); var path = UriComponentsBuilder @@ -185,12 +185,7 @@ public Map stashModifications(UUID groupUUid, List modificatio headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity httpEntity = new HttpEntity<>(headers); - return restTemplate.exchange( - path, - HttpMethod.PUT, - httpEntity, - new ParameterizedTypeReference>() { } - ).getBody(); + restTemplate.exchange(path, HttpMethod.PUT, httpEntity, Void.class); } public void updateModificationsMetadata(UUID groupUUid, List modificationsUuids, NetworkModificationMetadata metadata) { @@ -210,7 +205,7 @@ public void updateModificationsMetadata(UUID groupUUid, List modifications restTemplate.exchange(path, HttpMethod.PUT, httpEntity, Void.class); } - public Map restoreModifications(UUID groupUUid, List modificationsUuids) { + public void restoreModifications(UUID groupUUid, List modificationsUuids) { Objects.requireNonNull(groupUUid); Objects.requireNonNull(modificationsUuids); var path = UriComponentsBuilder @@ -226,9 +221,25 @@ public Map restoreModifications(UUID groupUUid, List modificat HttpEntity httpEntity = new HttpEntity<>(headers); + restTemplate.exchange(path, HttpMethod.PUT, httpEntity, Void.class); + } + + public Map getReferencesData(List modificationsUuids) { + Objects.requireNonNull(modificationsUuids); + var path = UriComponentsBuilder + .fromUriString(getNetworkModificationServerURI(false) + "references") + .queryParam(UUIDS, modificationsUuids) + .buildAndExpand() + .toUriString(); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity httpEntity = new HttpEntity<>(headers); + return restTemplate.exchange( path, - HttpMethod.PUT, + HttpMethod.GET, httpEntity, new ParameterizedTypeReference>() { } ).getBody(); diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 703a8062e..c3fb1ed32 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -2012,7 +2012,8 @@ public void stashNetworkModifications(UUID studyUuid, UUID nodeUuid, List } UUID groupId = networkModificationTreeService.getModificationGroupUuid(nodeUuid); - Map referenceToBeDeleted = networkModificationService.stashModifications(groupId, modificationsUuids); + Map referenceToBeDeleted = networkModificationService.getReferencesData(modificationsUuids); + networkModificationService.stashModifications(groupId, modificationsUuids); // if there are references modifications in the stashed netmods, those references have to be removed from directory server referenceToBeDeleted.forEach((modUuid, refUuid) -> { directoryService.removeReference(refUuid != null ? refUuid : nodeUuid, userId, modUuid); @@ -2071,7 +2072,8 @@ public void restoreNetworkModifications(UUID studyUuid, UUID nodeUuid, List referenceToBeRecreated = networkModificationService.restoreModifications(groupId, modificationsUuids); + Map referenceToBeRecreated = networkModificationService.getReferencesData(modificationsUuids); + networkModificationService.restoreModifications(groupId, modificationsUuids); // if there are references modifications in the unstashed netmods, those references had been removed and must be recreated in directory server directoryService.addReferencesToSharedComposites( referenceToBeRecreated.keySet().stream().toList(), From 09e9813801c5a2b072a770b8bbd897c29523fe0c Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Wed, 17 Jun 2026 14:01:21 +0200 Subject: [PATCH 05/19] remove references when deleting studies and nodes Signed-off-by: Mathieu DEHARBE --- .../study/server/dto/DeleteStudyInfos.java | 3 +- .../service/NetworkModificationService.java | 19 +++++++++- .../study/server/service/StudyService.java | 37 +++++++++++++++++-- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/dto/DeleteStudyInfos.java b/src/main/java/org/gridsuite/study/server/dto/DeleteStudyInfos.java index a84124f9d..96e6f2325 100644 --- a/src/main/java/org/gridsuite/study/server/dto/DeleteStudyInfos.java +++ b/src/main/java/org/gridsuite/study/server/dto/DeleteStudyInfos.java @@ -8,6 +8,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import org.springframework.data.util.Pair; import java.util.List; import java.util.UUID; @@ -22,5 +23,5 @@ public class DeleteStudyInfos { private List rootNetworkInfosList; - private List modificationGroupUuids; + private List> modificationGroupUuidsNodeUuids; } diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java index 5a6da5b73..781ce7031 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java @@ -235,7 +235,7 @@ public Map getReferencesData(List modificationsUuids) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity httpEntity = new HttpEntity<>(headers); + HttpEntity> httpEntity = new HttpEntity<>(headers); return restTemplate.exchange( path, @@ -245,6 +245,23 @@ public Map getReferencesData(List modificationsUuids) { ).getBody(); } + public Map getAllReferencesDataFromGroup(UUID groupUuid) { + Objects.requireNonNull(groupUuid); + var path = UriComponentsBuilder.fromPath(GROUP_PATH + DELIMITER + "references"); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity> httpEntity = new HttpEntity<>(headers); + + return restTemplate.exchange( + getNetworkModificationServerURI(false) + path.buildAndExpand(groupUuid).toUriString(), + HttpMethod.GET, + httpEntity, + new ParameterizedTypeReference>() { } + ).getBody(); + } + public void buildNode(@NonNull UUID nodeUuid, @NonNull UUID rootNetworkUuid, @NonNull BuildInfos buildInfos, AbstractWorkflowInfos workflowInfos) { UUID networkUuid = rootNetworkService.getNetworkUuid(rootNetworkUuid); String receiver = buildReceiver(nodeUuid, rootNetworkUuid); diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 7cfd23698..a4b8418c7 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -81,6 +81,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import static org.gridsuite.study.server.StudyConstants.BUS_ID_TO_ICC_VALUES; @@ -548,7 +549,10 @@ private Optional doDeleteStudyIfNotCreationInProgress(UUID stu if (studyCreationRequestEntity.isEmpty()) { List rootNetworkInfos = getStudyRootNetworksInfos(studyUuid); // get all modification groups related to the study - List modificationGroupUuids = networkModificationTreeService.getAllStudyNetworkModificationNodeInfo(studyUuid).stream().map(NetworkModificationNodeInfoEntity::getModificationGroupUuid).toList(); + List allStudyNetworkModificationNodeInfo = networkModificationTreeService.getAllStudyNetworkModificationNodeInfo(studyUuid); + List> modificationGroupUuidsNodeUuids = allStudyNetworkModificationNodeInfo.stream() + .map(nodeInfoEntity -> Pair.of(nodeInfoEntity.getModificationGroupUuid(),nodeInfoEntity.getIdNode())) + .toList(); studyEntity.ifPresent(s -> { networkModificationTreeService.doDeleteTree(studyUuid); studyRepository.deleteById(studyUuid); @@ -559,7 +563,7 @@ private Optional doDeleteStudyIfNotCreationInProgress(UUID stu removeWorkspacesConfig(s.getWorkspacesConfigUuid()); removeNadConfigs(s.getNadConfigsUuids().stream().toList()); }); - deleteStudyInfos = new DeleteStudyInfos(rootNetworkInfos, modificationGroupUuids); + deleteStudyInfos = new DeleteStudyInfos(rootNetworkInfos, modificationGroupUuidsNodeUuids); } else { studyCreationRequestRepository.deleteById(studyCreationRequestEntity.get().getId()); } @@ -593,13 +597,25 @@ public void deleteStudyIfNotCreationInProgress(UUID studyUuid) { rootNetworkService.invalidateRootNetworkRemoteInfos(deleteStudyInfos.getRootNetworkInfosList(), false, true); // delete all distant resources linked to nodes - studyServerExecutionService.runAsync(() -> deleteStudyInfos.getModificationGroupUuids().stream().filter(Objects::nonNull).forEach(networkModificationService::deleteModifications)); + studyServerExecutionService.runAsync(() -> deleteStudyInfos.getModificationGroupUuidsNodeUuids().stream() + .filter(Objects::nonNull) + .forEach(this::deleteModificationsFromGroup)); LOGGER.trace("Delete study '{}' : {} seconds", studyUuid, TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime.get())); } } + private void deleteModificationsFromGroup(Pair groupUuidNodeUuid) { + // fetch the references data in order to remove those references from directory-server + Map referenceToBeDeleted = networkModificationService.getAllReferencesDataFromGroup(groupUuidNodeUuid.getFirst()); + referenceToBeDeleted.forEach((modUuid, refUuid) -> { + directoryService.removeReference(refUuid != null ? refUuid : groupUuidNodeUuid.getSecond(), "userId", modUuid); // TODO : handle userId ?? voir avec Slimane + }); + + networkModificationService.deleteModifications(groupUuidNodeUuid.getFirst()); + } + @Transactional public CreatedStudyBasicInfos insertStudy(UUID studyUuid, String userId, NetworkInfos networkInfos, CaseInfos caseInfos, ComputationParameterUUIDs computationParameterUUIDs, UUID networkVisualizationParametersUuid, @@ -1993,7 +2009,15 @@ public void deleteNetworkModifications(UUID studyUuid, UUID nodeUuid, List throw new StudyException(NOT_ALLOWED); } UUID groupId = networkModificationTreeService.getModificationGroupUuid(nodeUuid); + + Map referenceToBeDeleted = networkModificationService.getReferencesData(modificationsUuids); networkModificationService.deleteModifications(groupId, modificationsUuids); + // if there are unstashed references modifications in the deleted netmods, those references have to be removed from directory server + referenceToBeDeleted.forEach((modUuid, refUuid) -> { + directoryService.removeReference(refUuid != null ? refUuid : nodeUuid, userId, modUuid); + }); + invalidateNodeTree(studyUuid, nodeUuid); + // for each root network, remove modifications from excluded ones studyEntity.getRootNetworks().forEach(rootNetworkEntity -> rootNetworkNodeInfoService.updateModificationsToExclude(nodeUuid, rootNetworkEntity.getId(), new HashSet<>(modificationsUuids), true)); } finally { @@ -2155,7 +2179,12 @@ private CompletableFuture deleteInvalidationInfos(InvalidateNodeInfos inva private void deleteNodesInfos(DeleteNodeInfos deleteNodeInfos) { List> futures = new ArrayList<>(); futures.add(studyServerExecutionService.runAsync(() -> deleteNodeInfos.getVariantIds().forEach(networkStoreService::deleteVariants))); - futures.add(studyServerExecutionService.runAsync(() -> deleteNodeInfos.getModificationGroupUuids().forEach(networkModificationService::deleteModifications))); + List modificationGroupUuids = deleteNodeInfos.getModificationGroupUuids(); + List removedNodeUuids = deleteNodeInfos.getRemovedNodeUuids(); + List> modificationGroupUuidsNodeUuids = IntStream.range(0, modificationGroupUuids.size()) + .mapToObj(index -> Pair.of(modificationGroupUuids.get(index), removedNodeUuids.get(index))) + .toList(); + futures.add(studyServerExecutionService.runAsync(() -> modificationGroupUuidsNodeUuids.forEach(this::deleteModificationsFromGroup))); futures.add(studyServerExecutionService.runAsync(() -> deleteNodeInfos.getRemovedNodeUuids().forEach(dynamicSimulationEventService::deleteEventsByNodeId))); futures.addAll(rootNetworkNodeInfoService.getRemoteDeletions(deleteNodeInfos)); // Do not wait completion and do not throw exception From e434747b800cc18caf3e103c7e7bdddd8bce267c Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Thu, 18 Jun 2026 12:06:03 +0200 Subject: [PATCH 06/19] corrects a few TU including deleteModificationRequest Signed-off-by: Mathieu DEHARBE --- .../study/server/service/StudyService.java | 4 +--- .../study/server/NetworkModificationTest.java | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index a4b8418c7..367f0cfc2 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -551,7 +551,7 @@ private Optional doDeleteStudyIfNotCreationInProgress(UUID stu // get all modification groups related to the study List allStudyNetworkModificationNodeInfo = networkModificationTreeService.getAllStudyNetworkModificationNodeInfo(studyUuid); List> modificationGroupUuidsNodeUuids = allStudyNetworkModificationNodeInfo.stream() - .map(nodeInfoEntity -> Pair.of(nodeInfoEntity.getModificationGroupUuid(),nodeInfoEntity.getIdNode())) + .map(nodeInfoEntity -> Pair.of(nodeInfoEntity.getModificationGroupUuid(), nodeInfoEntity.getIdNode())) .toList(); studyEntity.ifPresent(s -> { networkModificationTreeService.doDeleteTree(studyUuid); @@ -2016,8 +2016,6 @@ public void deleteNetworkModifications(UUID studyUuid, UUID nodeUuid, List referenceToBeDeleted.forEach((modUuid, refUuid) -> { directoryService.removeReference(refUuid != null ? refUuid : nodeUuid, userId, modUuid); }); - invalidateNodeTree(studyUuid, nodeUuid); - // for each root network, remove modifications from excluded ones studyEntity.getRootNetworks().forEach(rootNetworkEntity -> rootNetworkNodeInfoService.updateModificationsToExclude(nodeUuid, rootNetworkEntity.getId(), new HashSet<>(modificationsUuids), true)); } finally { diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index 8394d4b7a..467fddacb 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -1123,6 +1123,13 @@ void testCreateTwoWindingsTransformer() throws Exception { void deleteModificationRequest() throws Exception { String userId = "userId"; + // stubs the checks and updates of referenced modifications : there are none here + UUID referencesStubId = wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo("/v1/references")) + .willReturn(WireMock.ok() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withBody("{}")) + ).getId(); + UUID stubId = wireMockServer.stubFor(WireMock.delete(WireMock.urlPathMatching("/v1/network-modifications")).willReturn(WireMock.ok())).getId(); StudyEntity studyEntity = insertDummyStudy(UUID.fromString(NETWORK_UUID_STRING), CASE_UUID, "UCTE"); @@ -1149,6 +1156,7 @@ void deleteModificationRequest() throws Exception { .queryParam("uuids", modificationUuid.toString()) .header(USER_ID_HEADER, userId)) .andExpect(status().isOk()); + WireMockUtils.verifyGetRequest(wireMockServer, referencesStubId, "/v1/references", Map.of("uuids", WireMock.equalTo(modificationUuid.toString()))); WireMockUtils.verifyDeleteRequest(wireMockServer, stubId, "/v1/network-modifications", false, Map.of("uuids", WireMock.equalTo(modificationUuid.toString()))); checkEquipmentDeletingMessagesReceived(studyUuid, modificationNode.getId()); checkEquipmentDeletingFinishedMessagesReceived(studyUuid, modificationNode.getId()); @@ -1160,6 +1168,7 @@ void deleteModificationRequest() throws Exception { .queryParam("uuids", modificationUuid.toString()) .header(USER_ID_HEADER, "userId")) .andExpect(status().isInternalServerError()); + WireMockUtils.verifyGetRequest(wireMockServer, referencesStubId, "/v1/references", Map.of("uuids", WireMock.equalTo(modificationUuid.toString()))); WireMockUtils.verifyDeleteRequest(wireMockServer, stubId, "/v1/network-modifications", false, Map.of("uuids", WireMock.equalTo(modificationUuid.toString()))); checkEquipmentDeletingMessagesReceived(studyUuid, modificationNode.getId()); checkEquipmentDeletingFinishedMessagesReceived(studyUuid, modificationNode.getId()); @@ -2089,8 +2098,8 @@ void testInsertComposite() throws Exception { NetworkModificationNode node1 = createNetworkModificationNode(studyUuid, rootNodeUuid, UUID.randomUUID(), VARIANT_ID, "New node 1", "userId"); UUID nodeUuid1 = node1.getId(); - Pair modification1 = Pair.of(UUID.randomUUID(), "composite 1"); - Pair modification2 = Pair.of(UUID.randomUUID(), "composite 2"); + CompositesToBeInserted modification1 = new CompositesToBeInserted(UUID.randomUUID(), "composite 1", false); + CompositesToBeInserted modification2 = new CompositesToBeInserted(UUID.randomUUID(), "composite 2", false); String compositesData = mapper.writeValueAsString( Arrays.asList( modification1, @@ -2116,7 +2125,7 @@ void testInsertComposite() throws Exception { checkEquipmentUpdatingFinishedMessagesReceived(studyUuid, nodeUuid1); checkElementUpdatedMessageSent(studyUuid, userId); - Pair>, List> modificationBody = + Pair, List> modificationBody = Pair.of( List.of(modification1, modification2), List.of(rootNetworkNodeInfoService.getNetworkModificationApplicationContext(firstRootNetworkUuid, node1.getId(), NETWORK_UUID) From 896d551b42626311b6df76dda95253f676ed6963 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Thu, 18 Jun 2026 15:08:48 +0200 Subject: [PATCH 07/19] corrects a few TU fromNetworkModificationTreeTest Signed-off-by: Mathieu DEHARBE --- .../gridsuite/study/server/service/DirectoryService.java | 3 ++- .../org/gridsuite/study/server/service/StudyService.java | 6 ++++-- .../gridsuite/study/server/NetworkModificationTreeTest.java | 6 ++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java index e06626aed..6e03a8e74 100644 --- a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java +++ b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java @@ -7,6 +7,7 @@ package org.gridsuite.study.server.service; import lombok.Getter; +import lombok.NonNull; import lombok.Setter; import org.gridsuite.study.server.RemoteServicesProperties; import org.gridsuite.study.server.dto.ElementAttributes; @@ -107,7 +108,7 @@ public boolean elementExists(UUID directoryUuid, String elementName, String type * @param userId id of the user who started the insertion * @param targetNodeUuid where the new references will point */ - public void addReferencesToSharedComposites(List elementsUuids, String userId, UUID targetNodeUuid) { + public void addReferencesToSharedComposites(@NonNull List elementsUuids, String userId, UUID targetNodeUuid) { elementsUuids.forEach(elementUuid -> { var path = UriComponentsBuilder.fromPath( DELIMITER + DIRECTORY_API_VERSION + DELIMITER + "elements/{elementUuid}/references") diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 367f0cfc2..c7a02414b 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -2097,11 +2097,13 @@ public void restoreNetworkModifications(UUID studyUuid, UUID nodeUuid, List referenceToBeRecreated = networkModificationService.getReferencesData(modificationsUuids); networkModificationService.restoreModifications(groupId, modificationsUuids); // if there are references modifications in the unstashed netmods, those references had been removed and must be recreated in directory server - directoryService.addReferencesToSharedComposites( + if (!referenceToBeRecreated.isEmpty()) { + directoryService.addReferencesToSharedComposites( referenceToBeRecreated.keySet().stream().toList(), userId, nodeUuid - ); + ); + } invalidateNodeTree(studyUuid, nodeUuid); } finally { notificationService.emitEndModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids); diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java index 511751764..3046dfb14 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java @@ -305,6 +305,12 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse(HttpStatus.OK.value()); } else if (path.matches("/v1/reports") && request.getMethod().equals("DELETE")) { return new MockResponse(HttpStatus.OK.value()); + } else if (path.matches("/v1/references\\?uuids=.*") && request.getMethod().equals("GET")) { + return new MockResponse( + HttpStatus.OK.value(), + Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), + "{}" + ); } else { LOGGER.error("Path not supported: {}", request.getPath()); return new MockResponse(HttpStatus.NOT_FOUND.value()); From 25d10db377043cf1ffe3265ce888306b483130be Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Thu, 18 Jun 2026 16:02:14 +0200 Subject: [PATCH 08/19] corrects TU testDeleteStudyWithNonExistingCase Signed-off-by: Mathieu DEHARBE --- .../server/service/NetworkModificationService.java | 2 +- .../study/server/studycontroller/StudyTest.java | 2 ++ .../study/server/utils/wiremock/WireMockStubs.java | 11 +++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java index 781ce7031..b1ecd31ae 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java @@ -69,7 +69,7 @@ public class NetworkModificationService { } public void setNetworkModificationServerBaseUri(String networkModificationServerBaseUri) { - this.networkModificationServerBaseUri = networkModificationServerBaseUri + DELIMITER; + this.networkModificationServerBaseUri = networkModificationServerBaseUri; } private String getNetworkModificationServerURI(boolean addNetworksPart) { diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java index d8e80caf0..0fba5ecfa 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java @@ -288,6 +288,7 @@ void testDeleteStudyWithNonExistingCase() throws Exception { UUID nonExistingCaseUuid = UUID.randomUUID(); UUID stubUuid = wireMockStubs.stubNetworkModificationDeleteGroup(); + UUID stubReferencesUuid = wireMockStubs.stubGetAllReferencesDataFromGroup(); // Changing the study case uuid with a non-existing case StudyEntity studyEntity = studyRepository.findById(studyUuid).orElse(null); @@ -304,6 +305,7 @@ void testDeleteStudyWithNonExistingCase() throws Exception { assertTrue(studyRepository.findById(studyUuid).isEmpty()); + wireMockStubs.verifyGetAllReferencesDataFromGroup(stubReferencesUuid); wireMockStubs.verifyNetworkModificationDeleteGroup(stubUuid, false); wireMockStubs.caseServer.verifyDeleteCase(stubDeleteCaseId, nonExistingCaseUuid.toString()); deleteStudyStubs.verify(wireMockStubs, computationServerStubs, 10); diff --git a/src/test/java/org/gridsuite/study/server/utils/wiremock/WireMockStubs.java b/src/test/java/org/gridsuite/study/server/utils/wiremock/WireMockStubs.java index 5dc39d607..94929eb10 100644 --- a/src/test/java/org/gridsuite/study/server/utils/wiremock/WireMockStubs.java +++ b/src/test/java/org/gridsuite/study/server/utils/wiremock/WireMockStubs.java @@ -238,6 +238,17 @@ public UUID stubNetworkModificationDeleteGroup() { ).getId(); } + public UUID stubGetAllReferencesDataFromGroup() { + return wireMock.stubFor(WireMock.get(WireMock.urlPathMatching(URI_NETWORK_MODIFICATION_GROUPS + DELIMITER + ".*/references")) + .willReturn(WireMock.ok() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .withBody("{}"))).getId(); + } + + public void verifyGetAllReferencesDataFromGroup(UUID getAllReferencesDataUuid) { + verifyGetRequest(wireMock, getAllReferencesDataUuid, URI_NETWORK_MODIFICATION_GROUPS + DELIMITER + ".*/references", true, Map.of(), 1); + } + public UUID stubNetworkModificationDeleteIndex() { return wireMock.stubFor(WireMock.delete(WireMock.urlPathMatching(URI_NETWORK_MODIFICATION + DELIMITER + "index.*")) .willReturn(WireMock.ok()) From 933f6f60dcf5a8cf389053dd17530f3a69e96771 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Thu, 18 Jun 2026 16:11:59 +0200 Subject: [PATCH 09/19] corrects last TU Signed-off-by: Mathieu DEHARBE --- .../org/gridsuite/study/server/studycontroller/StudyTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java index 0fba5ecfa..dd4b27647 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java @@ -239,12 +239,14 @@ void testDeleteStudy() throws Exception { UUID stubUuid = wireMockStubs.stubNetworkModificationDeleteGroup(); UUID stubDeleteCaseId = wireMockStubs.caseServer.stubDeleteCase(CASE_UUID_STRING); DeleteStudyStubs deleteStudyStubs = setupDeleteStudyStubs(); + UUID stubReferencesUuid = wireMockStubs.stubGetAllReferencesDataFromGroup(); mockMvc.perform(delete("/v1/studies/{studyUuid}", studyUuid).header(USER_ID_HEADER, "userId")) .andExpect(status().isOk()); assertTrue(studyRepository.findById(studyUuid).isEmpty()); + wireMockStubs.verifyGetAllReferencesDataFromGroup(stubReferencesUuid); wireMockStubs.verifyNetworkModificationDeleteGroup(stubUuid, false); wireMockStubs.caseServer.verifyDeleteCase(stubDeleteCaseId, CASE_UUID_STRING); deleteStudyStubs.verify(wireMockStubs, computationServerStubs, 10); // voltageInit, loadFlow, securityAnalysis, sensitivityAnalysis, stateEstimation, pccMin, dynamic, shortCircuit @@ -273,11 +275,13 @@ void testDeleteStudyWithError(final CapturedOutput capturedOutput) throws Except }).when(caseService).deleteCase(any()); UUID stubUuid = wireMockStubs.stubNetworkModificationDeleteGroup(); DeleteStudyStubs deleteStudyStubs = setupDeleteStudyStubs(); + UUID stubReferencesUuid = wireMockStubs.stubGetAllReferencesDataFromGroup(); mockMvc.perform(delete("/v1/studies/{studyUuid}", studyUuid).header(USER_ID_HEADER, "userId")) .andExpectAll(status().isOk()); assertTrue(capturedOutput.getOut().contains(StudyServerExecutionService.class.getName() + " - " + CompletionException.class.getName() + ": " + InterruptedException.class.getName())); + wireMockStubs.verifyGetAllReferencesDataFromGroup(stubReferencesUuid); wireMockStubs.verifyNetworkModificationDeleteGroup(stubUuid, false); deleteStudyStubs.verify(wireMockStubs, computationServerStubs, 3); // loadflow, security, sensitivity, stateEstimation, shortCircuit, pccMin } From fe574d9b3eddae5d1dea477be6cfdffb7d862524 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Thu, 18 Jun 2026 16:32:32 +0200 Subject: [PATCH 10/19] adds userId everywhere Signed-off-by: Mathieu DEHARBE --- .../server/controller/StudyController.java | 5 +++-- .../study/server/service/ConsumerService.java | 4 ++-- .../study/server/service/StudyService.java | 20 ++++++++++--------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 13493c873..9c73949a0 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -182,8 +182,9 @@ public ResponseEntity getStudy(@PathVariable("studyUuid" @DeleteMapping(value = "/studies/{studyUuid}") @Operation(summary = "delete the study") @ApiResponse(responseCode = "200", description = "Study deleted") - public ResponseEntity deleteStudy(@PathVariable("studyUuid") UUID studyUuid) { - studyService.deleteStudyIfNotCreationInProgress(studyUuid); + public ResponseEntity deleteStudy(@PathVariable("studyUuid") UUID studyUuid, + @RequestHeader(HEADER_USER_ID) String userId) { // TODO : aller ajouter ce param dans le front (où?) + studyService.deleteStudyIfNotCreationInProgress(studyUuid, userId); return ResponseEntity.ok().build(); } diff --git a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java index a256da6fb..0d6e4dc73 100644 --- a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java +++ b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java @@ -248,7 +248,7 @@ private void handleConsumeCaseImportSucceeded(CaseImportReceiver receiver, UUID } finally { // if studyEntity is already existing, we don't delete anything in the end of the process if (caseImportAction == CaseImportAction.STUDY_CREATION) { - studyService.deleteStudyIfNotCreationInProgress(studyUuid); + studyService.deleteStudyIfNotCreationInProgress(studyUuid, userId); } if (caseImportAction == CaseImportAction.ROOT_NETWORK_MODIFICATION) { UUID rootNodeUuid = networkModificationTreeService.getStudyRootNodeUuid(studyUuid); @@ -346,7 +346,7 @@ public Consumer> consumeCaseImportFailed() { UUID rootNetworkUuid = receiver.getRootNetworkUuid(); if (receiver.getCaseImportAction() == CaseImportAction.STUDY_CREATION) { - studyService.deleteStudyIfNotCreationInProgress(studyUuid); + studyService.deleteStudyIfNotCreationInProgress(studyUuid, userId); notificationService.emitStudyCreationError(studyUuid, userId, errorMessage); } else { if (receiver.getCaseImportAction() == CaseImportAction.ROOT_NETWORK_CREATION) { diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index c7a02414b..b2faf40b5 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -298,7 +298,7 @@ public BasicStudyInfos createStudy(UUID caseUuid, String userId, UUID studyUuid, persistNetwork(rootNetworkInfos, basicStudyInfos.getId(), NetworkModificationTreeService.FIRST_VARIANT_ID, userId, importParameters, CaseImportAction.STUDY_CREATION); } catch (Exception e) { - self.deleteStudyIfNotCreationInProgress(basicStudyInfos.getId()); + self.deleteStudyIfNotCreationInProgress(basicStudyInfos.getId(), userId); throw e; } @@ -492,7 +492,7 @@ public void duplicateStudyAsync(BasicStudyInfos basicStudyInfos, UUID sourceStud } catch (Exception e) { LOGGER.error(e.toString(), e); } finally { - self.deleteStudyIfNotCreationInProgress(basicStudyInfos.getId()); + self.deleteStudyIfNotCreationInProgress(basicStudyInfos.getId(), userId); LOGGER.trace("Create study '{}' from source {} : {} seconds", basicStudyInfos.getId(), sourceStudyUuid, TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime.get())); } @@ -586,7 +586,7 @@ private void removeNetworkVisualizationParameters(@Nullable UUID uuid) { } @Transactional - public void deleteStudyIfNotCreationInProgress(UUID studyUuid) { + public void deleteStudyIfNotCreationInProgress(UUID studyUuid, String userId) { AtomicReference startTime = new AtomicReference<>(null); Optional deleteStudyInfosOpt = doDeleteStudyIfNotCreationInProgress(studyUuid); if (deleteStudyInfosOpt.isPresent()) { @@ -599,18 +599,18 @@ public void deleteStudyIfNotCreationInProgress(UUID studyUuid) { // delete all distant resources linked to nodes studyServerExecutionService.runAsync(() -> deleteStudyInfos.getModificationGroupUuidsNodeUuids().stream() .filter(Objects::nonNull) - .forEach(this::deleteModificationsFromGroup)); + .forEach(groupUuidNodeUuid -> deleteModificationsFromGroup(groupUuidNodeUuid, userId))); LOGGER.trace("Delete study '{}' : {} seconds", studyUuid, TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime.get())); } } - private void deleteModificationsFromGroup(Pair groupUuidNodeUuid) { + private void deleteModificationsFromGroup(Pair groupUuidNodeUuid, String userId) { // fetch the references data in order to remove those references from directory-server Map referenceToBeDeleted = networkModificationService.getAllReferencesDataFromGroup(groupUuidNodeUuid.getFirst()); referenceToBeDeleted.forEach((modUuid, refUuid) -> { - directoryService.removeReference(refUuid != null ? refUuid : groupUuidNodeUuid.getSecond(), "userId", modUuid); // TODO : handle userId ?? voir avec Slimane + directoryService.removeReference(refUuid != null ? refUuid : groupUuidNodeUuid.getSecond(), userId, modUuid); }); networkModificationService.deleteModifications(groupUuidNodeUuid.getFirst()); @@ -2149,7 +2149,7 @@ public void deleteNodes(UUID studyUuid, List nodeIds, boolean deleteChildr } } - deleteNodesInfos(deleteNodeInfos); + deleteNodesInfos(deleteNodeInfos, userId); notificationService.emitElementUpdated(studyUuid, userId); } @@ -2176,7 +2176,7 @@ private CompletableFuture deleteInvalidationInfos(InvalidateNodeInfos inva return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)); } - private void deleteNodesInfos(DeleteNodeInfos deleteNodeInfos) { + private void deleteNodesInfos(DeleteNodeInfos deleteNodeInfos, String userId) { List> futures = new ArrayList<>(); futures.add(studyServerExecutionService.runAsync(() -> deleteNodeInfos.getVariantIds().forEach(networkStoreService::deleteVariants))); List modificationGroupUuids = deleteNodeInfos.getModificationGroupUuids(); @@ -2184,7 +2184,9 @@ private void deleteNodesInfos(DeleteNodeInfos deleteNodeInfos) { List> modificationGroupUuidsNodeUuids = IntStream.range(0, modificationGroupUuids.size()) .mapToObj(index -> Pair.of(modificationGroupUuids.get(index), removedNodeUuids.get(index))) .toList(); - futures.add(studyServerExecutionService.runAsync(() -> modificationGroupUuidsNodeUuids.forEach(this::deleteModificationsFromGroup))); + futures.add(studyServerExecutionService.runAsync(() -> modificationGroupUuidsNodeUuids.forEach( + groupUuidNodeUuid -> deleteModificationsFromGroup(groupUuidNodeUuid, userId)) + )); futures.add(studyServerExecutionService.runAsync(() -> deleteNodeInfos.getRemovedNodeUuids().forEach(dynamicSimulationEventService::deleteEventsByNodeId))); futures.addAll(rootNetworkNodeInfoService.getRemoteDeletions(deleteNodeInfos)); // Do not wait completion and do not throw exception From 3cc7454b330ad7a0d262b5e9f46f355a331d6ff2 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Thu, 18 Jun 2026 17:13:07 +0200 Subject: [PATCH 11/19] testInsertComposite Signed-off-by: Mathieu DEHARBE --- .../study/server/service/StudyService.java | 8 +++++-- .../study/server/NetworkModificationTest.java | 22 +++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index b2faf40b5..14aae63e3 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -2405,8 +2405,12 @@ public void insertCompositeNetworkModifications( String userId, StudyConstants.CompositeModificationsActionType action) { // is some of the inserted modifications are shared, references have to be created in directory server - if (action == StudyConstants.CompositeModificationsActionType.INSERT && !compositesInfos.stream().filter(CompositesToBeInserted::isShared).toList().isEmpty()) { - directoryService.addReferencesToSharedComposites(compositesInfos.stream().map(CompositesToBeInserted::id).toList(), userId, targetNodeUuid); + List sharedCompositeUuids = compositesInfos.stream() + .filter(CompositesToBeInserted::isShared) + .map(CompositesToBeInserted::id) + .toList(); + if (action == StudyConstants.CompositeModificationsActionType.INSERT && !sharedCompositeUuids.isEmpty()) { + directoryService.addReferencesToSharedComposites(sharedCompositeUuids, userId, targetNodeUuid); } duplicateModificationsOrInsertComposites( diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index 467fddacb..42f2f97e2 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -79,6 +79,7 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.gridsuite.study.server.StudyConstants.HEADER_ERROR_MESSAGE; import static org.gridsuite.study.server.StudyConstants.QUERY_PARAM_RECEIVER; +import static org.gridsuite.study.server.dto.ReferenceAttributes.ReferenceType.STUDY_NODE; import static org.gridsuite.study.server.error.StudyBusinessErrorCode.MAX_NODE_BUILDS_EXCEEDED; import static org.gridsuite.study.server.error.StudyBusinessErrorCode.NOT_FOUND; import static org.gridsuite.study.server.utils.ImpactUtils.createModificationResultWithElementImpact; @@ -193,6 +194,9 @@ class NetworkModificationTest { @Autowired private NetworkModificationService networkModificationService; + @Autowired + private DirectoryService directoryService; + @Autowired private ReportService reportService; @@ -302,6 +306,7 @@ void setup() { doReturn(baseUrl).when(dynamicMarginCalculationClient).getBaseUri(); networkModificationService.setNetworkModificationServerBaseUri(baseUrl); + directoryService.setDirectoryServerServerBaseUri(baseUrl); userAdminService.setUserAdminServerBaseUri(baseUrl); buildOkStubId = wireMockServer.stubFor(WireMock.post(WireMock.urlPathEqualTo("/v1/networks/" + NETWORK_UUID_STRING + "/build")) @@ -2099,7 +2104,8 @@ void testInsertComposite() throws Exception { UUID.randomUUID(), VARIANT_ID, "New node 1", "userId"); UUID nodeUuid1 = node1.getId(); CompositesToBeInserted modification1 = new CompositesToBeInserted(UUID.randomUUID(), "composite 1", false); - CompositesToBeInserted modification2 = new CompositesToBeInserted(UUID.randomUUID(), "composite 2", false); + UUID sharedNetModId = UUID.randomUUID(); + CompositesToBeInserted modification2 = new CompositesToBeInserted(sharedNetModId, "composite 2", true); String compositesData = mapper.writeValueAsString( Arrays.asList( modification1, @@ -2111,7 +2117,14 @@ void testInsertComposite() throws Exception { .withQueryParam("action", WireMock.equalTo("INSERT")) .willReturn(WireMock.ok() .withBody(mapper.writeValueAsString(new NetworkModificationsResult(List.of(UUID.randomUUID(), UUID.randomUUID()), List.of(Optional.empty())))) - .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))).getId(); + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))); + + // stubs the references creation : + wireMockServer.stubFor(WireMock.post(WireMock.urlPathEqualTo("/v1/elements/" + sharedNetModId + "/references")) + .withHeader(USER_ID_HEADER, WireMock.equalTo(userId)) + .withHeader(HttpHeaders.CONTENT_TYPE, WireMock.containing(MediaType.APPLICATION_JSON_VALUE)) + .willReturn(WireMock.ok() + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))); // insert 2 composite modifications in node1 mockMvc.perform(put("/v1/studies/{studyUuid}/nodes/{nodeUuid}/composite-modifications?action=INSERT", @@ -2136,6 +2149,11 @@ void testInsertComposite() throws Exception { WireMockUtilsCriteria.verifyPutRequest(wireMockServer, url, false, Map.of( "action", WireMock.equalTo("INSERT")), expectedBody); + WireMockUtilsCriteria.verifyPostRequest( + wireMockServer, + "/v1/elements/" + sharedNetModId + "/references", + Map.of(), + mapper.writeValueAsString(new ReferenceAttributes(nodeUuid1, STUDY_NODE))); } @Test From 0bb437b6aff1e6fc01b8f804d3e49f0f205bb5f1 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 19 Jun 2026 11:31:25 +0200 Subject: [PATCH 12/19] stash / unstash nodes 6> update references Signed-off-by: Mathieu DEHARBE --- .../server/controller/StudyController.java | 3 --- .../study/server/service/StudyService.java | 21 +++++++++++++++++++ .../server/NetworkModificationTreeTest.java | 6 ++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 9c73949a0..2affa023f 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -707,9 +707,6 @@ public ResponseEntity assembleModificationsIntoNewComposite( return ResponseEntity.ok().body(newCompositeUuid); } - /** - * @param compositesToBeInserted is a List but there is no need to create a specific dto here, the data is only useful in network-modification-server - */ @PutMapping(value = "/studies/{studyUuid}/nodes/{nodeUuid}/composite-modifications", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "For a list of composite network modifications passed in body, insert them into the target node") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The composite modification list has been inserted.")}) diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 14aae63e3..80403d3ea 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -2214,8 +2214,15 @@ public void stashNode(UUID studyUuid, UUID nodeId, boolean stashChildren, String ); } + UUID groupId = networkModificationTreeService.getModificationGroupUuid(nodeId); networkModificationTreeService.doStashNode(nodeId, stashChildren); + Map referenceToBeDeleted = networkModificationService.getAllReferencesDataFromGroup(groupId); + // if there are references modifications in the stashed node, those references have to be removed from directory server + referenceToBeDeleted.forEach((modUuid, refUuid) -> { + directoryService.removeReference(refUuid != null ? refUuid : nodeId, userId, modUuid); + }); + if (startTime.get() != null) { LOGGER.trace("Delete node '{}' of study '{}' : {} seconds", nodeId, studyUuid, TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime.get())); @@ -2231,6 +2238,20 @@ public List> getStashedNodes(UUID studyId) { public void restoreNodes(UUID studyId, List nodeIds, UUID anchorNodeId, String userId) { networkModificationTreeService.assertIsRootOrConstructionNode(anchorNodeId); networkModificationTreeService.restoreNode(studyId, nodeIds, anchorNodeId); + + // if there are references modifications in the unstashed node, those references had been removed and must be recreated in directory server + nodeIds.forEach(nodeId -> { + UUID groupId = networkModificationTreeService.getModificationGroupUuid(nodeId); + Map referenceToBeRecreated = networkModificationService.getAllReferencesDataFromGroup(groupId); + if (!referenceToBeRecreated.isEmpty()) { + directoryService.addReferencesToSharedComposites( + referenceToBeRecreated.keySet().stream().toList(), + userId, + nodeId + ); + } + }); + notificationService.emitElementUpdated(studyId, userId); } diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java index 3046dfb14..9858e4b5f 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java @@ -311,6 +311,12 @@ public MockResponse dispatch(RecordedRequest request) { Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), "{}" ); + } else if (path.matches("/v1/groups/.*/references") && Objects.equals(request.getMethod(), "GET")) { + return new MockResponse( + HttpStatus.OK.value(), + Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), + "{}" + ); } else { LOGGER.error("Path not supported: {}", request.getPath()); return new MockResponse(HttpStatus.NOT_FOUND.value()); From 08c98bb60e6d2306d23f51b8cfabf61b1e675fc1 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 19 Jun 2026 13:11:52 +0200 Subject: [PATCH 13/19] more specific stubs before Signed-off-by: Mathieu DEHARBE --- .../server/NetworkModificationTreeTest.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java index 9858e4b5f..a97d837c6 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java @@ -287,6 +287,18 @@ public MockResponse dispatch(RecordedRequest request) { if (path.matches("/v1/results.*") && request.getMethod().equals("DELETE")) { return new MockResponse(HttpStatus.OK.value()); + } else if (path.matches("/v1/references\\?uuids=.*") && request.getMethod().equals("GET")) { + return new MockResponse( + HttpStatus.OK.value(), + Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), + "{}" + ); + } else if (path.matches("/v1/groups/.*/references") && Objects.equals(request.getMethod(), "GET")) { + return new MockResponse( + HttpStatus.OK.value(), + Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), + "{}" + ); } else if (path.matches("/v1/network-modifications.*")) { return new MockResponse(HttpStatus.OK.value()); } else if (path.matches("/v1/groups/" + MODIFICATION_GROUP_UUID + "/network-modifications-count.*") && request.getMethod().equals("GET")) { @@ -305,18 +317,6 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse(HttpStatus.OK.value()); } else if (path.matches("/v1/reports") && request.getMethod().equals("DELETE")) { return new MockResponse(HttpStatus.OK.value()); - } else if (path.matches("/v1/references\\?uuids=.*") && request.getMethod().equals("GET")) { - return new MockResponse( - HttpStatus.OK.value(), - Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), - "{}" - ); - } else if (path.matches("/v1/groups/.*/references") && Objects.equals(request.getMethod(), "GET")) { - return new MockResponse( - HttpStatus.OK.value(), - Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), - "{}" - ); } else { LOGGER.error("Path not supported: {}", request.getPath()); return new MockResponse(HttpStatus.NOT_FOUND.value()); From dfdf0063dc0a273b5881b9838de0266dedf84690 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 19 Jun 2026 15:46:24 +0200 Subject: [PATCH 14/19] TU on removeReference Signed-off-by: Mathieu DEHARBE --- .../server/service/DirectoryService.java | 9 +++++++++ .../study/server/NetworkModificationTest.java | 20 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java index 6e03a8e74..4927c82bb 100644 --- a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java +++ b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java @@ -126,7 +126,16 @@ public void addReferencesToSharedComposites(@NonNull List elementsUuids, S }); } + /** + * remove references from the shared modification in directory server + * @param referenceUuid uuid of the composite or group where the 'Modification reference' is located + * @param userId id of the user who caused the unreferencing + * @param sharedElementUuid uuid of the referenced shared element in the directory-server + */ public void removeReference(UUID referenceUuid, String userId, UUID sharedElementUuid) { + Objects.requireNonNull(referenceUuid); + Objects.requireNonNull(sharedElementUuid); + var path = UriComponentsBuilder.fromPath( DELIMITER + DIRECTORY_API_VERSION + DELIMITER + "elements/{elementUuid}/references/{referenceUuid}") .buildAndExpand(sharedElementUuid, referenceUuid) diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index 42f2f97e2..03d0b67a0 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -1128,11 +1128,19 @@ void testCreateTwoWindingsTransformer() throws Exception { void deleteModificationRequest() throws Exception { String userId = "userId"; - // stubs the checks and updates of referenced modifications : there are none here + UUID modificationUuid = UUID.randomUUID(); + // stubs the checks and updates of referenced modifications + Map stubbedReferences = new HashMap<>(); + stubbedReferences.put(modificationUuid, null); UUID referencesStubId = wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo("/v1/references")) + .withQueryParam("uuids", WireMock.equalTo(modificationUuid.toString())) .willReturn(WireMock.ok() .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .withBody("{}")) + .withBody(mapper.writeValueAsString(stubbedReferences))) + ).getId(); + UUID removeReferencesStubId = wireMockServer.stubFor(WireMock.delete(WireMock.urlPathMatching( + "/v1/elements/" + modificationUuid + "/references/.*")) + .willReturn(WireMock.ok()) ).getId(); UUID stubId = wireMockServer.stubFor(WireMock.delete(WireMock.urlPathMatching("/v1/network-modifications")).willReturn(WireMock.ok())).getId(); @@ -1156,13 +1164,19 @@ void deleteModificationRequest() throws Exception { .header(USER_ID_HEADER, userId)) .andExpect(status().isNotFound()); - UUID modificationUuid = UUID.randomUUID(); mockMvc.perform(delete(URI_NETWORK_MODIF, studyUuid, modificationNode.getId()) .queryParam("uuids", modificationUuid.toString()) .header(USER_ID_HEADER, userId)) .andExpect(status().isOk()); WireMockUtils.verifyGetRequest(wireMockServer, referencesStubId, "/v1/references", Map.of("uuids", WireMock.equalTo(modificationUuid.toString()))); WireMockUtils.verifyDeleteRequest(wireMockServer, stubId, "/v1/network-modifications", false, Map.of("uuids", WireMock.equalTo(modificationUuid.toString()))); + WireMockUtils.verifyDeleteRequest( + wireMockServer, + removeReferencesStubId, + "/v1/elements/" + modificationUuid + "/references/.*", + true, + Map.of() + ); checkEquipmentDeletingMessagesReceived(studyUuid, modificationNode.getId()); checkEquipmentDeletingFinishedMessagesReceived(studyUuid, modificationNode.getId()); From a955fe48d5e7b12b6ae660575765d9f5b7bc5e5b Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 19 Jun 2026 17:08:04 +0200 Subject: [PATCH 15/19] SONAR Signed-off-by: Mathieu DEHARBE --- .../server/service/DirectoryService.java | 6 ++--- .../study/server/service/StudyService.java | 24 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java index 4927c82bb..cba64f366 100644 --- a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java +++ b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java @@ -116,7 +116,7 @@ public void addReferencesToSharedComposites(@NonNull List elementsUuids, S .toUriString(); HttpHeaders headers = new HttpHeaders(); - headers.set("userId", userId); + headers.set(HEADER_USER_ID, userId); headers.setContentType(MediaType.APPLICATION_JSON); ReferenceAttributes referenceAttributes = new ReferenceAttributes(targetNodeUuid, STUDY_NODE); @@ -142,7 +142,7 @@ public void removeReference(UUID referenceUuid, String userId, UUID sharedElemen .toUriString(); HttpHeaders headers = new HttpHeaders(); - headers.set("userId", userId); + headers.set(HEADER_USER_ID, userId); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity requestEntity = new HttpEntity<>(headers); @@ -155,7 +155,7 @@ public void createElement(UUID directoryUuid, String description, UUID elementUu String path = pathBuilder.buildAndExpand(directoryUuid).toUriString(); HttpHeaders headers = new HttpHeaders(); - headers.set("userId", userId); + headers.set(HEADER_USER_ID, userId); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity requestEntity = new HttpEntity<>(elementAttributes, headers); diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 80403d3ea..1c52fe297 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -609,9 +609,9 @@ public void deleteStudyIfNotCreationInProgress(UUID studyUuid, String userId) { private void deleteModificationsFromGroup(Pair groupUuidNodeUuid, String userId) { // fetch the references data in order to remove those references from directory-server Map referenceToBeDeleted = networkModificationService.getAllReferencesDataFromGroup(groupUuidNodeUuid.getFirst()); - referenceToBeDeleted.forEach((modUuid, refUuid) -> { - directoryService.removeReference(refUuid != null ? refUuid : groupUuidNodeUuid.getSecond(), userId, modUuid); - }); + referenceToBeDeleted.forEach((modUuid, refUuid) -> + directoryService.removeReference(refUuid != null ? refUuid : groupUuidNodeUuid.getSecond(), userId, modUuid) + ); networkModificationService.deleteModifications(groupUuidNodeUuid.getFirst()); } @@ -2013,9 +2013,9 @@ public void deleteNetworkModifications(UUID studyUuid, UUID nodeUuid, List Map referenceToBeDeleted = networkModificationService.getReferencesData(modificationsUuids); networkModificationService.deleteModifications(groupId, modificationsUuids); // if there are unstashed references modifications in the deleted netmods, those references have to be removed from directory server - referenceToBeDeleted.forEach((modUuid, refUuid) -> { - directoryService.removeReference(refUuid != null ? refUuid : nodeUuid, userId, modUuid); - }); + referenceToBeDeleted.forEach((modUuid, refUuid) -> + directoryService.removeReference(refUuid != null ? refUuid : nodeUuid, userId, modUuid) + ); // for each root network, remove modifications from excluded ones studyEntity.getRootNetworks().forEach(rootNetworkEntity -> rootNetworkNodeInfoService.updateModificationsToExclude(nodeUuid, rootNetworkEntity.getId(), new HashSet<>(modificationsUuids), true)); } finally { @@ -2037,9 +2037,9 @@ public void stashNetworkModifications(UUID studyUuid, UUID nodeUuid, List Map referenceToBeDeleted = networkModificationService.getReferencesData(modificationsUuids); networkModificationService.stashModifications(groupId, modificationsUuids); // if there are references modifications in the stashed netmods, those references have to be removed from directory server - referenceToBeDeleted.forEach((modUuid, refUuid) -> { - directoryService.removeReference(refUuid != null ? refUuid : nodeUuid, userId, modUuid); - }); + referenceToBeDeleted.forEach((modUuid, refUuid) -> + directoryService.removeReference(refUuid != null ? refUuid : nodeUuid, userId, modUuid) + ); invalidateNodeTree(studyUuid, nodeUuid); } finally { notificationService.emitEndModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids); @@ -2219,9 +2219,9 @@ public void stashNode(UUID studyUuid, UUID nodeId, boolean stashChildren, String Map referenceToBeDeleted = networkModificationService.getAllReferencesDataFromGroup(groupId); // if there are references modifications in the stashed node, those references have to be removed from directory server - referenceToBeDeleted.forEach((modUuid, refUuid) -> { - directoryService.removeReference(refUuid != null ? refUuid : nodeId, userId, modUuid); - }); + referenceToBeDeleted.forEach((modUuid, refUuid) -> + directoryService.removeReference(refUuid != null ? refUuid : nodeId, userId, modUuid) + ); if (startTime.get() != null) { LOGGER.trace("Delete node '{}' of study '{}' : {} seconds", nodeId, studyUuid, From d6c4c8350ddd764aec720eeeb5a7c33ce9e125c2 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Mon, 22 Jun 2026 13:10:49 +0200 Subject: [PATCH 16/19] comments and renaming Signed-off-by: Mathieu DEHARBE --- .../study/server/controller/StudyController.java | 2 +- .../org/gridsuite/study/server/dto/DeleteStudyInfos.java | 1 + .../gridsuite/study/server/dto/ReferenceAttributes.java | 1 + .../study/server/service/NetworkModificationService.java | 8 +++++++- .../org/gridsuite/study/server/service/StudyService.java | 8 ++++---- .../gridsuite/study/server/studycontroller/StudyTest.java | 6 +++--- .../study/server/utils/wiremock/WireMockStubs.java | 4 ++-- 7 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 2affa023f..05c300313 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -183,7 +183,7 @@ public ResponseEntity getStudy(@PathVariable("studyUuid" @Operation(summary = "delete the study") @ApiResponse(responseCode = "200", description = "Study deleted") public ResponseEntity deleteStudy(@PathVariable("studyUuid") UUID studyUuid, - @RequestHeader(HEADER_USER_ID) String userId) { // TODO : aller ajouter ce param dans le front (où?) + @RequestHeader(HEADER_USER_ID) String userId) { studyService.deleteStudyIfNotCreationInProgress(studyUuid, userId); return ResponseEntity.ok().build(); } diff --git a/src/main/java/org/gridsuite/study/server/dto/DeleteStudyInfos.java b/src/main/java/org/gridsuite/study/server/dto/DeleteStudyInfos.java index 96e6f2325..0379a4ace 100644 --- a/src/main/java/org/gridsuite/study/server/dto/DeleteStudyInfos.java +++ b/src/main/java/org/gridsuite/study/server/dto/DeleteStudyInfos.java @@ -23,5 +23,6 @@ public class DeleteStudyInfos { private List rootNetworkInfosList; + // group uuids as keys and each their own node as values private List> modificationGroupUuidsNodeUuids; } diff --git a/src/main/java/org/gridsuite/study/server/dto/ReferenceAttributes.java b/src/main/java/org/gridsuite/study/server/dto/ReferenceAttributes.java index 7e853e547..fcfcbf5e7 100644 --- a/src/main/java/org/gridsuite/study/server/dto/ReferenceAttributes.java +++ b/src/main/java/org/gridsuite/study/server/dto/ReferenceAttributes.java @@ -17,6 +17,7 @@ /** * @author Mathieu Deharbe + * attributes of the references to the shared composites stored in directory server */ @Getter @Setter diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java index b1ecd31ae..f973581f6 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java @@ -224,6 +224,9 @@ public void restoreModifications(UUID groupUUid, List modificationsUuids) restTemplate.exchange(path, HttpMethod.PUT, httpEntity, Void.class); } + /** + * @return references data of the modificationsUuids as Pair + */ public Map getReferencesData(List modificationsUuids) { Objects.requireNonNull(modificationsUuids); var path = UriComponentsBuilder @@ -245,7 +248,10 @@ public Map getReferencesData(List modificationsUuids) { ).getBody(); } - public Map getAllReferencesDataFromGroup(UUID groupUuid) { + /** + * @return references data of the modifications in the group as Pair + */ + public Map getReferencesDataFromGroup(UUID groupUuid) { Objects.requireNonNull(groupUuid); var path = UriComponentsBuilder.fromPath(GROUP_PATH + DELIMITER + "references"); diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 1c52fe297..32c1cd418 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -548,7 +548,7 @@ private Optional doDeleteStudyIfNotCreationInProgress(UUID stu DeleteStudyInfos deleteStudyInfos = null; if (studyCreationRequestEntity.isEmpty()) { List rootNetworkInfos = getStudyRootNetworksInfos(studyUuid); - // get all modification groups related to the study + // get all modification groups and nodes related to the study List allStudyNetworkModificationNodeInfo = networkModificationTreeService.getAllStudyNetworkModificationNodeInfo(studyUuid); List> modificationGroupUuidsNodeUuids = allStudyNetworkModificationNodeInfo.stream() .map(nodeInfoEntity -> Pair.of(nodeInfoEntity.getModificationGroupUuid(), nodeInfoEntity.getIdNode())) @@ -608,7 +608,7 @@ public void deleteStudyIfNotCreationInProgress(UUID studyUuid, String userId) { private void deleteModificationsFromGroup(Pair groupUuidNodeUuid, String userId) { // fetch the references data in order to remove those references from directory-server - Map referenceToBeDeleted = networkModificationService.getAllReferencesDataFromGroup(groupUuidNodeUuid.getFirst()); + Map referenceToBeDeleted = networkModificationService.getReferencesDataFromGroup(groupUuidNodeUuid.getFirst()); referenceToBeDeleted.forEach((modUuid, refUuid) -> directoryService.removeReference(refUuid != null ? refUuid : groupUuidNodeUuid.getSecond(), userId, modUuid) ); @@ -2217,7 +2217,7 @@ public void stashNode(UUID studyUuid, UUID nodeId, boolean stashChildren, String UUID groupId = networkModificationTreeService.getModificationGroupUuid(nodeId); networkModificationTreeService.doStashNode(nodeId, stashChildren); - Map referenceToBeDeleted = networkModificationService.getAllReferencesDataFromGroup(groupId); + Map referenceToBeDeleted = networkModificationService.getReferencesDataFromGroup(groupId); // if there are references modifications in the stashed node, those references have to be removed from directory server referenceToBeDeleted.forEach((modUuid, refUuid) -> directoryService.removeReference(refUuid != null ? refUuid : nodeId, userId, modUuid) @@ -2242,7 +2242,7 @@ public void restoreNodes(UUID studyId, List nodeIds, UUID anchorNodeId, St // if there are references modifications in the unstashed node, those references had been removed and must be recreated in directory server nodeIds.forEach(nodeId -> { UUID groupId = networkModificationTreeService.getModificationGroupUuid(nodeId); - Map referenceToBeRecreated = networkModificationService.getAllReferencesDataFromGroup(groupId); + Map referenceToBeRecreated = networkModificationService.getReferencesDataFromGroup(groupId); if (!referenceToBeRecreated.isEmpty()) { directoryService.addReferencesToSharedComposites( referenceToBeRecreated.keySet().stream().toList(), diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java index dd4b27647..9329164a4 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java @@ -246,7 +246,7 @@ void testDeleteStudy() throws Exception { assertTrue(studyRepository.findById(studyUuid).isEmpty()); - wireMockStubs.verifyGetAllReferencesDataFromGroup(stubReferencesUuid); + wireMockStubs.verifyGetReferencesDataFromGroup(stubReferencesUuid); wireMockStubs.verifyNetworkModificationDeleteGroup(stubUuid, false); wireMockStubs.caseServer.verifyDeleteCase(stubDeleteCaseId, CASE_UUID_STRING); deleteStudyStubs.verify(wireMockStubs, computationServerStubs, 10); // voltageInit, loadFlow, securityAnalysis, sensitivityAnalysis, stateEstimation, pccMin, dynamic, shortCircuit @@ -281,7 +281,7 @@ void testDeleteStudyWithError(final CapturedOutput capturedOutput) throws Except .andExpectAll(status().isOk()); assertTrue(capturedOutput.getOut().contains(StudyServerExecutionService.class.getName() + " - " + CompletionException.class.getName() + ": " + InterruptedException.class.getName())); - wireMockStubs.verifyGetAllReferencesDataFromGroup(stubReferencesUuid); + wireMockStubs.verifyGetReferencesDataFromGroup(stubReferencesUuid); wireMockStubs.verifyNetworkModificationDeleteGroup(stubUuid, false); deleteStudyStubs.verify(wireMockStubs, computationServerStubs, 3); // loadflow, security, sensitivity, stateEstimation, shortCircuit, pccMin } @@ -309,7 +309,7 @@ void testDeleteStudyWithNonExistingCase() throws Exception { assertTrue(studyRepository.findById(studyUuid).isEmpty()); - wireMockStubs.verifyGetAllReferencesDataFromGroup(stubReferencesUuid); + wireMockStubs.verifyGetReferencesDataFromGroup(stubReferencesUuid); wireMockStubs.verifyNetworkModificationDeleteGroup(stubUuid, false); wireMockStubs.caseServer.verifyDeleteCase(stubDeleteCaseId, nonExistingCaseUuid.toString()); deleteStudyStubs.verify(wireMockStubs, computationServerStubs, 10); diff --git a/src/test/java/org/gridsuite/study/server/utils/wiremock/WireMockStubs.java b/src/test/java/org/gridsuite/study/server/utils/wiremock/WireMockStubs.java index 94929eb10..c2c77d0ef 100644 --- a/src/test/java/org/gridsuite/study/server/utils/wiremock/WireMockStubs.java +++ b/src/test/java/org/gridsuite/study/server/utils/wiremock/WireMockStubs.java @@ -245,8 +245,8 @@ public UUID stubGetAllReferencesDataFromGroup() { .withBody("{}"))).getId(); } - public void verifyGetAllReferencesDataFromGroup(UUID getAllReferencesDataUuid) { - verifyGetRequest(wireMock, getAllReferencesDataUuid, URI_NETWORK_MODIFICATION_GROUPS + DELIMITER + ".*/references", true, Map.of(), 1); + public void verifyGetReferencesDataFromGroup(UUID getReferencesDataUuid) { + verifyGetRequest(wireMock, getReferencesDataUuid, URI_NETWORK_MODIFICATION_GROUPS + DELIMITER + ".*/references", true, Map.of(), 1); } public UUID stubNetworkModificationDeleteIndex() { From 2369e751b111188d74e67cfed7aceb919c7ef8af Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Tue, 7 Jul 2026 11:12:30 +0200 Subject: [PATCH 17/19] new sonar locks Signed-off-by: Mathieu DEHARBE --- .../study/server/controller/StudyController.java | 7 ++++++- .../study/server/service/NetworkModificationService.java | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 2ac3f85c8..f068b3cb2 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -743,7 +743,12 @@ public ResponseEntity insertCompositeModifications(@PathVariable("studyUui return ResponseEntity.ok().build(); } - private void handleInsertCompositeNetworkModifications(UUID targetStudyUuid, UUID targetNodeUuid, List compositesToBeInserted, String userId, CompositeModificationsActionType action) { + private void handleInsertCompositeNetworkModifications( + UUID targetStudyUuid, + UUID targetNodeUuid, + List compositesToBeInserted, + String userId, + CompositeModificationsActionType action) { studyService.assertNoBlockedNodeInStudy(targetStudyUuid, targetNodeUuid); studyService.invalidateNodeTreeWithLF(targetStudyUuid, targetNodeUuid); try { diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java index d40405a6f..731a64ab4 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java @@ -225,7 +225,9 @@ public void restoreModifications(UUID groupUUid, List modificationsUuids) } /** - * @return references data of the modificationsUuids as Pair + * @return references data of the modificationsUuids as Pair of : + * - element uuid in directory server + * - uuid of its mother composite (null if the modification is at the root level) */ public Map getReferencesData(List modificationsUuids) { Objects.requireNonNull(modificationsUuids); @@ -249,7 +251,9 @@ public Map getReferencesData(List modificationsUuids) { } /** - * @return references data of the modifications in the group as Pair + * @return references data of the modifications in the group as Pairof : + * - element uuid in directory server + * - uuid of its mother composite (null if the modification is at the root level) */ public Map getReferencesDataFromGroup(UUID groupUuid) { Objects.requireNonNull(groupUuid); From 585580691288d210df7ea5701599c155ab3b30fb Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Tue, 7 Jul 2026 17:47:29 +0200 Subject: [PATCH 18/19] remove references recreation after restoration Signed-off-by: Mathieu DEHARBE --- .../study/server/service/StudyService.java | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index bad690a8c..0dd2983fd 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -2101,16 +2101,7 @@ public void restoreNetworkModifications(UUID studyUuid, UUID nodeUuid, List referenceToBeRecreated = networkModificationService.getReferencesData(modificationsUuids); networkModificationService.restoreModifications(groupId, modificationsUuids); - // if there are references modifications in the unstashed netmods, those references had been removed and must be recreated in directory server - if (!referenceToBeRecreated.isEmpty()) { - directoryService.addReferencesToSharedComposites( - referenceToBeRecreated.keySet().stream().toList(), - userId, - nodeUuid - ); - } invalidateNodeTree(studyUuid, nodeUuid); } finally { notificationService.emitEndModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids); @@ -2245,20 +2236,6 @@ public List> getStashedNodes(UUID studyId) { public void restoreNodes(UUID studyId, List nodeIds, UUID anchorNodeId, String userId) { networkModificationTreeService.assertIsRootOrConstructionNode(anchorNodeId); networkModificationTreeService.restoreNode(studyId, nodeIds, anchorNodeId); - - // if there are references modifications in the unstashed node, those references had been removed and must be recreated in directory server - nodeIds.forEach(nodeId -> { - UUID groupId = networkModificationTreeService.getModificationGroupUuid(nodeId); - Map referenceToBeRecreated = networkModificationService.getReferencesDataFromGroup(groupId); - if (!referenceToBeRecreated.isEmpty()) { - directoryService.addReferencesToSharedComposites( - referenceToBeRecreated.keySet().stream().toList(), - userId, - nodeId - ); - } - }); - notificationService.emitElementUpdated(studyId, userId); } From 8a9e97038fe00a59a0f4b0243f3871362ba54e62 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Tue, 7 Jul 2026 18:20:41 +0200 Subject: [PATCH 19/19] java doc Signed-off-by: Mathieu DEHARBE --- .../study/server/service/DirectoryService.java | 10 +++++----- .../gridsuite/study/server/service/StudyService.java | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java index cba64f366..e7b3b48a3 100644 --- a/src/main/java/org/gridsuite/study/server/service/DirectoryService.java +++ b/src/main/java/org/gridsuite/study/server/service/DirectoryService.java @@ -103,12 +103,12 @@ public boolean elementExists(UUID directoryUuid, String elementName, String type } /** - * - * @param elementsUuids all the element uuids of the shared composites that need to be referenced in directory server - * @param userId id of the user who started the insertion + * creates references and add them to shared composite modifications stored in directory server + * @param elementsUuids element uuids of the shared composites in directory server + * @param userId id of the user who creates the references * @param targetNodeUuid where the new references will point */ - public void addReferencesToSharedComposites(@NonNull List elementsUuids, String userId, UUID targetNodeUuid) { + public void createsReferencesToSharedComposites(@NonNull List elementsUuids, String userId, UUID targetNodeUuid) { elementsUuids.forEach(elementUuid -> { var path = UriComponentsBuilder.fromPath( DELIMITER + DIRECTORY_API_VERSION + DELIMITER + "elements/{elementUuid}/references") @@ -127,7 +127,7 @@ public void addReferencesToSharedComposites(@NonNull List elementsUuids, S } /** - * remove references from the shared modification in directory server + * remove reference from the shared modification in directory server * @param referenceUuid uuid of the composite or group where the 'Modification reference' is located * @param userId id of the user who caused the unreferencing * @param sharedElementUuid uuid of the referenced shared element in the directory-server diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 0dd2983fd..ea87b8820 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -2417,7 +2417,7 @@ public void insertCompositeNetworkModifications( .map(CompositesToBeInserted::id) .toList(); if (action == StudyConstants.CompositeModificationsActionType.INSERT && !sharedCompositeUuids.isEmpty()) { - directoryService.addReferencesToSharedComposites(sharedCompositeUuids, userId, targetNodeUuid); + directoryService.createsReferencesToSharedComposites(sharedCompositeUuids, userId, targetNodeUuid); } duplicateModificationsOrInsertComposites(