Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f5553f3
export study
ghazwarhili Aug 7, 2026
c22592d
add TU
ghazwarhili Aug 7, 2026
84ff55c
enhance coverage code
ghazwarhili Aug 7, 2026
aa595a3
rework exportStudyArchive()
etiennehomer Aug 7, 2026
db1e926
code review remarks
ghazwarhili Aug 7, 2026
e04ee14
remove BuildStatus
ghazwarhili Aug 7, 2026
34e39f6
update TU
ghazwarhili Aug 7, 2026
7b241aa
add index
ghazwarhili Aug 10, 2026
6b0c1c8
Merge branch 'main' into razwa/export-study
ghazwarhili Aug 10, 2026
2ca0b42
remove CaseExportInfos
ghazwarhili Aug 10, 2026
115841b
Merge branch 'main' into razwa/export-study
ghazwarhili Aug 11, 2026
836cbfd
fix sonar issues
ghazwarhili Aug 11, 2026
83bb25b
code review Etienne L
ghazwarhili Aug 11, 2026
107d377
enhance TU
ghazwarhili Aug 11, 2026
ca94ad3
import study
ghazwarhili Aug 11, 2026
a217703
simplify the studyentity creation
ghazwarhili Aug 12, 2026
4c79e99
add TU
ghazwarhili Aug 12, 2026
43b9d7d
resolve conflicts
ghazwarhili Aug 13, 2026
ae80bc2
revert useless changes
ghazwarhili Aug 13, 2026
53a1e22
revert useless changes
ghazwarhili Aug 13, 2026
cf2cda1
revert useless changes
ghazwarhili Aug 13, 2026
6223d03
add StudyImportService
ghazwarhili Aug 14, 2026
b1fb871
resolve conflicts
ghazwarhili Aug 14, 2026
67b9307
code review rabbit
ghazwarhili Aug 14, 2026
f504693
enhance comments
ghazwarhili Aug 17, 2026
7742935
revert
ghazwarhili Aug 17, 2026
f613fb4
renaming fix
ghazwarhili Aug 17, 2026
2b02356
export network modification group plus filters plus loadflow parameters
ghazwarhili Aug 17, 2026
ba64d3e
draft version
ghazwarhili Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
import org.gridsuite.filter.utils.EquipmentType;
import org.gridsuite.study.server.StudyApi;
import org.gridsuite.study.server.dto.*;
import org.gridsuite.study.server.dto.caseimport.CaseImportAction;
import org.gridsuite.study.server.dto.elasticsearch.EquipmentInfos;
import org.gridsuite.study.server.dto.modification.*;
import org.gridsuite.study.server.dto.networkexport.ExportNetworkStatus;
import org.gridsuite.study.server.dto.networkexport.NodeExportInfos;
import org.gridsuite.study.server.dto.sequence.NodeSequenceType;
import org.gridsuite.study.server.dto.studyexport.NetworkModificationExportInfos;
import org.gridsuite.study.server.dto.studyexport.TreeExportInfos;
import org.gridsuite.study.server.elasticsearch.EquipmentInfosService;
import org.gridsuite.study.server.error.StudyException;
import org.gridsuite.study.server.exception.PartialResultException;
Expand All @@ -37,6 +40,7 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.beans.PropertyEditorSupport;
import java.util.*;
Expand All @@ -62,6 +66,7 @@ public class StudyController {
private final RootNetworkService rootNetworkService;
private final RebuildNodeService rebuildNodeService;
private final StudyExportService studyExportService;
private final StudyImportService studyImportService;

public StudyController(StudyService studyService,
NetworkService networkStoreService,
Expand All @@ -72,7 +77,8 @@ public StudyController(StudyService studyService,
RemoteServicesInspector remoteServicesInspector,
RootNetworkService rootNetworkService,
RebuildNodeService rebuildNodeService,
StudyExportService studyExportService) {
StudyExportService studyExportService,
StudyImportService studyImportService) {
this.studyService = studyService;
this.networkModificationTreeService = networkModificationTreeService;
this.networkStoreService = networkStoreService;
Expand All @@ -83,6 +89,7 @@ public StudyController(StudyService studyService,
this.rootNetworkService = rootNetworkService;
this.rebuildNodeService = rebuildNodeService;
this.studyExportService = studyExportService;
this.studyImportService = studyImportService;
}

@InitBinder
Expand Down Expand Up @@ -175,7 +182,8 @@ public ResponseEntity<List<BasicRootNetworkInfos>> getRootNetworks(@PathVariable
public ResponseEntity<RootNetworkRequestInfos> createRootNetwork(@PathVariable("studyUuid") UUID studyUuid,
@RequestBody RootNetworkInfos rootNetworkInfos,
@RequestHeader(HEADER_USER_ID) String userId) {
return ResponseEntity.ok().body(studyService.createRootNetworkRequest(studyUuid, rootNetworkInfos, userId));
rootNetworkInfos.setId(null);
return ResponseEntity.ok().body(studyService.createRootNetworkRequest(studyUuid, rootNetworkInfos, userId, CaseImportAction.ROOT_NETWORK_CREATION));
}

@PutMapping(value = "/studies/{studyUuid}/root-networks/{rootNetworkUuid}")
Expand Down Expand Up @@ -948,8 +956,8 @@ public ResponseEntity<String> getNetworkModifications(@Parameter(description = "
@Operation(summary = "Get network modifications to export for a given node")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The network modifications to export was returned"), @ApiResponse(responseCode = "404",
description = "The study/node is not found")})
public ResponseEntity<String> getExportedNetworkModifications(@Parameter(description = "Study UUID") @PathVariable("studyUuid") UUID studyUuid,
@Parameter(description = "Node UUID") @PathVariable("nodeUuid") UUID nodeUuid) {
public ResponseEntity<NetworkModificationExportInfos> getExportedNetworkModifications(@Parameter(description = "Study UUID") @PathVariable("studyUuid") UUID studyUuid,
@Parameter(description = "Node UUID") @PathVariable("nodeUuid") UUID nodeUuid) {
studyService.assertIsStudyAndNodeExist(studyUuid, nodeUuid);
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(studyService.getExportedNetworkModifications(studyUuid, nodeUuid));
}
Expand Down Expand Up @@ -1615,4 +1623,14 @@ public ResponseEntity<Resource> exportStudy(@PathVariable("studyUuid") UUID stud
headers.setContentType(MediaType.parseMediaType("application/zip"));
return ResponseEntity.ok().headers(headers).body(studyExportService.exportStudy(studyUuid, userId));
}

@PostMapping(value = "/studies/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Create a study and its root networks from a previously exported study archive")
@ApiResponse(responseCode = "200", description = "Study import initiated successfully")
public ResponseEntity<Void> importStudy(@RequestPart("treeExportInfos") TreeExportInfos treeExportInfos,
@RequestPart("modificationsArchive") MultipartFile modificationsArchive,
@RequestHeader(HEADER_USER_ID) String userId) {
studyImportService.importStudy(treeExportInfos, modificationsArchive, userId);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public enum CaseImportAction {
STUDY_CREATION("Study creation"),
ROOT_NETWORK_CREATION("Root network creation"),
ROOT_NETWORK_CREATION_FOR_STUDY_IMPORT("Root network creation for study import"),
NETWORK_RECREATION("Network recreation"),
ROOT_NETWORK_MODIFICATION("Root network modification");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2026, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.study.server.dto.studyexport;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import org.gridsuite.filter.AbstractFilter;

import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public record NetworkModificationExportInfos(
@JsonProperty("modifications")
List<JsonNode> exportedModifications,
@JsonProperty("filters")
Map<UUID, List<AbstractFilter>> exportedFilters,
@JsonProperty("loadFlowParameters")
Map<UUID, UUID> exportedLoadFlowParameters
) { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2026, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.study.server.dto.studyexport;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import org.gridsuite.filter.AbstractFilter;

import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public record NetworkModificationImportInfos(
@JsonProperty("modifications")
List<JsonNode> modifications,
@JsonProperty("filtersByOldId")
Map<UUID, AbstractFilter> filtersByOldId,
@JsonProperty("loadFlowParametersIdMapping")
Map<UUID, UUID> loadFlowParametersIdMapping
) { }
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.gridsuite.study.server.repository.voltageinit.StudyVoltageInitParametersEntity;

import java.util.*;
import java.util.stream.Collectors;

/**
* @author Abdelsalem Hedhili <abdelsalem.hedhili at rte-france.com>
Expand All @@ -36,6 +37,17 @@ public class StudyEntity extends AbstractManuallyAssignedIdentifierEntity<UUID>
@Builder.Default
private List<RootNetworkEntity> rootNetworks = new ArrayList<>();

/**
* Root network order to restore during an in-progress study import; null otherwise.
*/
@ElementCollection
@CollectionTable(name = "StudyRootNetworkOrder", foreignKey = @ForeignKey(
name = "study_root_network_order_fk"
))
@OrderColumn(name = "index")
@Column(name = "rootNetworkUuid")
private List<UUID> rootNetworkOrder;

/**
* @deprecated to remove when the data is migrated into the loadflow-server
*/
Expand Down Expand Up @@ -140,7 +152,19 @@ public RootNetworkEntity getFirstRootNetwork() {
public void addRootNetwork(RootNetworkEntity rootNetworkEntity) {
rootNetworkEntity.setStudy(this);
rootNetworkEntity.setIndexationStatus(RootNetworkIndexationStatus.INDEXED);
rootNetworks.add(rootNetworkEntity);
rootNetworks.add(resolveInsertPosition(rootNetworkEntity.getId()), rootNetworkEntity);
}

/**
* Insert index for rootNetworkId based on prior ordered networks, append outside pending import
*/
private int resolveInsertPosition(UUID rootNetworkId) {
int targetPos = rootNetworkOrder == null ? -1 : rootNetworkOrder.indexOf(rootNetworkId);
if (targetPos < 0) {
return rootNetworks.size();
}
Set<UUID> alreadyPresent = rootNetworks.stream().map(RootNetworkEntity::getId).collect(Collectors.toSet());
return (int) rootNetworkOrder.subList(0, targetPos).stream().filter(alreadyPresent::contains).count();
}

public void deleteRootNetworks(Set<UUID> uuids) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@

@Repository
public interface StudyRepository extends JpaRepository<StudyEntity, UUID> {
@EntityGraph(attributePaths = {"rootNetworks"}, type = EntityGraph.EntityGraphType.LOAD)
@EntityGraph(attributePaths = {"rootNetworks", "rootNetworkOrder"}, type = EntityGraph.EntityGraphType.LOAD)
Optional<StudyEntity> findWithRootNetworksById(UUID id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
package org.gridsuite.study.server.repository.rootnetwork;

import org.gridsuite.study.server.dto.RootNetworkAction;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
Expand All @@ -17,6 +18,8 @@ public interface RootNetworkRequestRepository extends JpaRepository<RootNetworkR

int countAllByStudyUuid(UUID studyUuid);

int countAllByStudyUuidAndActionRequest(UUID studyUuid, RootNetworkAction actionRequest);

Optional<RootNetworkRequestRepository> findByNameAndStudyUuid(String name, UUID studyUuid);

Optional<RootNetworkRequestRepository> findByTagAndStudyUuid(String tag, UUID studyUuid);
Expand Down
25 changes: 22 additions & 3 deletions src/main/java/org/gridsuite/study/server/service/CaseService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;

import static org.gridsuite.study.server.StudyConstants.CASE_API_VERSION;
Expand Down Expand Up @@ -96,11 +100,26 @@ public UUID createCase(String caseKey, String contentType) {
return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.POST, null, UUID.class).getBody();
}

public ResponseEntity<byte[]> getCaseContent(UUID caseUuid) {
public void streamCaseContent(UUID caseUuid, CaseContentHandler handler) throws IOException {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_API_VERSION + "/cases/{caseUuid}")
.buildAndExpand(caseUuid)
.toUriString();

return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.GET, null, byte[].class);
try {
restTemplate.execute(caseServerBaseUri + path, HttpMethod.GET, null, (ClientHttpResponse response) -> {
handler.handle(response.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING), response.getBody());
return null;
});
} catch (ResourceAccessException e) {
if (e.getCause() instanceof IOException ioException) {
throw ioException;
}
throw e;
}
}

@FunctionalInterface
public interface CaseContentHandler {
void handle(String contentEncoding, InputStream body) throws IOException;
}
}
Loading
Loading