From 20523014e08cbe0a9ed92276390af342a2fca894 Mon Sep 17 00:00:00 2001 From: dbraquart <107846716+dbraquart@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:21:40 +0100 Subject: [PATCH] add MODIFICATION type (#72) Signed-off-by: David BRAQUART --- .../explore/server/ExploreController.java | 10 +++ .../server/services/DirectoryService.java | 13 ++- .../server/services/ExploreService.java | 23 +++++ .../services/NetworkModificationService.java | 90 +++++++++++++++++++ src/main/resources/application-local.yml | 5 +- .../gridsuite/explore/server/ExploreTest.java | 69 ++++++++++---- 6 files changed, 190 insertions(+), 20 deletions(-) create mode 100644 src/main/java/org/gridsuite/explore/server/services/NetworkModificationService.java diff --git a/src/main/java/org/gridsuite/explore/server/ExploreController.java b/src/main/java/org/gridsuite/explore/server/ExploreController.java index d11e2708..75352b00 100644 --- a/src/main/java/org/gridsuite/explore/server/ExploreController.java +++ b/src/main/java/org/gridsuite/explore/server/ExploreController.java @@ -306,4 +306,14 @@ public ResponseEntity duplicateParameters(@RequestParam("duplicateFrom") U exploreService.duplicateParameters(parentParameterId, parametersType, parametersName, parentDirectoryUuid, userId); return ResponseEntity.ok().build(); } + + @PostMapping(value = "/explore/modifications") + @Operation(summary = "create some modification elements from existing network modifications") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Modifications have been duplicated and corresponding elements created in the directory")}) + public ResponseEntity createNetworkModifications(@RequestBody List bodyContent, + @RequestParam(QUERY_PARAM_PARENT_DIRECTORY_ID) UUID parentDirectoryUuid, + @RequestHeader("userId") String userId) { + exploreService.createNetworkModifications(bodyContent, userId, parentDirectoryUuid); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java b/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java index bb1f47be..b989bc77 100644 --- a/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java +++ b/src/main/java/org/gridsuite/explore/server/services/DirectoryService.java @@ -48,7 +48,7 @@ public class DirectoryService implements IDirectoryElementsService { @Autowired public DirectoryService( - FilterService filterService, ContingencyListService contingencyListService, StudyService studyService, + FilterService filterService, ContingencyListService contingencyListService, StudyService studyService, NetworkModificationService networkModificationService, CaseService caseService, ParametersService parametersService, RestTemplate restTemplate, RemoteServicesProperties remoteServicesProperties) { this.directoryServerBaseUri = remoteServicesProperties.getServiceUri("directory-server"); this.restTemplate = restTemplate; @@ -57,6 +57,7 @@ public DirectoryService( CONTINGENCY_LIST, contingencyListService, STUDY, studyService, DIRECTORY, this, + MODIFICATION, networkModificationService, CASE, caseService, ParametersType.VOLTAGE_INIT_PARAMETERS.name(), parametersService, ParametersType.SECURITY_ANALYSIS_PARAMETERS.name(), parametersService, @@ -68,9 +69,13 @@ public void setDirectoryServerBaseUri(String directoryServerBaseUri) { } public ElementAttributes createElement(ElementAttributes elementAttributes, UUID directoryUuid, String userId) { + return createElementWithNewName(elementAttributes, directoryUuid, userId, false); + } + + public ElementAttributes createElementWithNewName(ElementAttributes elementAttributes, UUID directoryUuid, String userId, boolean allowNewName) { String path = UriComponentsBuilder - .fromPath(DIRECTORIES_SERVER_ROOT_PATH + "/{directoryUuid}/elements") - .buildAndExpand(directoryUuid) + .fromPath(DIRECTORIES_SERVER_ROOT_PATH + "/{directoryUuid}/elements?allowNewName={allowNewName}") + .buildAndExpand(directoryUuid, allowNewName) .toUriString(); HttpHeaders headers = new HttpHeaders(); headers.add(HEADER_USER_ID, userId); @@ -93,7 +98,7 @@ public void deleteDirectoryElement(UUID elementUuid, String userId) { public ElementAttributes getElementInfos(UUID elementUuid) { String path = UriComponentsBuilder - .fromPath(ELEMENTS_SERVER_ROOT_PATH + "/{directoryUuid}") + .fromPath(ELEMENTS_SERVER_ROOT_PATH + "/{elementUuid}") .buildAndExpand(elementUuid) .toUriString(); return restTemplate.exchange(directoryServerBaseUri + path, HttpMethod.GET, null, ElementAttributes.class) diff --git a/src/main/java/org/gridsuite/explore/server/services/ExploreService.java b/src/main/java/org/gridsuite/explore/server/services/ExploreService.java index ad71e2cd..ba008105 100644 --- a/src/main/java/org/gridsuite/explore/server/services/ExploreService.java +++ b/src/main/java/org/gridsuite/explore/server/services/ExploreService.java @@ -15,6 +15,8 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; + +import java.util.List; import java.util.Map; import java.util.UUID; @@ -33,11 +35,13 @@ public class ExploreService { static final String CASE = "CASE"; static final String CONTINGENCY_LIST = "CONTINGENCY_LIST"; static final String FILTER = "FILTER"; + static final String MODIFICATION = "MODIFICATION"; static final String DIRECTORY = "DIRECTORY"; private DirectoryService directoryService; private StudyService studyService; private ContingencyListService contingencyListService; + private NetworkModificationService networkModificationService; private FilterService filterService; private CaseService caseService; private ParametersService parametersService; @@ -49,6 +53,7 @@ public ExploreService( StudyService studyService, ContingencyListService contingencyListService, FilterService filterService, + NetworkModificationService networkModificationService, CaseService caseService, ParametersService parametersService) { @@ -56,6 +61,7 @@ public ExploreService( this.studyService = studyService; this.contingencyListService = contingencyListService; this.filterService = filterService; + this.networkModificationService = networkModificationService; this.caseService = caseService; this.parametersService = parametersService; } @@ -248,4 +254,21 @@ public void duplicateParameters(UUID parentParameterId, ParametersType parameter null, userId, 0L, null); directoryService.createElement(elementAttributes, parentDirectoryUuid, userId); } + + public void createNetworkModifications(List modificationAttributesList, String userId, UUID parentDirectoryUuid) { + List existingModificationsUuids = modificationAttributesList.stream() + .map(ElementAttributes::getElementUuid) + .toList(); + + // create all duplicated modifications + Map newModificationsUuids = networkModificationService.createModifications(existingModificationsUuids); + + // create all corresponding directory elements + modificationAttributesList.forEach(m -> { + final UUID newId = newModificationsUuids.get(m.getElementUuid()); + ElementAttributes elementAttributes = new ElementAttributes(newId, m.getElementName(), MODIFICATION, + null, userId, 0L, m.getDescription()); + directoryService.createElementWithNewName(elementAttributes, parentDirectoryUuid, userId, true); + }); + } } diff --git a/src/main/java/org/gridsuite/explore/server/services/NetworkModificationService.java b/src/main/java/org/gridsuite/explore/server/services/NetworkModificationService.java new file mode 100644 index 00000000..209615d5 --- /dev/null +++ b/src/main/java/org/gridsuite/explore/server/services/NetworkModificationService.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2024, 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.services; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author David Braquart + */ +@Service +public class NetworkModificationService implements IDirectoryElementsService { + private static final String NETWORK_MODIFICATION_API_VERSION = "v1"; + private static final String DELIMITER = "/"; + private static final String HEADER_USER_ID = "userId"; + public static final String UUIDS = "uuids"; + private static final String NETWORK_MODIFICATIONS_PATH = "network-modifications"; + private String networkModificationServerBaseUri; + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; + + @Autowired + public NetworkModificationService(RestTemplate restTemplate, RemoteServicesProperties remoteServicesProperties, ObjectMapper objectMapper) { + this.networkModificationServerBaseUri = remoteServicesProperties.getServiceUri("network-modification-server"); + this.restTemplate = restTemplate; + this.objectMapper = objectMapper; + } + + public void setNetworkModificationServerBaseUri(String networkModificationServerBaseUri) { + this.networkModificationServerBaseUri = networkModificationServerBaseUri; + } + + public Map createModifications(List modificationUuids) { + String path = UriComponentsBuilder.fromPath(DELIMITER + NETWORK_MODIFICATION_API_VERSION + DELIMITER + NETWORK_MODIFICATIONS_PATH + "/duplicate") + .buildAndExpand() + .toUriString(); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity httpEntity; + try { + httpEntity = new HttpEntity<>(objectMapper.writeValueAsString(modificationUuids), headers); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + return restTemplate.exchange(networkModificationServerBaseUri + path, HttpMethod.POST, httpEntity, new ParameterizedTypeReference>() { }) + .getBody(); + } + + @Override + public void delete(UUID id, String userId) { + String path = UriComponentsBuilder.fromPath(DELIMITER + NETWORK_MODIFICATION_API_VERSION + DELIMITER + NETWORK_MODIFICATIONS_PATH) + .queryParam(UUIDS, List.of(id)) + .buildAndExpand() + .toUriString(); + HttpHeaders headers = new HttpHeaders(); + headers.add(HEADER_USER_ID, userId); + restTemplate.exchange(networkModificationServerBaseUri + path, HttpMethod.DELETE, new HttpEntity<>(headers), Void.class); + } + + @Override + public List> getMetadata(List modificationUuids) { + var ids = modificationUuids.stream().map(UUID::toString).collect(Collectors.joining(",")); + String path = UriComponentsBuilder + .fromPath(DELIMITER + NETWORK_MODIFICATION_API_VERSION + DELIMITER + NETWORK_MODIFICATIONS_PATH + "/metadata" + "?ids=" + ids) + .buildAndExpand() + .toUriString(); + return restTemplate.exchange(networkModificationServerBaseUri + path, HttpMethod.GET, null, + new ParameterizedTypeReference>>() { + }).getBody(); + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 92d9e6a0..e47935c5 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -21,7 +21,10 @@ gridsuite: - name: actions-server base-uri: http://localhost:5022 - - + - + name: network-modification-server + base-uri: http://172.17.0.1:5007 + - name: filter-server base-uri: http://localhost:5027 - diff --git a/src/test/java/org/gridsuite/explore/server/ExploreTest.java b/src/test/java/org/gridsuite/explore/server/ExploreTest.java index 526ef5bb..f55dd0db 100644 --- a/src/test/java/org/gridsuite/explore/server/ExploreTest.java +++ b/src/test/java/org/gridsuite/explore/server/ExploreTest.java @@ -70,6 +70,7 @@ public class ExploreTest { private static final UUID CONTINGENCY_LIST_UUID = UUID.randomUUID(); private static final UUID INVALID_ELEMENT_UUID = UUID.randomUUID(); private static final UUID PARAMETERS_UUID = UUID.randomUUID(); + private static final UUID MODIFICATION_UUID = UUID.randomUUID(); private static final String STUDY_ERROR_NAME = "studyInError"; private static final String STUDY1 = "study1"; private static final String CASE1 = "case1"; @@ -78,9 +79,10 @@ public class ExploreTest { public static final String FILTER_CONTINGENCY_LIST = "filterContingencyList"; public static final String FILTER_CONTINGENCY_LIST_2 = "filterContingencyList2"; public static final String FILTER = "FILTER"; - private Map specificMetadata = new HashMap<>(); - private Map specificMetadata2 = new HashMap<>(); - private Map caseSpecificMetadata = new HashMap<>(); + private final Map specificMetadata = Map.of("id", FILTER_UUID); + private final Map specificMetadata2 = Map.of("equipmentType", "LINE", "id", FILTER_UUID_2); + private final Map caseSpecificMetadata = Map.of("uuid", CASE_UUID, "name", TEST_FILE, "format", "XIIDM"); + private final Map modificationSpecificMetadata = Map.of("id", MODIFICATION_UUID, "type", "LOAD_MODIFICATION"); private static final UUID SCRIPT_ID_BASE_FORM_CONTINGENCY_LIST_UUID = UUID.randomUUID(); @@ -95,6 +97,8 @@ public class ExploreTest { @Autowired private StudyService studyService; @Autowired + private NetworkModificationService networkModificationService; + @Autowired private CaseService caseService; @Autowired private RemoteServicesProperties remoteServicesProperties; @@ -105,8 +109,6 @@ public class ExploreTest { @Before public void setup() throws IOException { server = new MockWebServer(); - - // Start the server. server.start(); // Ask the server for its URL. You'll need this to make HTTP requests. @@ -117,16 +119,9 @@ public void setup() throws IOException { studyService.setStudyServerBaseUri(baseUrl); filterService.setFilterServerBaseUri(baseUrl); contingencyListService.setActionsServerBaseUri(baseUrl); + networkModificationService.setNetworkModificationServerBaseUri(baseUrl); caseService.setBaseUri(baseUrl); remoteServicesProperties.getServices().forEach(s -> s.setBaseUri(baseUrl)); - specificMetadata.put("id", FILTER_UUID); - - specificMetadata2.put("equipmentType", "LINE"); - specificMetadata2.put("id", FILTER_UUID_2); - - caseSpecificMetadata.put("uuid", CASE_UUID); - caseSpecificMetadata.put("name", TEST_FILE); - caseSpecificMetadata.put("format", "XIIDM"); String privateStudyAttributesAsString = mapper.writeValueAsString(new ElementAttributes(PRIVATE_STUDY_UUID, STUDY1, "STUDY", new AccessRightsAttributes(true), USER1, 0, null)); String listOfPrivateStudyAttributesAsString = mapper.writeValueAsString(List.of(new ElementAttributes(PRIVATE_STUDY_UUID, STUDY1, "STUDY", new AccessRightsAttributes(true), USER1, 0, null))); @@ -142,6 +137,9 @@ public void setup() throws IOException { String parametersElementAttributesAsString = mapper.writeValueAsString(new ElementAttributes(PARAMETERS_UUID, "voltageInitParametersName", ParametersType.VOLTAGE_INIT_PARAMETERS.name(), new AccessRightsAttributes(true), USER1, 0, null)); String listElementsAttributesAsString = "[" + filterAttributesAsString + "," + privateStudyAttributesAsString + "," + formContingencyListAttributesAsString + "]"; String caseInfosAttributesAsString = mapper.writeValueAsString(List.of(caseSpecificMetadata)); + String modificationElementAttributesAsString = mapper.writeValueAsString(new ElementAttributes(MODIFICATION_UUID, "one modif", "MODIFICATION", new AccessRightsAttributes(true), USER1, 0L, null)); + String modificationInfosAttributesAsString = mapper.writeValueAsString(List.of(modificationSpecificMetadata)); + String modificationIdsAsString = mapper.writeValueAsString(Map.of(MODIFICATION_UUID, MODIFICATION_UUID)); final Dispatcher dispatcher = new Dispatcher() { @SneakyThrows @@ -168,10 +166,10 @@ public MockResponse dispatch(RecordedRequest request) { } else { return new MockResponse().setResponseCode(200); } - } else if (path.matches("/v1/directories/" + PARENT_DIRECTORY_UUID + "/elements") && "POST".equals(request.getMethod())) { + } else if (path.matches("/v1/directories/" + PARENT_DIRECTORY_UUID + "/elements\\?allowNewName=.*") && "POST".equals(request.getMethod())) { return new MockResponse().setBody(privateStudyAttributesAsString).setResponseCode(200) .addHeader("Content-Type", "application/json; charset=utf-8"); - } else if (path.matches("/v1/directories/" + PARENT_DIRECTORY_WITH_ERROR_UUID + "/elements") && "POST".equals(request.getMethod())) { + } else if (path.matches("/v1/directories/" + PARENT_DIRECTORY_WITH_ERROR_UUID + "/elements\\?allowNewName=.*") && "POST".equals(request.getMethod())) { return new MockResponse().setResponseCode(500); } else if (path.matches("/v1/elements/" + CONTINGENCY_LIST_UUID) && "GET".equals(request.getMethod())) { return new MockResponse().setBody(formContingencyListAttributesAsString).setResponseCode(200) @@ -188,6 +186,9 @@ public MockResponse dispatch(RecordedRequest request) { } else if (path.matches("/v1/elements/" + CASE_UUID) && "GET".equals(request.getMethod())) { return new MockResponse().setBody(caseElementAttributesAsString).setResponseCode(200) .addHeader("Content-Type", "application/json; charset=utf-8"); + } else if (path.matches("/v1/elements/" + MODIFICATION_UUID) && "GET".equals(request.getMethod())) { + return new MockResponse().setBody(modificationElementAttributesAsString).setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8"); } else if (path.matches("/v1/elements/" + PRIVATE_STUDY_UUID) && "GET".equals(request.getMethod())) { return new MockResponse().setBody(privateStudyAttributesAsString).setResponseCode(200) .addHeader("Content-Type", "application/json; charset=utf-8"); @@ -205,6 +206,10 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse().setBody("[" + caseElementAttributesAsString + "]") .setResponseCode(200) .addHeader("Content-Type", "application/json; charset=utf-8"); + } else if (path.matches("/v1/elements\\?ids=" + MODIFICATION_UUID) && "GET".equals(request.getMethod())) { + return new MockResponse().setBody("[" + modificationElementAttributesAsString + "]") + .setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8"); } else if (path.matches("/v1/filters/metadata\\?ids=" + FILTER_UUID + "," + FILTER_UUID_2) && "GET".equals(request.getMethod())) { return new MockResponse().setBody("[" + mapper.writeValueAsString(specificMetadata) + "," + mapper.writeValueAsString(specificMetadata2) + "]") .setResponseCode(200) @@ -246,6 +251,8 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse().setResponseCode(200); } else if (path.matches("/v1/parameters.*")) { return new MockResponse().setResponseCode(200); + } else if (path.matches("/v1/network-modifications/duplicate")) { + return new MockResponse().setBody(modificationIdsAsString).setResponseCode(200).addHeader("Content-Type", "application/json; charset=utf-8"); } else if ("GET".equals(request.getMethod())) { if (path.matches("/v1/elements/" + INVALID_ELEMENT_UUID)) { return new MockResponse().setBody(invalidElementAsString).setResponseCode(200).addHeader("Content-Type", "application/json; charset=utf-8"); @@ -257,6 +264,8 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse().setBody(listOfFilterAttributesAsString.replace("elementUuid", "id")).setResponseCode(200).addHeader("Content-Type", "application/json; charset=utf-8"); } else if (path.matches("/v1/cases/metadata[?]ids=" + CASE_UUID)) { return new MockResponse().setBody(caseInfosAttributesAsString).setResponseCode(200).addHeader("Content-Type", "application/json; charset=utf-8"); + } else if (path.matches("/v1/network-modifications/metadata[?]ids=" + MODIFICATION_UUID)) { + return new MockResponse().setBody(modificationInfosAttributesAsString).setResponseCode(200).addHeader("Content-Type", "application/json; charset=utf-8"); } else if (path.matches("/v1/studies/metadata[?]ids=" + PRIVATE_STUDY_UUID)) { return new MockResponse().setBody(listOfPrivateStudyAttributesAsString.replace("elementUuid", "id")).setResponseCode(200) .addHeader("Content-Type", "application/json; charset=utf-8"); @@ -268,6 +277,8 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse().setResponseCode(200); } else if (path.matches("/v1/contingency-lists/" + CONTINGENCY_LIST_UUID)) { return new MockResponse().setResponseCode(200); + } else if (path.matches("/v1/network-modifications\\?uuids=" + MODIFICATION_UUID)) { + return new MockResponse().setResponseCode(200); } else if (path.matches("/v1/elements/" + INVALID_ELEMENT_UUID)) { return new MockResponse().setResponseCode(200); } else if (path.matches("/v1/elements/" + PRIVATE_STUDY_UUID)) { @@ -280,6 +291,8 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse().setResponseCode(200); } else if (path.matches("/v1/elements/" + PARAMETERS_UUID)) { return new MockResponse().setResponseCode(200); + } else if (path.matches("/v1/elements/" + MODIFICATION_UUID)) { + return new MockResponse().setResponseCode(200); } else if (path.matches("/v1/(cases|elements)/" + CASE_UUID)) { return new MockResponse().setResponseCode(200); } else if (path.matches("/v1/parameters/" + PARAMETERS_UUID)) { @@ -460,6 +473,7 @@ public void testDeleteElement() throws Exception { deleteElement(PARENT_DIRECTORY_UUID); deleteElement(CASE_UUID); deleteElement(PARAMETERS_UUID); + deleteElement(MODIFICATION_UUID); } @Test @@ -642,4 +656,29 @@ public void testGetMetadata() throws Exception { assertEquals(1, elementsMetadata.size()); assertEquals(mapper.writeValueAsString(elementsMetadata.get(0)), caseAttributesAsString); } + + @Test + @SneakyThrows + public void testcreateNetworkModifications() { + final String body = mapper.writeValueAsString(List.of(new ElementAttributes(MODIFICATION_UUID, "one modif", "", null, USER1, 0L, "a description"))); + mockMvc.perform(post("/v1/explore/modifications?parentDirectoryUuid={parentDirectoryUuid}", PARENT_DIRECTORY_UUID) + .header("userId", USER1) + .contentType(MediaType.APPLICATION_JSON) + .content(body) + ).andExpect(status().isOk()); + } + + @Test + @SneakyThrows + public void testGetModificationMetadata() { + final String expectedResult = mapper.writeValueAsString(new ElementAttributes(MODIFICATION_UUID, "one modif", "MODIFICATION", new AccessRightsAttributes(true), USER1, 0L, null, modificationSpecificMetadata)); + MvcResult result = mockMvc.perform(get("/v1/explore/elements/metadata?ids=" + MODIFICATION_UUID) + .header("userId", USER1)) + .andExpect(status().isOk()) + .andReturn(); + String response = result.getResponse().getContentAsString(); + List elementsMetadata = mapper.readValue(response, new TypeReference<>() { }); + assertEquals(1, elementsMetadata.size()); + assertEquals(mapper.writeValueAsString(elementsMetadata.get(0)), expectedResult); + } }