Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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 @@ -19,6 +19,7 @@
import org.gridsuite.explore.server.dto.ReferencingElementInfos;
import org.gridsuite.explore.server.services.DirectoryService;
import org.gridsuite.explore.server.services.ExploreService;
import org.gridsuite.explore.server.services.StudyImportService;
import org.gridsuite.explore.server.utils.ContingencyListType;
import org.gridsuite.explore.server.utils.ParametersType;
import org.springframework.http.HttpStatus;
Expand All @@ -42,6 +43,7 @@ public class ExploreController {

// /!\ This query parameter is used by the gateway to control access
private static final String QUERY_PARAM_NAME = "name";
private static final String QUERY_PARAM_STUDY_NAME = "studyName";
private static final String QUERY_PARAM_DESCRIPTION = "description";
private static final String QUERY_PARAM_PARENT_DIRECTORY_ID = "parentDirectoryUuid";

Expand All @@ -50,10 +52,12 @@ public class ExploreController {

private final ExploreService exploreService;
private final DirectoryService directoryService;
private final StudyImportService studyImportService;

public ExploreController(ExploreService exploreService, DirectoryService directoryService) {
public ExploreController(ExploreService exploreService, DirectoryService directoryService, StudyImportService studyImportService) {
this.exploreService = exploreService;
this.directoryService = directoryService;
this.studyImportService = studyImportService;
}

@PostMapping(value = "/explore/studies/{studyName}/cases/{caseUuid}")
Expand Down Expand Up @@ -778,4 +782,18 @@ public ResponseEntity<UUID> duplicateDynamicMapping(@PathVariable("id") UUID id,
UUID newDynamicMappingUuid = exploreService.duplicateDynamicMapping(id, targetDirectoryId, userId);
return ResponseEntity.ofNullable(newDynamicMappingUuid);
}

@PostMapping(value = "/explore/studies/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Import a study from an archive")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Study import started asynchronously")})
@PreAuthorize("@authorizationService.isAuthorized(#userId, #parentDirectoryUuid, null, T(org.gridsuite.explore.server.dto.PermissionType).WRITE)")
public ResponseEntity<Void> importStudy(@RequestParam(QUERY_PARAM_STUDY_NAME) String studyName,
@RequestPart("archiveFile") MultipartFile archiveFile,
@RequestParam(QUERY_PARAM_DESCRIPTION) String description,
@RequestParam(QUERY_PARAM_PARENT_DIRECTORY_ID) UUID parentDirectoryUuid,
@RequestHeader(QUERY_PARAM_USER_ID) String userId) {
exploreService.assertCanCreateCase(userId);
studyImportService.importStudy(archiveFile, studyName, description, userId, parentDirectoryUuid);
return ResponseEntity.ok().build();
}
}
20 changes: 20 additions & 0 deletions src/main/java/org/gridsuite/explore/server/dto/CaseInfos.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* 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.explore.server.dto;

import java.util.UUID;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public record CaseInfos(
UUID caseUuid,
UUID originalCaseUuid,
String caseName,
String caseFormat
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* 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.explore.server.dto;

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

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public record NodeTreeExportInfos(
String name,
String type,
UUID modificationGroupUuid,
String nodeType,
List<NodeTreeExportInfos> children
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* 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.explore.server.dto;

import java.util.Map;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public record RootNetworkExportInfos(
String name,
String tag,
Integer index,
CaseInfos caseInfos,
Map<String, Object> importParameters
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* 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.explore.server.dto;

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

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public record TreeExportInfos(
UUID studyUuid,
List<RootNetworkExportInfos> rootNetworks,
NodeTreeExportInfos nodeTree
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
* Business error codes emitted by the explore service.
*/
public enum ExploreBusinessErrorCode implements BusinessErrorCode {
EXPLORE_MAX_ELEMENTS_EXCEEDED("explore.maxElementsExceeded");
EXPLORE_MAX_ELEMENTS_EXCEEDED("explore.maxElementsExceeded"),
IMPORT_STUDY_FAILED("explore.importStudyFailed");

private final String code;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ public ExploreException(ExploreBusinessErrorCode errorCode, String message, Map<
this.businessErrorValues = businessErrorValues != null ? Map.copyOf(businessErrorValues) : Map.of();
}

public ExploreException(ExploreBusinessErrorCode errorCode, String message, Throwable cause) {
super(Objects.requireNonNull(message, "message must not be null"), cause);
this.errorCode = Objects.requireNonNull(errorCode, "errorCode must not be null");
this.businessErrorValues = Map.of();
}

public static ExploreException of(ExploreBusinessErrorCode errorCode, String message, Object... args) {
return new ExploreException(errorCode, args.length == 0 ? message : String.format(message, args));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ protected ExploreBusinessErrorCode getBusinessCode(ExploreException ex) {
protected HttpStatus mapStatus(ExploreBusinessErrorCode errorCode) {
return switch (errorCode) {
case EXPLORE_MAX_ELEMENTS_EXCEEDED -> HttpStatus.FORBIDDEN;
case IMPORT_STUDY_FAILED -> HttpStatus.INTERNAL_SERVER_ERROR;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -27,7 +28,7 @@
@Service
public class CaseService implements IDirectoryElementsService {
private static final String CASE_SERVER_API_VERSION = "v1";

private static final String CASES_URL = "cases";
private static final String DELIMITER = "/";
private final RestTemplate restTemplate;
private String caseServerBaseUri;
Expand All @@ -53,7 +54,7 @@ UUID importCase(MultipartFile multipartFile) {
}
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(
body, headers);
caseUuid = restTemplate.postForObject(caseServerBaseUri + "/" + CASE_SERVER_API_VERSION + "/cases", request,
caseUuid = restTemplate.postForObject(caseServerBaseUri + "/" + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL, request,
UUID.class);
return caseUuid;
}
Expand All @@ -69,47 +70,62 @@ public UUID importCaseWithoutDirectoryElementCreation(MultipartFile multipartFil
body.add("withExpiration", withExpiration);
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(body, headers);

String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL)
.buildAndExpand()
.toUriString();
return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.POST, request, UUID.class).getBody();
}

public ResponseEntity<Resource> downloadCase(UUID caseUuid) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/{caseUuid}")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "{caseUuid}")
.buildAndExpand(caseUuid)
.toUriString();

return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.GET, null, Resource.class);
}

public Void deleteCase(UUID caseUuid) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/{caseUuid}")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "{caseUuid}")
.buildAndExpand(caseUuid)
.toUriString();

return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.DELETE, null, Void.class).getBody();
}

public String getBaseName(String caseName) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/caseBaseName")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "caseBaseName")
.queryParam("caseName", caseName)
.buildAndExpand()
.toUriString();

return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.GET, null, String.class).getBody();
}

public UUID importCaseFromFile(File file) {
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);

body.add("file", new org.springframework.core.io.FileSystemResource(file));

HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(body, headers);
return restTemplate.postForObject(
caseServerBaseUri + "/" + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL,
request,
UUID.class
);
}

void persistCase(UUID caseUuid) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/" + caseUuid + "/disableExpiration")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + DELIMITER + CASES_URL + DELIMITER + caseUuid + "/disableExpiration")
.buildAndExpand()
.toUriString();

restTemplate.exchange(caseServerBaseUri + path, HttpMethod.PUT, new HttpEntity<>(new HttpHeaders()), void.class);
}

UUID duplicateCase(UUID caseId) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/{uuid}/duplicate")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "{uuid}/duplicate")
.buildAndExpand(caseId)
.toUriString();
HttpHeaders headers = new HttpHeaders();
Expand All @@ -120,7 +136,7 @@ UUID duplicateCase(UUID caseId) {

@Override
public void delete(UUID id, String userId) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/{id}")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "{id}")
.buildAndExpand(id)
.toUriString();
HttpHeaders headers = new HttpHeaders();
Expand All @@ -132,7 +148,7 @@ public void delete(UUID id, String userId) {
public List<Map<String, Object>> getMetadata(List<UUID> casesUuids) {
var ids = casesUuids.stream().map(UUID::toString).collect(Collectors.joining(","));
String path = UriComponentsBuilder
.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/metadata" + "?ids=" + ids)
.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "metadata" + "?ids=" + ids)
.buildAndExpand()
.toUriString();
return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.GET, null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,11 @@ public void createFilterBasedContingencyList(String listName, String content, St
createDirectoryElementOrDeleteElement(elementAttributes, parentDirectoryUuid, userId, contingencyListService::delete);
}

public void createFilter(String filter, String filterName, String description, UUID parentDirectoryUuid, String userId) {
public UUID createFilter(String filter, String filterName, String description, UUID parentDirectoryUuid, String userId) {
ElementAttributes elementAttributes = new ElementAttributes(UUID.randomUUID(), filterName, FILTER, userId, 0, description);
filterService.insertFilter(filter, elementAttributes.getElementUuid(), userId);
createDirectoryElementOrDeleteElement(elementAttributes, parentDirectoryUuid, userId, filterService::delete);
return elementAttributes.getElementUuid();
}

public void duplicateFilter(UUID sourceFilterId, UUID targetDirectoryId, String userId) {
Expand Down Expand Up @@ -579,7 +580,7 @@ public UUID duplicateDynamicMapping(UUID sourceDynamicMappingUuid, UUID targetDi
return newDynamicMappingUuid;
}

private void createDirectoryElementOrDeleteElement(ElementAttributes elementAttributes, UUID parentDirectoryUuid, String userId, BiConsumer<UUID, String> rollback) {
void createDirectoryElementOrDeleteElement(ElementAttributes elementAttributes, UUID parentDirectoryUuid, String userId, BiConsumer<UUID, String> rollback) {
executeWithRollback(() -> directoryService.createElement(elementAttributes, parentDirectoryUuid, userId), elementAttributes.getElementUuid(), userId, rollback);
}

Expand Down
Loading
Loading