diff --git a/network-store-client/src/main/java/com/powsybl/network/store/client/PreloadingNetworkStoreClient.java b/network-store-client/src/main/java/com/powsybl/network/store/client/PreloadingNetworkStoreClient.java index 3e064a117..a527da437 100644 --- a/network-store-client/src/main/java/com/powsybl/network/store/client/PreloadingNetworkStoreClient.java +++ b/network-store-client/src/main/java/com/powsybl/network/store/client/PreloadingNetworkStoreClient.java @@ -883,4 +883,16 @@ public Map getAllExtensionsAttributesByIdentifiable delegate.getAllExtensionsAttributesByResourceType(networkUuid, variantNum, resourceType); return delegate.getAllExtensionsAttributesByIdentifiableId(networkUuid, variantNum, resourceType, id); } + + @Override + public Optional getOperationalLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String branchId, String operationalLimitGroupId, int side) { + delegate.getAllOperationalLimitsGroupAttributesByResourceType(networkUuid, variantNum, resourceType); + return delegate.getOperationalLimitsGroupAttributes(networkUuid, variantNum, resourceType, branchId, operationalLimitGroupId, side); + } + + @Override + public Optional getCurrentLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String branchId, String operationalLimitGroupId, int side) { + delegate.getAllSelectedCurrentLimitsGroupAttributesByResourceType(networkUuid, variantNum, resourceType); + return delegate.getOperationalLimitsGroupAttributes(networkUuid, variantNum, resourceType, branchId, operationalLimitGroupId, side); + } } diff --git a/network-store-client/src/main/java/com/powsybl/network/store/client/RestClient.java b/network-store-client/src/main/java/com/powsybl/network/store/client/RestClient.java index d74f96ddf..0300db19e 100644 --- a/network-store-client/src/main/java/com/powsybl/network/store/client/RestClient.java +++ b/network-store-client/src/main/java/com/powsybl/network/store/client/RestClient.java @@ -6,10 +6,7 @@ */ package com.powsybl.network.store.client; -import com.powsybl.network.store.model.Attributes; -import com.powsybl.network.store.model.ExtensionAttributes; -import com.powsybl.network.store.model.IdentifiableAttributes; -import com.powsybl.network.store.model.Resource; +import com.powsybl.network.store.model.*; import org.springframework.core.ParameterizedTypeReference; import java.util.List; @@ -31,6 +28,12 @@ public interface RestClient { */ Optional getOneExtensionAttributes(String url, Object... uriVariables); + /** + * Retrieves one operational limit group attributes from the server. + * @return {@link OperationalLimitsGroupAttributes} which is a subset of a branch resource there is a list each side of a branch. + */ + Optional getOperationalLimitsGroupAttributes(String url, Object... uriVariables); + List> getAll(String target, String url, Object... uriVariables); void updateAll(String url, List> resources, Object... uriVariables); diff --git a/network-store-client/src/main/java/com/powsybl/network/store/client/RestClientImpl.java b/network-store-client/src/main/java/com/powsybl/network/store/client/RestClientImpl.java index 4b76bfe78..dc03b9c68 100644 --- a/network-store-client/src/main/java/com/powsybl/network/store/client/RestClientImpl.java +++ b/network-store-client/src/main/java/com/powsybl/network/store/client/RestClientImpl.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.powsybl.commons.PowsyblException; import com.powsybl.network.store.model.*; @@ -48,7 +49,10 @@ public static RestTemplateBuilder createRestTemplateBuilder(String baseUri) { private static ObjectMapper createObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); + SimpleModule module = new SimpleModule(); + module.addKeyDeserializer(OperationalLimitsGroupIdentifier.class, new OperationalLimitsGroupIdentifierDeserializer()); objectMapper.registerModule(new JavaTimeModule()) + .registerModule(module) .configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false) .configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false); return objectMapper; @@ -101,6 +105,12 @@ public Optional getOneExtensionAttributes(String url, Objec }, uriVariables); } + @Override + public Optional getOperationalLimitsGroupAttributes(String url, Object... uriVariables) { + return getOneDocument(url, new ParameterizedTypeReference() { + }, uriVariables); + } + private > Optional getOneDocument(String url, ParameterizedTypeReference parameterizedTypeReference, Object... uriVariables) { ResponseEntity response = getDocument(url, parameterizedTypeReference, uriVariables); if (response.getStatusCode() == HttpStatus.OK) { diff --git a/network-store-client/src/main/java/com/powsybl/network/store/client/RestNetworkStoreClient.java b/network-store-client/src/main/java/com/powsybl/network/store/client/RestNetworkStoreClient.java index c8316e888..307ccd546 100644 --- a/network-store-client/src/main/java/com/powsybl/network/store/client/RestNetworkStoreClient.java +++ b/network-store-client/src/main/java/com/powsybl/network/store/client/RestNetworkStoreClient.java @@ -10,11 +10,13 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; import com.powsybl.commons.PowsyblException; import com.powsybl.network.store.iidm.impl.NetworkStoreClient; +import com.powsybl.network.store.model.OperationalLimitsGroupIdentifier; import com.powsybl.network.store.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,7 +69,10 @@ public RestNetworkStoreClient(RestClient restClient) { public RestNetworkStoreClient(RestClient restClient, ObjectMapper objectMapper) { this.restClient = Objects.requireNonNull(restClient); this.objectMapper = Objects.requireNonNull(objectMapper); + SimpleModule module = new SimpleModule(); + module.addKeyDeserializer(OperationalLimitsGroupIdentifier.class, new OperationalLimitsGroupIdentifierDeserializer()); objectMapper.registerModule(new JavaTimeModule()) + .registerModule(module) .configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false) .configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false); @@ -167,6 +172,29 @@ private static Map filterRawExtensionAttributes(Map .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } + private Optional getOperationalLimitsGroupAttributes(String urlTemplate, Object... uriVariables) { + logGetOperationalLimitsGroupAttributesUrl(urlTemplate, uriVariables); + Stopwatch stopwatch = Stopwatch.createStarted(); + Optional operationalLimitsGroupAttributes = restClient.getOperationalLimitsGroupAttributes(urlTemplate, uriVariables); + stopwatch.stop(); + logGetOperationalLimitsGroupAttributesTime(operationalLimitsGroupAttributes.isPresent() ? 1 : 0, stopwatch.elapsed(TimeUnit.MILLISECONDS)); + + return operationalLimitsGroupAttributes; + } + + private Map> getOperationalLimitsGroupAttributesNestedMap(String urlTemplate, Object... uriVariables) { + logGetOperationalLimitsGroupAttributesUrl(urlTemplate, uriVariables); + Stopwatch stopwatch = Stopwatch.createStarted(); + Map> operationalLimitsGroupAttributes = restClient.get(urlTemplate, new ParameterizedTypeReference<>() { }, uriVariables); + stopwatch.stop(); + long loadedAttributesCount = operationalLimitsGroupAttributes.values().stream() + .mapToLong(innerMap -> innerMap.values().size()) + .sum(); + logGetOperationalLimitsGroupAttributesTime(loadedAttributesCount, stopwatch.elapsed(TimeUnit.MILLISECONDS)); + + return operationalLimitsGroupAttributes; + } + private static void logGetExtensionAttributesUrl(String urlTemplate, Object... uriVariables) { if (LOGGER.isInfoEnabled()) { LOGGER.info("Loading extension attributes {}", UriComponentsBuilder.fromUriString(urlTemplate).build(uriVariables)); @@ -181,6 +209,16 @@ private static void logGetExtensionAttributesTime(long loadedAttributesCount, lo } } + private static void logGetOperationalLimitsGroupAttributesUrl(String urlTemplate, Object... uriVariables) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Loading operational limits group attributes {}", UriComponentsBuilder.fromUriString(urlTemplate).build(uriVariables)); + } + } + + private static void logGetOperationalLimitsGroupAttributesTime(long loadedAttributesCount, long timeElapsed) { + LOGGER.info("{} operational limits group attributes loaded in {} ms", loadedAttributesCount, timeElapsed); + } + private void updatePartition(String target, String url, AttributeFilter attributeFilter, List> resources, Object[] uriVariables) { if (attributeFilter == null) { if (LOGGER.isInfoEnabled()) { @@ -954,4 +992,33 @@ public Map> getAllExtensionsAttributesB public void removeExtensionAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String identifiableId, String extensionName) { restClient.delete("/networks/{networkUuid}/{variantNum}/identifiables/{identifiableId}/extensions/{extensionName}", networkUuid, variantNum, identifiableId, extensionName); } + + @Override + public Optional getOperationalLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String branchId, String operationalLimitsGroupId, int side) { + return getOperationalLimitsGroupAttributes("/networks/{networkUuid}/{variantNum}/branch/{branchId}/types/{resourceType}/operationalLimitsGroup/{operationalLimitsGroupId}/side/{side}", + networkUuid, variantNum, branchId, resourceType, operationalLimitsGroupId, side); + } + + @Override + public void removeOperationalLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String identifiableId, String operationalLimitGroupName, int side) { + + } + + @Override + public Map> getAllOperationalLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType resourceType) { + return getOperationalLimitsGroupAttributesNestedMap("/networks/{networkUuid}/{variantNum}/branch/types/{resourceType}/operationalLimitsGroup/", + networkUuid, variantNum, resourceType); + } + + @Override + public Map> getAllSelectedCurrentLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType resourceType) { + return getOperationalLimitsGroupAttributesNestedMap("/networks/{networkUuid}/{variantNum}/branch/types/{resourceType}/operationalLimitsGroup/currentLimits/", + networkUuid, variantNum, resourceType); + } + + @Override + public Optional getCurrentLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String branchId, String operationalLimitsGroupId, int side) { + return getOperationalLimitsGroupAttributes("/networks/{networkUuid}/{variantNum}/branch/{branchId}/types/{resourceType}/operationalLimitsGroup/currentLimits/{operationalLimitsGroupId}/side/{side}", + networkUuid, variantNum, branchId, resourceType, operationalLimitsGroupId, side); + } } diff --git a/network-store-client/src/test/java/com/powsybl/network/store/client/CachedNetworkStoreClientTest.java b/network-store-client/src/test/java/com/powsybl/network/store/client/CachedNetworkStoreClientTest.java index 97c352c9c..829f26a09 100644 --- a/network-store-client/src/test/java/com/powsybl/network/store/client/CachedNetworkStoreClientTest.java +++ b/network-store-client/src/test/java/com/powsybl/network/store/client/CachedNetworkStoreClientTest.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; +import com.powsybl.iidm.network.LimitType; import com.powsybl.iidm.network.SwitchKind; import com.powsybl.iidm.network.VariantManagerConstants; import com.powsybl.iidm.network.extensions.ActivePowerControl; @@ -31,11 +32,12 @@ import org.springframework.test.web.client.MockRestServiceServer; import java.io.IOException; -import java.time.ZonedDateTime; +import java.util.*; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.time.ZonedDateTime; import java.util.concurrent.ForkJoinPool; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -873,6 +875,23 @@ private void loadGeneratorToCache(String identifiableId, UUID networkUuid, Cache server.reset(); } + private void loadLineToCache(String identifiableId, UUID networkUuid, CachedNetworkStoreClient cachedClient) throws JsonProcessingException { + Resource g1Resource = Resource.lineBuilder() + .id(identifiableId) + .attributes(LineAttributes.builder() + .voltageLevelId1("VL_1") + .voltageLevelId2("VL_2") + .build()) + .build(); + String lineJson = objectMapper.writeValueAsString(TopLevelDocument.of(List.of(g1Resource))); + server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + "/lines/" + identifiableId)) + .andExpect(method(GET)) + .andRespond(withSuccess(lineJson, MediaType.APPLICATION_JSON)); + cachedClient.getLine(networkUuid, Resource.INITIAL_VARIANT_NUM, identifiableId); + server.verify(); + server.reset(); + } + private void loadIdentifiableToCache(String identifiableId, UUID networkUuid, CachedNetworkStoreClient cachedClient) throws JsonProcessingException { Resource g1Resource = Resource.generatorBuilder() .id(identifiableId) @@ -939,6 +958,85 @@ private void loadExtensionAttributesToCache(UUID networkUuid, String identifiabl server.reset(); } + @Test + public void testGetOperationalLimitsGroupCache() throws IOException { + CachedNetworkStoreClient cachedClient = new CachedNetworkStoreClient(new BufferedNetworkStoreClient(restStoreClient, ForkJoinPool.commonPool())); + UUID networkUuid = UUID.fromString("7928181c-7977-4592-ba19-88027e4254e4"); + String identifiableId = "LINE"; + String operationalLimitsGroupId = "default"; + + // Load the identifiable in the cache + loadLineToCache(identifiableId, networkUuid, cachedClient); + + // Two successive OperationalLimitsGroup retrieval, only the first should send a REST request, the second uses the cache + TreeMap temporaryLimits = new TreeMap<>(); + temporaryLimits.put(10, TemporaryLimitAttributes.builder() + .operationalLimitsGroupId(operationalLimitsGroupId) + .limitType(LimitType.CURRENT) + .value(12) + .name("temporarylimit1") + .acceptableDuration(10) + .fictitious(false) + .side(1) + .build()); + temporaryLimits.put(15, TemporaryLimitAttributes.builder() + .operationalLimitsGroupId(operationalLimitsGroupId) + .limitType(LimitType.CURRENT) + .value(9) + .name("temporarylimit2") + .acceptableDuration(15) + .fictitious(false) + .side(1) + .build()); + OperationalLimitsGroupAttributes olg1 = OperationalLimitsGroupAttributes.builder() + .currentLimits(LimitsAttributes.builder() + .permanentLimit(1) + .temporaryLimits(temporaryLimits) + .operationalLimitsGroupId(operationalLimitsGroupId) + .build()) + .build(); + getOperationalLimitsGroup(olg1, networkUuid, identifiableId, cachedClient, operationalLimitsGroupId, 1); + + // Not found Operational Limits Group + server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + "/branch/" + "otherId" + "/types/" + ResourceType.LINE + "/operationalLimitsGroup/" + "randomOLGid" + "/side/" + "1")) + .andExpect(method(GET)) + .andRespond(withStatus(HttpStatus.NOT_FOUND)); + + Optional notFoundOperationalLimitsGroup = cachedClient.getOperationalLimitsGroupAttributes(networkUuid, Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, "otherId", "randomOLGid", 1); + assertFalse(notFoundOperationalLimitsGroup.isPresent()); + server.verify(); + server.reset(); + + // When removing the line, the operational Limits Group attributes should be removed from the cache as well + String oneOperationalLimitsGroupAttributes = objectMapper.writeValueAsString(OperationalLimitsGroupAttributesTopLevelDocument.of(olg1)); + server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + "/branch/" + identifiableId + "/types/" + ResourceType.LINE + "/operationalLimitsGroup/" + operationalLimitsGroupId + "/side/" + "1")) + .andExpect(method(GET)) + .andRespond(withSuccess(oneOperationalLimitsGroupAttributes, MediaType.APPLICATION_JSON)); + + Optional olg1Attributes = cachedClient.getOperationalLimitsGroupAttributes(networkUuid, Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, identifiableId, operationalLimitsGroupId, 1); + assertTrue(olg1Attributes.isPresent()); + + cachedClient.removeLines(networkUuid, Resource.INITIAL_VARIANT_NUM, List.of(identifiableId)); + olg1Attributes = cachedClient.getOperationalLimitsGroupAttributes(networkUuid, Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, identifiableId, operationalLimitsGroupId, 1); + assertFalse(olg1Attributes.isPresent()); + } + + private void getOperationalLimitsGroup(OperationalLimitsGroupAttributes operationalLimitsGroupAttributes, UUID networkUuid, String identifiableId, CachedNetworkStoreClient cachedClient, String operationalLimitsGroupId, int side) throws JsonProcessingException { + String oneOperationaLimitsGroupAttributes = objectMapper.writeValueAsString(OperationalLimitsGroupAttributesTopLevelDocument.of(List.of(operationalLimitsGroupAttributes))); + server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + "/branch/" + identifiableId + "/types/" + ResourceType.LINE + "/operationalLimitsGroup/" + operationalLimitsGroupId + "/side/" + side)) + .andExpect(method(GET)) + .andRespond(withSuccess(oneOperationaLimitsGroupAttributes, MediaType.APPLICATION_JSON)); + + Optional operationalLimitsGroupAttributesResult = cachedClient.getOperationalLimitsGroupAttributes(networkUuid, Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, identifiableId, operationalLimitsGroupId, side); + assertTrue(operationalLimitsGroupAttributesResult.isPresent()); + + operationalLimitsGroupAttributesResult = cachedClient.getOperationalLimitsGroupAttributes(networkUuid, Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, identifiableId, operationalLimitsGroupId, side); + assertTrue(operationalLimitsGroupAttributesResult.isPresent()); + + server.verify(); + server.reset(); + } + @Test public void testClone() throws IOException { CachedNetworkStoreClient cachedClient = new CachedNetworkStoreClient(new BufferedNetworkStoreClient(restStoreClient, ForkJoinPool.commonPool())); diff --git a/network-store-client/src/test/java/com/powsybl/network/store/client/PreloadingNetworkStoreClientTest.java b/network-store-client/src/test/java/com/powsybl/network/store/client/PreloadingNetworkStoreClientTest.java index dbe87a1d2..e4413bbef 100644 --- a/network-store-client/src/test/java/com/powsybl/network/store/client/PreloadingNetworkStoreClientTest.java +++ b/network-store-client/src/test/java/com/powsybl/network/store/client/PreloadingNetworkStoreClientTest.java @@ -9,10 +9,9 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; import com.google.common.collect.ImmutableList; -import com.powsybl.iidm.network.Country; -import com.powsybl.iidm.network.LoadType; -import com.powsybl.iidm.network.SwitchKind; +import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.ActivePowerControl; import com.powsybl.iidm.network.extensions.GeneratorStartup; import com.powsybl.network.store.iidm.impl.CachedNetworkStoreClient; @@ -66,6 +65,8 @@ public void setUp() throws IOException { restStoreClient = new RestNetworkStoreClient(restClient); cachedClient = new PreloadingNetworkStoreClient(new CachedNetworkStoreClient(new BufferedNetworkStoreClient(restStoreClient, ForkJoinPool.commonPool())), false, ForkJoinPool.commonPool()); networkUuid = UUID.fromString("7928181c-7977-4592-ba19-88027e4254e4"); + objectMapper.registerModule(new SimpleModule().addKeyDeserializer(OperationalLimitsGroupIdentifier.class, + new OperationalLimitsGroupIdentifierDeserializer())); } @Test @@ -1014,4 +1015,275 @@ public void testGetExtensionsCacheWithClonedNetwork() throws IOException { server.verify(); server.reset(); } + + @Test + public void testGetOperationalLimitsGroupCache() throws IOException { + String identifiableId1 = "lineId"; + String identifiableId2 = "LINE1"; + + // Load the identifiables in the cache + loadTwoLinesToCache(identifiableId1, identifiableId2); + + // Two successive Operational limits groups retrieval, only the first should send a REST request, the second uses the cache + String operationalLimitsGroup1 = "olg1"; + String operationalLimitsGroup2 = "olg2"; + TreeMap temporaryLimits1 = new TreeMap<>(); + temporaryLimits1.put(10, TemporaryLimitAttributes.builder() + .operationalLimitsGroupId(operationalLimitsGroup1) + .limitType(LimitType.CURRENT) + .value(12) + .name("temporarylimit1") + .acceptableDuration(10) + .fictitious(false) + .side(1) + .build()); + OperationalLimitsGroupAttributes olg1 = OperationalLimitsGroupAttributes.builder() + .id(operationalLimitsGroup1) + .currentLimits(LimitsAttributes.builder() + .permanentLimit(1) + .temporaryLimits(temporaryLimits1) + .operationalLimitsGroupId(operationalLimitsGroup1) + .build()) + .build(); + TreeMap temporaryLimits2 = new TreeMap<>(); + temporaryLimits2.put(10, TemporaryLimitAttributes.builder() + .operationalLimitsGroupId(operationalLimitsGroup2) + .limitType(LimitType.CURRENT) + .value(12) + .name("temporarylimit2") + .acceptableDuration(10) + .fictitious(false) + .side(2) + .build()); + OperationalLimitsGroupAttributes olg2 = OperationalLimitsGroupAttributes.builder() + .id(operationalLimitsGroup2) + .currentLimits(LimitsAttributes.builder() + .permanentLimit(1) + .temporaryLimits(temporaryLimits2) + .operationalLimitsGroupId(operationalLimitsGroup2) + .build()) + .build(); + OperationalLimitsGroupIdentifier olgi1 = new OperationalLimitsGroupIdentifier(identifiableId1, operationalLimitsGroup1, 1); + OperationalLimitsGroupIdentifier olgi2 = new OperationalLimitsGroupIdentifier(identifiableId2, operationalLimitsGroup2, 2); + String operationalLimitsGroupAttributes = objectMapper.writerFor(new TypeReference>>() { + }).writeValueAsString(Map.of(identifiableId1, Map.of(olgi1, olg1), identifiableId2, Map.of(olgi2, olg2))); + server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + + "/branch/types/" + ResourceType.LINE + "/operationalLimitsGroup/")) + .andExpect(method(GET)) + .andRespond(withSuccess(operationalLimitsGroupAttributes, MediaType.APPLICATION_JSON)); + + Optional olg1Attributes = cachedClient.getOperationalLimitsGroupAttributes(networkUuid, + Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, identifiableId1, operationalLimitsGroup1, 1); + assertTrue(olg1Attributes.isPresent()); + OperationalLimitsGroupAttributes operationalLimitsGroupAttributes1 = (OperationalLimitsGroupAttributes) olg1Attributes.get(); + assertEquals(operationalLimitsGroup1, operationalLimitsGroupAttributes1.getId()); + assertEquals(1, operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().size()); + assertNotNull(operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10)); + assertEquals("temporarylimit1", operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10).getName()); + assertEquals(12, operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10).getValue(), 0.001); + + olg1Attributes = cachedClient.getOperationalLimitsGroupAttributes(networkUuid, + Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, identifiableId1, operationalLimitsGroup1, 1); + assertTrue(olg1Attributes.isPresent()); + operationalLimitsGroupAttributes1 = (OperationalLimitsGroupAttributes) olg1Attributes.get(); + assertEquals(operationalLimitsGroup1, operationalLimitsGroupAttributes1.getId()); + assertEquals(1, operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().size()); + assertNotNull(operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10)); + assertEquals("temporarylimit1", operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10).getName()); + assertEquals(12, operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10).getValue(), 0.001); + + server.verify(); + server.reset(); + } + + private void loadTwoLinesToCache(String identifiableId1, String identifiableId2) throws JsonProcessingException { + Resource l1Resource = Resource.lineBuilder() + .id(identifiableId1) + .attributes(LineAttributes.builder() + .voltageLevelId1("VL_1") + .voltageLevelId2("VL_2") + .build()) + .build(); + Resource l2Resource = Resource.lineBuilder() + .id(identifiableId2) + .attributes(LineAttributes.builder() + .voltageLevelId1("VL_1") + .voltageLevelId2("VL_2") + .build()) + .build(); + String lineJson = objectMapper.writeValueAsString(TopLevelDocument.of(List.of(l1Resource, l2Resource))); + server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + "/lines")) + .andExpect(method(GET)) + .andRespond(withSuccess(lineJson, MediaType.APPLICATION_JSON)); + cachedClient.getLine(networkUuid, Resource.INITIAL_VARIANT_NUM, identifiableId1); + server.verify(); + server.reset(); + } + + @Test + public void testGetOperationalLimitsGroupEmptyExtensionAttributesCache() throws IOException { + // Two successive ExtensionAttributes retrieval, only the first should send a REST request, the second uses the cache + String identifiableId1 = "GEN"; + String operationalLimitsGroupAttributes = objectMapper.writerFor(new TypeReference>>() { + }).writeValueAsString(Map.of()); + server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + + "/branch/types/" + ResourceType.LINE + "/operationalLimitsGroup/")) + .andExpect(method(GET)) + .andRespond(withSuccess(operationalLimitsGroupAttributes, MediaType.APPLICATION_JSON)); + + String operationalLimitsGroup1 = "test"; + Optional olg1Attributes = cachedClient.getOperationalLimitsGroupAttributes(networkUuid, + Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, identifiableId1, operationalLimitsGroup1, 1); + assertFalse(olg1Attributes.isPresent()); + + olg1Attributes = cachedClient.getOperationalLimitsGroupAttributes(networkUuid, + Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, identifiableId1, operationalLimitsGroup1, 1); + assertFalse(olg1Attributes.isPresent()); + + server.verify(); + server.reset(); + } + + @Test + public void testGetOperationalLimitsGroupCacheWithClonedNetwork() throws IOException { + int targetVariantNum = 1; + String targetVariantId = "new_variant"; + String identifiableId1 = "lineId"; + String identifiableId2 = "LINE1"; + + // Load the identifiables in the cache + loadTwoLinesToCache(identifiableId1, identifiableId2); + + // Two successive Operational limits groups retrieval, only the first should send a REST request, the second uses the cache + String operationalLimitsGroup1 = "olg1"; + String operationalLimitsGroup2 = "olg2"; + TreeMap temporaryLimits1 = new TreeMap<>(); + temporaryLimits1.put(10, TemporaryLimitAttributes.builder() + .operationalLimitsGroupId(operationalLimitsGroup1) + .limitType(LimitType.CURRENT) + .value(12) + .name("temporarylimit1") + .acceptableDuration(10) + .fictitious(false) + .side(1) + .build()); + OperationalLimitsGroupAttributes olg1 = OperationalLimitsGroupAttributes.builder() + .id(operationalLimitsGroup1) + .currentLimits(LimitsAttributes.builder() + .permanentLimit(1) + .temporaryLimits(temporaryLimits1) + .operationalLimitsGroupId(operationalLimitsGroup1) + .build()) + .build(); + TreeMap temporaryLimits2 = new TreeMap<>(); + temporaryLimits2.put(10, TemporaryLimitAttributes.builder() + .operationalLimitsGroupId(operationalLimitsGroup2) + .limitType(LimitType.CURRENT) + .value(12) + .name("temporarylimit2") + .acceptableDuration(10) + .fictitious(false) + .side(2) + .build()); + OperationalLimitsGroupAttributes olg2 = OperationalLimitsGroupAttributes.builder() + .id(operationalLimitsGroup2) + .currentLimits(LimitsAttributes.builder() + .permanentLimit(1) + .temporaryLimits(temporaryLimits2) + .operationalLimitsGroupId(operationalLimitsGroup2) + .build()) + .build(); + OperationalLimitsGroupIdentifier olgi1 = new OperationalLimitsGroupIdentifier(identifiableId1, operationalLimitsGroup1, 1); + OperationalLimitsGroupIdentifier olgi2 = new OperationalLimitsGroupIdentifier(identifiableId2, operationalLimitsGroup2, 2); + String operationalLimitsGroupAttributes = objectMapper.writerFor(new TypeReference>>() { + }).writeValueAsString(Map.of(identifiableId1, Map.of(olgi1, olg1), identifiableId2, Map.of(olgi2, olg2))); + server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + + "/branch/types/" + ResourceType.LINE + "/operationalLimitsGroup/")) + .andExpect(method(GET)) + .andRespond(withSuccess(operationalLimitsGroupAttributes, MediaType.APPLICATION_JSON)); + cachedClient.getAllOperationalLimitsGroupAttributesByResourceType(networkUuid, Resource.INITIAL_VARIANT_NUM, ResourceType.LINE); + server.verify(); + server.reset(); + + // Clone network + server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + "/to/" + targetVariantNum + "?targetVariantId=" + targetVariantId)) + .andExpect(method(PUT)) + .andRespond(withSuccess()); + cachedClient.cloneNetwork(networkUuid, Resource.INITIAL_VARIANT_NUM, targetVariantNum, targetVariantId); + + // Verify that the cache is copied and there is no new fetch + cachedClient.getAllOperationalLimitsGroupAttributesByResourceType(networkUuid, targetVariantNum, ResourceType.LINE); + Optional olg1Attributes = cachedClient.getOperationalLimitsGroupAttributes(networkUuid, + Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, identifiableId1, operationalLimitsGroup1, 1); + assertTrue(olg1Attributes.isPresent()); + OperationalLimitsGroupAttributes operationalLimitsGroupAttributes1 = (OperationalLimitsGroupAttributes) olg1Attributes.get(); + assertEquals(operationalLimitsGroup1, operationalLimitsGroupAttributes1.getId()); + assertEquals(1, operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().size()); + assertNotNull(operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10)); + assertEquals("temporarylimit1", operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10).getName()); + assertEquals(12, operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10).getValue(), 0.001); + server.verify(); + server.reset(); + } + + @Test + public void testGetCurrentLimitsGroupCache() throws IOException { + String identifiableId1 = "lineId"; + String identifiableId2 = "LINE1"; + + // Load the identifiables in the cache + loadTwoLinesToCache(identifiableId1, identifiableId2); + + // Two successive Operational limits groups retrieval, only the first should send a REST request, the second uses the cache + String operationalLimitsGroup1 = "olg1"; + TreeMap temporaryLimits1 = new TreeMap<>(); + temporaryLimits1.put(10, TemporaryLimitAttributes.builder() + .operationalLimitsGroupId(operationalLimitsGroup1) + .limitType(LimitType.CURRENT) + .value(12) + .name("temporarylimit1") + .acceptableDuration(10) + .fictitious(false) + .side(1) + .build()); + OperationalLimitsGroupAttributes olg1 = OperationalLimitsGroupAttributes.builder() + .id(operationalLimitsGroup1) + .currentLimits(LimitsAttributes.builder() + .permanentLimit(1) + .temporaryLimits(temporaryLimits1) + .operationalLimitsGroupId(operationalLimitsGroup1) + .build()) + .build(); + + OperationalLimitsGroupIdentifier olgi1 = new OperationalLimitsGroupIdentifier(identifiableId1, operationalLimitsGroup1, 1); + String operationalLimitsGroupAttributes = objectMapper.writerFor(new TypeReference>>() { + }).writeValueAsString(Map.of(identifiableId1, Map.of(olgi1, olg1))); + server.expect(ExpectedCount.once(), requestTo("/networks/" + networkUuid + "/" + Resource.INITIAL_VARIANT_NUM + + "/branch/types/" + ResourceType.LINE + "/operationalLimitsGroup/currentLimits/")) + .andExpect(method(GET)) + .andRespond(withSuccess(operationalLimitsGroupAttributes, MediaType.APPLICATION_JSON)); + + Optional olg1Attributes = cachedClient.getCurrentLimitsGroupAttributes(networkUuid, + Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, identifiableId1, operationalLimitsGroup1, 1); + assertTrue(olg1Attributes.isPresent()); + OperationalLimitsGroupAttributes operationalLimitsGroupAttributes1 = (OperationalLimitsGroupAttributes) olg1Attributes.get(); + assertEquals(operationalLimitsGroup1, operationalLimitsGroupAttributes1.getId()); + assertEquals(1, operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().size()); + assertNotNull(operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10)); + assertEquals("temporarylimit1", operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10).getName()); + assertEquals(12, operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10).getValue(), 0.001); + + olg1Attributes = cachedClient.getCurrentLimitsGroupAttributes(networkUuid, + Resource.INITIAL_VARIANT_NUM, ResourceType.LINE, identifiableId1, operationalLimitsGroup1, 1); + assertTrue(olg1Attributes.isPresent()); + operationalLimitsGroupAttributes1 = (OperationalLimitsGroupAttributes) olg1Attributes.get(); + assertEquals(operationalLimitsGroup1, operationalLimitsGroupAttributes1.getId()); + assertEquals(1, operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().size()); + assertNotNull(operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10)); + assertEquals("temporarylimit1", operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10).getName()); + assertEquals(12, operationalLimitsGroupAttributes1.getCurrentLimits().getTemporaryLimits().get(10).getValue(), 0.001); + + server.verify(); + server.reset(); + } } diff --git a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/AbstractBranchImpl.java b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/AbstractBranchImpl.java index 27222f4ff..15a847a24 100644 --- a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/AbstractBranchImpl.java +++ b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/AbstractBranchImpl.java @@ -6,14 +6,12 @@ */ package com.powsybl.network.store.iidm.impl; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.extensions.Extension; import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.ConnectablePosition; import com.powsybl.iidm.network.util.LimitViolationUtils; -import com.powsybl.network.store.model.BranchAttributes; -import com.powsybl.network.store.model.LimitsAttributes; -import com.powsybl.network.store.model.OperationalLimitsGroupAttributes; -import com.powsybl.network.store.model.Resource; +import com.powsybl.network.store.model.*; import java.util.*; import java.util.stream.Collectors; @@ -158,6 +156,7 @@ public Optional getApparentPowerLimits(TwoSides side) { @Override public ApparentPowerLimits getNullableApparentPowerLimits1() { + loadOperationalLimitsGroup(TwoSides.ONE); var group = getResource().getAttributes().getSelectedOperationalLimitsGroup1(); return group != null && group.getApparentPowerLimits() != null ? new ApparentPowerLimitsImpl<>(this, TwoSides.ONE, group.getId(), group.getApparentPowerLimits()) @@ -171,6 +170,7 @@ public Optional getApparentPowerLimits1() { @Override public ApparentPowerLimits getNullableApparentPowerLimits2() { + loadOperationalLimitsGroup(TwoSides.TWO); var group = getResource().getAttributes().getSelectedOperationalLimitsGroup2(); return group != null && group.getApparentPowerLimits() != null ? new ApparentPowerLimitsImpl<>(this, TwoSides.TWO, group.getId(), group.getApparentPowerLimits()) @@ -229,6 +229,7 @@ public Optional getActivePowerLimits(TwoSides side) { @Override public ActivePowerLimits getNullableActivePowerLimits1() { + loadOperationalLimitsGroup(TwoSides.ONE); var group = getResource().getAttributes().getSelectedOperationalLimitsGroup1(); return group != null && group.getActivePowerLimits() != null ? new ActivePowerLimitsImpl<>(this, TwoSides.ONE, group.getId(), group.getActivePowerLimits()) @@ -242,6 +243,7 @@ public Optional getActivePowerLimits1() { @Override public ActivePowerLimits getNullableActivePowerLimits2() { + loadOperationalLimitsGroup(TwoSides.TWO); var group = getResource().getAttributes().getSelectedOperationalLimitsGroup2(); return group != null && group.getActivePowerLimits() != null ? new ActivePowerLimitsImpl<>(this, TwoSides.TWO, group.getId(), group.getActivePowerLimits()) @@ -297,6 +299,7 @@ public Optional getCurrentLimits(TwoSides side) { @Override public CurrentLimits getNullableCurrentLimits1() { + loadCurrentLimitsGroup(TwoSides.ONE); var group = getResource().getAttributes().getSelectedOperationalLimitsGroup1(); return group != null && group.getCurrentLimits() != null ? new CurrentLimitsImpl<>(this, TwoSides.ONE, group.getId(), group.getCurrentLimits()) @@ -310,6 +313,7 @@ public Optional getCurrentLimits1() { @Override public CurrentLimits getNullableCurrentLimits2() { + loadCurrentLimitsGroup(TwoSides.TWO); var group = getResource().getAttributes().getSelectedOperationalLimitsGroup2(); return group != null && group.getCurrentLimits() != null ? new CurrentLimitsImpl<>(this, TwoSides.TWO, group.getId(), group.getCurrentLimits()) @@ -372,6 +376,7 @@ public void removeOperationalLimitsGroup1(String id) { if (id.equals(resource.getAttributes().getSelectedOperationalLimitsGroupId1())) { updateResource(res -> res.getAttributes().setSelectedOperationalLimitsGroupId1(null), SELECTED_OPERATIONAL_LIMITS_GROUP_ID1, id, null); + index.removeOperationalLimitsGroupAttributes(resource.getType(), getId(), id, 1); } var oldValue = getResource().getAttributes().getOperationalLimitsGroups1().get(id); updateResource(res -> res.getAttributes().getOperationalLimitsGroups1().remove(id), @@ -439,6 +444,7 @@ public void removeOperationalLimitsGroup2(String id) { if (id.equals(resource.getAttributes().getSelectedOperationalLimitsGroupId2())) { updateResource(res -> res.getAttributes().setSelectedOperationalLimitsGroupId2(null), SELECTED_OPERATIONAL_LIMITS_GROUP_ID2, id, null); + index.notifyUpdate(this, SELECTED_OPERATIONAL_LIMITS_GROUP_ID2, index.getNetwork().getVariantManager().getWorkingVariantId(), id, null); } var oldValue = getResource().getAttributes().getOperationalLimitsGroups2().get(id); updateResource(res -> res.getAttributes().getOperationalLimitsGroups2().remove(id), @@ -609,4 +615,28 @@ public List getTerminals(ThreeSides side) { }; } } + + private void loadOperationalLimitsGroup(TwoSides side) { + String groupId; + switch (side) { + case TwoSides.ONE -> groupId = getResource().getAttributes().getSelectedOperationalLimitsGroupId1(); + case TwoSides.TWO -> groupId = getResource().getAttributes().getSelectedOperationalLimitsGroupId2(); + default -> throw new PowsyblException("can not load limits on branch for a side null"); + } + if (groupId != null) { + index.loadOperationalLimitsGroupAttributes(ResourceType.convert(getType()), getId(), groupId, side.getNum()); + } + } + + private void loadCurrentLimitsGroup(TwoSides side) { + String groupId; + switch (side) { + case TwoSides.ONE -> groupId = getResource().getAttributes().getSelectedOperationalLimitsGroupId1(); + case TwoSides.TWO -> groupId = getResource().getAttributes().getSelectedOperationalLimitsGroupId2(); + default -> throw new PowsyblException("can not load limits on branch for a side null"); + } + if (groupId != null) { + index.loadCurrentLimitsGroupAttributes(ResourceType.convert(getType()), getId(), groupId, side.getNum()); + } + } } diff --git a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CachedNetworkStoreClient.java b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CachedNetworkStoreClient.java index 12f5c53d0..2c49c5ea2 100644 --- a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CachedNetworkStoreClient.java +++ b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CachedNetworkStoreClient.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.powsybl.commons.json.JsonUtil; import com.powsybl.network.store.model.*; @@ -301,9 +302,11 @@ private static void cloneCollection(NetworkCo @Override public void cloneNetwork(UUID networkUuid, int sourceVariantNum, int targetVariantNum, String targetVariantId) { delegate.cloneNetwork(networkUuid, sourceVariantNum, targetVariantNum, targetVariantId); - + SimpleModule module = new SimpleModule(); + module.addKeyDeserializer(OperationalLimitsGroupIdentifier.class, new OperationalLimitsGroupIdentifierDeserializer()); var objectMapper = JsonUtil.createObjectMapper() .registerModule(new JavaTimeModule()) + .registerModule(module) .configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false) .configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false); @@ -1103,6 +1106,33 @@ public void removeExtensionAttributes(UUID networkUuid, int variantNum, Resource delegate.removeExtensionAttributes(networkUuid, variantNum, resourceType, identifiableId, extensionName); } + // limits + @Override + public Optional getOperationalLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String identifiableId, String operationalLimitGroupName, int side) { + return getCache(resourceType).getCollection(networkUuid, variantNum).getOperationalLimitsAttributes(networkUuid, variantNum, resourceType, identifiableId, operationalLimitGroupName, side); + } + + @Override + public void removeOperationalLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String identifiableId, String operationalLimitGroupName, int side) { + getCache(resourceType).getCollection(networkUuid, variantNum).removeOperationalLimitsGroupAttributesByName(identifiableId, operationalLimitGroupName, side); + delegate.removeOperationalLimitsGroupAttributes(networkUuid, variantNum, resourceType, identifiableId, operationalLimitGroupName, side); + } + + @Override + public Map> getAllOperationalLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType resourceType) { + return getCache(resourceType).getCollection(networkUuid, variantNum).getAllOperationalLimitsGroupAttributesByResourceType(networkUuid, variantNum, resourceType); + } + + @Override + public Map> getAllSelectedCurrentLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType resourceType) { + return getCache(resourceType).getCollection(networkUuid, variantNum).getSelectedCurrentLimitsGroupAttributesByResourceType(networkUuid, variantNum, resourceType); + } + + @Override + public Optional getCurrentLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String identifiableId, String operationalLimitGroupName, int side) { + return getCache(resourceType).getCollection(networkUuid, variantNum).getCurrentLimitsAttributes(networkUuid, variantNum, resourceType, identifiableId, operationalLimitGroupName, side); + } + private NetworkCollectionIndex> getCache(ResourceType resourceType) { return switch (resourceType) { case NETWORK -> networksCache; diff --git a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CollectionCache.java b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CollectionCache.java index a43a53e49..1e439ecdb 100644 --- a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CollectionCache.java +++ b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CollectionCache.java @@ -79,6 +79,31 @@ public class CollectionCache { */ private final Map> removedExtensionAttributes = new HashMap<>(); + /** + * Indicates if all the operational limits groups for this collection have been fully loaded and synchronized with the server. + */ + private boolean isFullyLoadedOperationalLimitsGroup = false; + + private boolean isFullyLoadedCurrentLimitsGroup = false; + + /** + * Indicates if all the operational limits groups for a specific identifiable has been fully loaded and synchronized with the server. + */ + private final Set fullyLoadedOperationalLimitsGroupsByBranchIds = new HashSet<>(); + + /** + * Indicates if all the operational limits groups for a specific identifiable has been fully loaded and synchronized with the server. + */ + private final Set fullyLoadedCurrentLimitsGroupsByBranchIds = new HashSet<>(); + + /** + * Map storing sets of removed operational limits names associated with identifiable IDs. + * The map is organized where: + * - The keys are identifiable IDs. + * - The values are sets of operational limits groups names that have been removed. + */ + private final Set removedOperationalLimitsGroupsAttributes = new HashSet<>(); + /** * A function to load one resource from the server. An optional is returned because resource could not exist on * the server. @@ -350,6 +375,8 @@ public CollectionCache clone(ObjectMapper objectMapper, int newVariantNum, Co containerClonedResources.put(id, clonedCache.resources.get(id)); } } + + // extensions for (Map.Entry> entry : removedExtensionAttributes.entrySet()) { clonedCache.removedExtensionAttributes.put(entry.getKey(), new HashSet<>(entry.getValue())); } @@ -358,6 +385,13 @@ public CollectionCache clone(ObjectMapper objectMapper, int newVariantNum, Co clonedCache.fullyLoadedExtensionsByIdentifiableIds.addAll(fullyLoadedExtensionsByIdentifiableIds); clonedCache.fullyLoaded = fullyLoaded; clonedCache.fullyLoadedExtensions = fullyLoadedExtensions; + + // limits + clonedCache.removedOperationalLimitsGroupsAttributes.addAll(removedOperationalLimitsGroupsAttributes); + clonedCache.fullyLoadedOperationalLimitsGroupsByBranchIds.addAll(fullyLoadedOperationalLimitsGroupsByBranchIds); + clonedCache.isFullyLoadedOperationalLimitsGroup = isFullyLoadedOperationalLimitsGroup; + clonedCache.isFullyLoadedCurrentLimitsGroup = isFullyLoadedCurrentLimitsGroup; + clonedCache.containerFullyLoaded.addAll(containerFullyLoaded); clonedCache.removedResources.addAll(removedResources); return clonedCache; @@ -534,4 +568,147 @@ public void removeExtensionAttributesByIdentifiableId(String identifiableId) { getCachedExtensionAttributes(identifiableId).clear(); } } + + // limits + public Optional getOperationalLimitsAttributes(UUID networkUuid, int variantNum, ResourceType type, + String branchId, String operationalLimitGroupName, int side) { + Objects.requireNonNull(branchId); + if (isOperationalLimitsGroupAttributesCached(branchId)) { + return Optional.ofNullable(getCachedOperationalLimitsGroupAttributes(branchId, side).get(operationalLimitGroupName)); + } + + if (!isFullyLoadedOperationalLimitsGroup(branchId) && !isRemovedOperationalLimitsGroupAttributes(branchId, operationalLimitGroupName, side)) { + return delegate.getOperationalLimitsGroupAttributes(networkUuid, variantNum, type, branchId, operationalLimitGroupName, side) + .map(attributes -> { + addOperationalLimitsGroupAttributesToCache(branchId, operationalLimitGroupName, side, attributes); + return attributes; + }); + } + return Optional.empty(); + } + + private Map getCachedOperationalLimitsGroupAttributes(String branchId, int side) { + Resource resource = resources.get(branchId); + if (resource != null && resource.getAttributes() instanceof BranchAttributes branchAttributes) { + return branchAttributes.getOperationalLimitsGroups(side); + } else { + throw new PowsyblException("Cannot manipulate operational limits groups for branch (" + branchId + ") as it has not been loaded into the cache."); + } + } + + private boolean isOperationalLimitsGroupAttributesCached(String branchId) { + return (fullyLoadedOperationalLimitsGroupsByBranchIds.contains(branchId) || isFullyLoadedOperationalLimitsGroup) && resources.containsKey(branchId); + } + + private boolean isCurrentLimitsGroupAttributesCached(String branchId) { + return (fullyLoadedOperationalLimitsGroupsByBranchIds.contains(branchId) || fullyLoadedCurrentLimitsGroupsByBranchIds.contains(branchId) || isFullyLoadedOperationalLimitsGroup || isFullyLoadedCurrentLimitsGroup) && resources.containsKey(branchId); + } + + private boolean isFullyLoadedOperationalLimitsGroup(String branchId) { + return isFullyLoadedOperationalLimitsGroup || fullyLoadedOperationalLimitsGroupsByBranchIds.contains(branchId); + } + + private boolean isRemovedOperationalLimitsGroupAttributes(String branchId, String operationalLimitGroupName, int side) { + return removedResources.contains(branchId) || removedOperationalLimitsGroupsAttributes.contains(OperationalLimitsGroupIdentifier.of(branchId, operationalLimitGroupName, side)); + } + + /** + * Add operational limits groups attributes in the cache for single operational limits groups attributes loading.
+ * This method is only used to get operational limits groups attributes from the server so even if it adds some checks and reduces performance by a tiny bit, + * we avoid to overwrite already loaded operational limits groups attributes because they are referenced in the operational limits groups attributes field of the resources or resourcesByContainerId map, + * but also directly in any identifiable with the iidm api. + */ + private void addOperationalLimitsGroupAttributesToCache(String branchId, String operationalLimitsGroupName, int side, OperationalLimitsGroupAttributes operationalLimitsGroupAttributes) { + Objects.requireNonNull(operationalLimitsGroupAttributes); + getCachedOperationalLimitsGroupAttributes(branchId, side).putIfAbsent(operationalLimitsGroupName, operationalLimitsGroupAttributes); + OperationalLimitsGroupIdentifier identifier = OperationalLimitsGroupIdentifier.of(branchId, operationalLimitsGroupName, side); + removedOperationalLimitsGroupsAttributes.remove(identifier); + fullyLoadedOperationalLimitsGroupsByBranchIds.add(branchId); + } + + public void removeOperationalLimitsGroupAttributesByName(String branchId, String operationalLimitsGroupName, int side) { + Objects.requireNonNull(branchId); + Objects.requireNonNull(operationalLimitsGroupName); + if (resources.containsKey(branchId)) { + getCachedOperationalLimitsGroupAttributes(branchId, side).remove(operationalLimitsGroupName); + OperationalLimitsGroupIdentifier identifier = OperationalLimitsGroupIdentifier.of(branchId, operationalLimitsGroupName, side); + removedOperationalLimitsGroupsAttributes.add(identifier); + } + } + + /** + * Get all the operational limits group attributes for all the identifiables with specified resource type in the cache + */ + public Map> getAllOperationalLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType type) { + if (!isFullyLoadedOperationalLimitsGroup) { + // if collection has not yet been fully loaded we load it from the server + Map> operationalLimitsGroupAttributesMap = + delegate.getAllOperationalLimitsGroupAttributesByResourceType(networkUuid, variantNum, type); + + // we update the full cache and set it as fully loaded + operationalLimitsGroupAttributesMap.forEach(this::addAllOperationalLimitsGroupAttributesToCache); + isFullyLoadedOperationalLimitsGroup = true; + } + return Collections.emptyMap(); + } + + /** + * Get all the operational limits group attributes for all the identifiables with specified resource type in the cache + */ + public Map> getSelectedCurrentLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType type) { + if (!isFullyLoadedCurrentLimitsGroup) { + // if collection has not yet been fully loaded we load it from the server + Map> operationalLimitsGroupAttributesMap = + delegate.getAllSelectedCurrentLimitsGroupAttributesByResourceType(networkUuid, variantNum, type); + + // we update the full cache and set it as fully loaded + operationalLimitsGroupAttributesMap.forEach(this::addAllOperationalLimitsGroupAttributesToCache); + isFullyLoadedCurrentLimitsGroup = true; + } + return Collections.emptyMap(); + } + + public Optional getCurrentLimitsAttributes(UUID networkUuid, int variantNum, ResourceType type, + String branchId, String operationalLimitGroupName, int side) { + Objects.requireNonNull(branchId); + if (isCurrentLimitsGroupAttributesCached(branchId)) { + return Optional.ofNullable(getCachedOperationalLimitsGroupAttributes(branchId, side).get(operationalLimitGroupName)); + } + + if (!isFullyLoadedOperationalLimitsGroup(branchId) && !isRemovedOperationalLimitsGroupAttributes(branchId, operationalLimitGroupName, side)) { + return delegate.getCurrentLimitsGroupAttributes(networkUuid, variantNum, type, branchId, operationalLimitGroupName, side) + .map(attributes -> { + addCurrentLimitsGroupAttributesToCache(branchId, operationalLimitGroupName, side, attributes); + return attributes; + }); + } + return Optional.empty(); + } + + private void addCurrentLimitsGroupAttributesToCache(String branchId, String operationalLimitsGroupName, int side, OperationalLimitsGroupAttributes operationalLimitsGroupAttributes) { + Objects.requireNonNull(operationalLimitsGroupAttributes); + getCachedOperationalLimitsGroupAttributes(branchId, side).putIfAbsent(operationalLimitsGroupName, operationalLimitsGroupAttributes); + OperationalLimitsGroupIdentifier identifier = OperationalLimitsGroupIdentifier.of(branchId, operationalLimitsGroupName, side); + removedOperationalLimitsGroupsAttributes.remove(identifier); + fullyLoadedCurrentLimitsGroupsByBranchIds.add(branchId); + } + + /** + * Add operational limits group attributes to the cache when loading all the operational limits group attributes of an identifiable.
+ * This method is only used to get operational limits group attributes from the server so even if it adds some checks and reduces performance by a tiny bit, + * we avoid to overwrite already loaded operational limits group attributes because they are referenced in the extensionAttributes field of the resources or resourcesByContainerId map, + * but also directly in any identifiable with the iidm api. + */ + private void addAllOperationalLimitsGroupAttributesToCache(String branchId, Map operationalLimitsGroupAttributesMap) { + Objects.requireNonNull(operationalLimitsGroupAttributesMap); + + operationalLimitsGroupAttributesMap.forEach((identifier, limitsGroup) -> { + if (limitsGroup != null) { + getCachedOperationalLimitsGroupAttributes(identifier.getBranchId(), identifier.getSide()) + .putIfAbsent(limitsGroup.getId(), limitsGroup); + removedOperationalLimitsGroupsAttributes.remove(identifier); + } + }); + fullyLoadedOperationalLimitsGroupsByBranchIds.add(branchId); + } } diff --git a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/NetworkObjectIndex.java b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/NetworkObjectIndex.java index c2acd9b30..7491ce983 100644 --- a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/NetworkObjectIndex.java +++ b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/NetworkObjectIndex.java @@ -1240,4 +1240,16 @@ public void loadAllExtensionsAttributesByIdentifiableId(ResourceType type, Strin public void removeExtensionAttributes(ResourceType type, String identifiableId, String extensionName) { storeClient.removeExtensionAttributes(network.getUuid(), workingVariantNum, type, identifiableId, extensionName); } + + public void loadOperationalLimitsGroupAttributes(ResourceType type, String branchId, String operationalLimitGroupName, int side) { + storeClient.getOperationalLimitsGroupAttributes(network.getUuid(), workingVariantNum, type, branchId, operationalLimitGroupName, side); + } + + public void loadCurrentLimitsGroupAttributes(ResourceType type, String branchId, String operationalLimitGroupName, int side) { + storeClient.getCurrentLimitsGroupAttributes(network.getUuid(), workingVariantNum, type, branchId, operationalLimitGroupName, side); + } + + public void removeOperationalLimitsGroupAttributes(ResourceType type, String identifiableId, String operationalLimitGroupName, int side) { + storeClient.removeOperationalLimitsGroupAttributes(network.getUuid(), workingVariantNum, type, identifiableId, operationalLimitGroupName, side); + } } diff --git a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/NetworkStoreClient.java b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/NetworkStoreClient.java index 785309dfc..7b5b1dfb9 100644 --- a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/NetworkStoreClient.java +++ b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/NetworkStoreClient.java @@ -333,6 +333,36 @@ public interface NetworkStoreClient { void removeExtensionAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String identifiableId, String extensionName); + // Limits Attributes + /** + * For one identifiable with a specific identifiable id, retrieves one operational limits group attributes by its name. + * @return {@link LimitsAttributes} which is a subset of an identifiable resource. + */ + Optional getOperationalLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String branchId, String operationalLimitGroupName, int side); + + void removeOperationalLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String identifiableId, String operationalLimitGroupName, int side); + + /** + * For all the identifiables of a specific resource type, retrieves all extension attributes of this identifiable. + * Used for preloading collection strategy. + * @return A {@link Map} where keys are identifiable IDs and values are {@link Map}s where keys are extension names and values are {@link ExtensionAttributes}. + */ + Map> getAllOperationalLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType resourceType); + + /** + * For one identifiable with a specific identifiable id, retrieves one extension attributes by its extension name. + * @return {@link LimitsAttributes} which is a subset of an identifiable resource. The extension attributes can be put in the + * extensionAttributes map of an {@link IdentifiableAttributes} or used to load an extension. + */ + Optional getCurrentLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String branchId, String operationalLimitGroupName, int side); + + /** + * For all the identifiables of a specific resource type, retrieves all extension attributes of this identifiable. + * Used for preloading collection strategy. + * @return A {@link Map} where keys are identifiable IDs and values are {@link Map}s where keys are extension names and values are {@link ExtensionAttributes}. + */ + Map> getAllSelectedCurrentLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType resourceType); + Optional> getIdentifiable(UUID networkUuid, int variantNum, String id); List getIdentifiablesIds(UUID networkUuid, int variantNum); diff --git a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/OfflineNetworkStoreClient.java b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/OfflineNetworkStoreClient.java index fa3d46323..3af3fb699 100644 --- a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/OfflineNetworkStoreClient.java +++ b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/OfflineNetworkStoreClient.java @@ -631,6 +631,31 @@ public void removeExtensionAttributes(UUID uuid, int workingVariantNum, Resource // nothing to do } + @Override + public Optional getOperationalLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String identifiableId, String operationalLimitGroupName, int side) { + return Optional.empty(); + } + + @Override + public void removeOperationalLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String identifiableId, String operationalLimitGroupName, int side) { + // nothing to do + } + + @Override + public Map> getAllOperationalLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType resourceType) { + return Map.of(); + } + + @Override + public Optional getCurrentLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String branchId, String operationalLimitGroupName, int side) { + return Optional.empty(); + } + + @Override + public Map> getAllSelectedCurrentLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType resourceType) { + return Map.of(); + } + @Override public List> getVoltageLevelGrounds(UUID networkUuid, int variantNum, String voltageLevelId) { diff --git a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/MockNetworkStoreClient.java b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/MockNetworkStoreClient.java index df4718fc4..713ff4148 100644 --- a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/MockNetworkStoreClient.java +++ b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/MockNetworkStoreClient.java @@ -97,6 +97,31 @@ public void removeExtensionAttributes(UUID networkUuid, int variantNum, Resource throw new UnsupportedOperationException("Unimplemented method"); } + @Override + public Optional getOperationalLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String identifiableId, String operationalLimitGroupName, int side) { + return Optional.empty(); + } + + @Override + public void removeOperationalLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String identifiableId, String operationalLimitGroupName, int side) { + throw new UnsupportedOperationException("Unimplemented method"); + } + + @Override + public Map> getAllOperationalLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType resourceType) { + return Map.of(); + } + + @Override + public Optional getCurrentLimitsGroupAttributes(UUID networkUuid, int variantNum, ResourceType resourceType, String branchId, String operationalLimitGroupName, int side) { + return Optional.empty(); + } + + @Override + public Map> getAllSelectedCurrentLimitsGroupAttributesByResourceType(UUID networkUuid, int variantNum, ResourceType resourceType) { + return Map.of(); + } + @Override public List getNetworksInfos() { throw new UnsupportedOperationException("Unimplemented method"); diff --git a/network-store-model/src/main/java/com/powsybl/network/store/model/LimitsAttributes.java b/network-store-model/src/main/java/com/powsybl/network/store/model/LimitsAttributes.java index 5e7de5599..70d03f935 100644 --- a/network-store-model/src/main/java/com/powsybl/network/store/model/LimitsAttributes.java +++ b/network-store-model/src/main/java/com/powsybl/network/store/model/LimitsAttributes.java @@ -37,4 +37,10 @@ public class LimitsAttributes { @Schema(description = "List of temporary limits") private TreeMap temporaryLimits; + public void addTemporaryLimit(TemporaryLimitAttributes temporaryLimit) { + if (temporaryLimit == null) { + return; + } + temporaryLimits.put(temporaryLimit.getAcceptableDuration(), temporaryLimit); + } } diff --git a/network-store-model/src/main/java/com/powsybl/network/store/model/OperationalLimitsGroupAttributesTopLevelDocument.java b/network-store-model/src/main/java/com/powsybl/network/store/model/OperationalLimitsGroupAttributesTopLevelDocument.java new file mode 100644 index 000000000..58ddf9ec9 --- /dev/null +++ b/network-store-model/src/main/java/com/powsybl/network/store/model/OperationalLimitsGroupAttributesTopLevelDocument.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2025, 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 com.powsybl.network.store.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Etienne Lesot + */ +@Schema(description = "Top level document compliant with Json API spec") +public class OperationalLimitsGroupAttributesTopLevelDocument extends AbstractTopLevelDocument { + + @JsonCreator + public OperationalLimitsGroupAttributesTopLevelDocument(@JsonProperty("data") List data, @JsonProperty("meta") Map meta) { + super(data, meta); + } + + public static OperationalLimitsGroupAttributesTopLevelDocument empty() { + return new OperationalLimitsGroupAttributesTopLevelDocument(List.of(), new HashMap<>()); + } + + public static OperationalLimitsGroupAttributesTopLevelDocument of(OperationalLimitsGroupAttributes data) { + return new OperationalLimitsGroupAttributesTopLevelDocument(List.of(data), new HashMap<>()); + } + + public static OperationalLimitsGroupAttributesTopLevelDocument of(List data) { + return new OperationalLimitsGroupAttributesTopLevelDocument(data, new HashMap<>()); + } +} diff --git a/network-store-model/src/main/java/com/powsybl/network/store/model/OperationalLimitsGroupIdentifier.java b/network-store-model/src/main/java/com/powsybl/network/store/model/OperationalLimitsGroupIdentifier.java new file mode 100644 index 000000000..9424e1983 --- /dev/null +++ b/network-store-model/src/main/java/com/powsybl/network/store/model/OperationalLimitsGroupIdentifier.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2025, 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 com.powsybl.network.store.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +/** + * @author Etienne Lesot + */ +@Getter +@EqualsAndHashCode +public class OperationalLimitsGroupIdentifier { + String operationalLimitsGroupId; + String branchId; + int side; + + @JsonCreator + public OperationalLimitsGroupIdentifier(@JsonProperty("branchId") String branchId, + @JsonProperty("operationalLimitsGroupId") String operationalLimitsGroupId, + @JsonProperty("side") int side) { + this.branchId = branchId; + this.operationalLimitsGroupId = operationalLimitsGroupId; + this.side = side; + } + + public static OperationalLimitsGroupIdentifier of(String branchId, String operationalLimitsGroupId, int side) { + return new OperationalLimitsGroupIdentifier(branchId, operationalLimitsGroupId, side); + } + + @Override + public String toString() { + ObjectMapper mapper = new ObjectMapper(); + try { + return mapper.writeValueAsString(this); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/network-store-model/src/main/java/com/powsybl/network/store/model/OperationalLimitsGroupIdentifierDeserializer.java b/network-store-model/src/main/java/com/powsybl/network/store/model/OperationalLimitsGroupIdentifierDeserializer.java new file mode 100644 index 000000000..a7a48239c --- /dev/null +++ b/network-store-model/src/main/java/com/powsybl/network/store/model/OperationalLimitsGroupIdentifierDeserializer.java @@ -0,0 +1,17 @@ +package com.powsybl.network.store.model; + +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; + +public class OperationalLimitsGroupIdentifierDeserializer extends KeyDeserializer { + + ObjectMapper mapper = new ObjectMapper(); + + @Override + public OperationalLimitsGroupIdentifier deserializeKey(String s, DeserializationContext deserializationContext) throws IOException { + return mapper.readValue(s, OperationalLimitsGroupIdentifier.class); + } +} diff --git a/network-store-model/src/test/java/com/powsybl/network/store/model/LimitHolderTest.java b/network-store-model/src/test/java/com/powsybl/network/store/model/LimitHolderTest.java index 87820ce90..131c84dee 100644 --- a/network-store-model/src/test/java/com/powsybl/network/store/model/LimitHolderTest.java +++ b/network-store-model/src/test/java/com/powsybl/network/store/model/LimitHolderTest.java @@ -245,4 +245,20 @@ public void getSideListTest() { assertEquals(3, new ThreeWindingsTransformerAttributes().getSideList().size()); assertEquals(1, new DanglingLineAttributes().getSideList().size()); } + + @Test + public void testAddTemporaryLimit() { + assertEquals(2, limitsAttributesA.getTemporaryLimits().size()); + assertEquals(2, tempLimitsA.size()); + limitsAttributesA.addTemporaryLimit(TemporaryLimitAttributes.builder() + .acceptableDuration(25) + .value(1000) + .build()); + assertEquals(3, limitsAttributesA.getTemporaryLimits().size()); + assertEquals(3, tempLimitsA.size()); + + limitsAttributesB.addTemporaryLimit(null); + assertEquals(1, limitsAttributesB.getTemporaryLimits().size()); + assertEquals(1, tempLimitsB.size()); + } } diff --git a/network-store-model/src/test/java/com/powsybl/network/store/model/TopLevelDocumentTest.java b/network-store-model/src/test/java/com/powsybl/network/store/model/TopLevelDocumentTest.java index c59377f86..850b822fc 100644 --- a/network-store-model/src/test/java/com/powsybl/network/store/model/TopLevelDocumentTest.java +++ b/network-store-model/src/test/java/com/powsybl/network/store/model/TopLevelDocumentTest.java @@ -137,4 +137,13 @@ public void testEmptyExtensionAttributes() throws IOException { String jsonRef = "{\"data\":[],\"meta\":{}}"; assertEquals(jsonRef, json); } + + @Test + public void testEmptyOperationalLimitsGroupAttributes() throws IOException { + OperationalLimitsGroupAttributesTopLevelDocument document = OperationalLimitsGroupAttributesTopLevelDocument.empty(); + ObjectMapper objectMapper = JsonUtil.createObjectMapper(); + String json = objectMapper.writeValueAsString(document); + String jsonRef = "{\"data\":[],\"meta\":{}}"; + assertEquals(jsonRef, json); + } }