diff --git a/network-store-server/pom.xml b/network-store-server/pom.xml index dd0a1dcf..5f3f4245 100644 --- a/network-store-server/pom.xml +++ b/network-store-server/pom.xml @@ -101,6 +101,11 @@ lombok + + com.powsybl + powsybl-network-store-iidm-impl + runtime + com.powsybl powsybl-config-classic diff --git a/network-store-server/src/main/java/com/powsybl/network/store/server/ExtensionHandler.java b/network-store-server/src/main/java/com/powsybl/network/store/server/ExtensionHandler.java new file mode 100644 index 00000000..4f5ce593 --- /dev/null +++ b/network-store-server/src/main/java/com/powsybl/network/store/server/ExtensionHandler.java @@ -0,0 +1,208 @@ +/** + * 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 com.powsybl.network.store.server; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; +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.ResourceType; +import com.powsybl.network.store.server.dto.OwnerInfo; +import com.powsybl.network.store.server.exceptions.UncheckedSqlException; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.io.UncheckedIOException; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +import static com.powsybl.network.store.server.NetworkStoreRepository.BATCH_SIZE; +import static com.powsybl.network.store.server.Utils.bindValues; + +/** + * @author Antoine Bouhours + */ +@Component +public class ExtensionHandler { + private final DataSource dataSource; + private final ObjectMapper mapper; + + public ExtensionHandler(DataSource dataSource, ObjectMapper mapper) { + this.dataSource = dataSource; + this.mapper = mapper; + } + + public void insertExtensions(Map> extensions) { + try (var connection = dataSource.getConnection()) { + try (var preparedStmt = connection.prepareStatement(QueryExtensionCatalog.buildInsertExtensionsQuery())) { + List values = new ArrayList<>(6); + List>> list = new ArrayList<>(extensions.entrySet()); + for (List>> subExtensions : Lists.partition(list, BATCH_SIZE)) { + for (Map.Entry> entry : subExtensions) { + for (Map.Entry extension : entry.getValue().entrySet()) { + values.clear(); + values.add(entry.getKey().getEquipmentId()); + values.add(entry.getKey().getEquipmentType().toString()); + values.add(entry.getKey().getNetworkUuid()); + values.add(entry.getKey().getVariantNum()); + values.add(extension.getKey()); + values.add(extension.getValue()); + bindValues(preparedStmt, values, mapper); + preparedStmt.addBatch(); + } + } + preparedStmt.executeBatch(); + } + } + } catch (SQLException e) { + throw new UncheckedSqlException(e); + } + } + + public Map> getExtensionsWithInClause(UUID networkUuid, int variantNum, String columnNameForWhereClause, List valuesForInClause) { + if (valuesForInClause.isEmpty()) { + return Collections.emptyMap(); + } + try (var connection = dataSource.getConnection()) { + var preparedStmt = connection.prepareStatement(QueryExtensionCatalog.buildGetExtensionsWithInClauseQuery(columnNameForWhereClause, valuesForInClause.size())); + preparedStmt.setObject(1, networkUuid); + preparedStmt.setInt(2, variantNum); + for (int i = 0; i < valuesForInClause.size(); i++) { + preparedStmt.setString(3 + i, valuesForInClause.get(i)); + } + + return innerGetExtensions(preparedStmt); + } catch (SQLException e) { + throw new UncheckedSqlException(e); + } + } + + public Map> getExtensions(UUID networkUuid, int variantNum, String columnNameForWhereClause, String valueForWhereClause) { + try (var connection = dataSource.getConnection()) { + var preparedStmt = connection.prepareStatement(QueryExtensionCatalog.buildGetExtensionsQuery(columnNameForWhereClause)); + preparedStmt.setObject(1, networkUuid); + preparedStmt.setInt(2, variantNum); + preparedStmt.setString(3, valueForWhereClause); + + return innerGetExtensions(preparedStmt); + } catch (SQLException e) { + throw new UncheckedSqlException(e); + } + } + + private Map> innerGetExtensions(PreparedStatement preparedStmt) throws SQLException { + try (ResultSet resultSet = preparedStmt.executeQuery()) { + Map> map = new HashMap<>(); + while (resultSet.next()) { + + OwnerInfo owner = new OwnerInfo(); + owner.setEquipmentId(resultSet.getString(1)); + owner.setEquipmentType(ResourceType.valueOf(resultSet.getString(2))); + owner.setNetworkUuid(UUID.fromString(resultSet.getString(3))); + owner.setVariantNum(resultSet.getInt(4)); + + String extensionName = resultSet.getString(5); + ExtensionAttributes extensionValue = mapper.readValue(resultSet.getString(6), ExtensionAttributes.class); + + map.computeIfAbsent(owner, k -> new HashMap<>()); + map.get(owner).put(extensionName, extensionValue); + } + return map; + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + } + + public Map> getExtensionsFromEquipments(UUID networkUuid, List> resources) { + Map> map = new HashMap<>(); + + if (!resources.isEmpty()) { + for (Resource resource : resources) { + Map extensions = resource.getAttributes().getExtensionAttributes(); + if (extensions != null && !extensions.isEmpty()) { + OwnerInfo info = new OwnerInfo( + resource.getId(), + resource.getType(), + networkUuid, + resource.getVariantNum() + ); + map.put(info, extensions); + } + } + } + return map; + } + + public void insertExtensionsInEquipments(UUID networkUuid, List> equipments, Map> extensions) { + if (!extensions.isEmpty() && !equipments.isEmpty()) { + for (Resource equipmentAttributesResource : equipments) { + OwnerInfo owner = new OwnerInfo( + equipmentAttributesResource.getId(), + equipmentAttributesResource.getType(), + networkUuid, + equipmentAttributesResource.getVariantNum() + ); + if (extensions.containsKey(owner)) { + T attributes = equipmentAttributesResource.getAttributes(); + extensions.get(owner).forEach((key, value) -> { + if (attributes != null && attributes.getExtensionAttributes() != null) { + attributes.getExtensionAttributes().put(key, value); + } + }); + } + } + } + } + + public void deleteExtensions(UUID networkUuid, int variantNum, String equipmentId) { + deleteExtensions(networkUuid, variantNum, List.of(equipmentId)); + } + + public void deleteExtensions(UUID networkUuid, int variantNum, List equipmentIds) { + try (var connection = dataSource.getConnection()) { + try (var preparedStmt = connection.prepareStatement(QueryExtensionCatalog.buildDeleteExtensionsVariantEquipmentINQuery(equipmentIds.size()))) { + preparedStmt.setObject(1, networkUuid); + preparedStmt.setInt(2, variantNum); + for (int i = 0; i < equipmentIds.size(); i++) { + preparedStmt.setString(3 + i, equipmentIds.get(i)); + } + preparedStmt.executeUpdate(); + } + } catch (SQLException e) { + throw new UncheckedSqlException(e); + } + } + + public void deleteExtensions(UUID networkUuid, List> resources) { + Map> resourceIdsByVariant = new HashMap<>(); + for (Resource resource : resources) { + List resourceIds = resourceIdsByVariant.get(resource.getVariantNum()); + if (resourceIds != null) { + resourceIds.add(resource.getId()); + } else { + resourceIds = new ArrayList<>(); + resourceIds.add(resource.getId()); + } + resourceIdsByVariant.put(resource.getVariantNum(), resourceIds); + } + resourceIdsByVariant.forEach((k, v) -> deleteExtensions(networkUuid, k, v)); + } + + /** + * Extensions do not always exist in the variant and can be added by a + * modification for example. Instead of implementing an UPSERT, we delete then insert + * the extensions. + */ + public void updateExtensions(UUID networkUuid, List> resources) { + deleteExtensions(networkUuid, resources); + insertExtensions(getExtensionsFromEquipments(networkUuid, resources)); + } +} diff --git a/network-store-server/src/main/java/com/powsybl/network/store/server/Mappings.java b/network-store-server/src/main/java/com/powsybl/network/store/server/Mappings.java index 8f391070..be86f904 100644 --- a/network-store-server/src/main/java/com/powsybl/network/store/server/Mappings.java +++ b/network-store-server/src/main/java/com/powsybl/network/store/server/Mappings.java @@ -106,7 +106,6 @@ public class Mappings { private static final String CONNECTABLE_BUS = "connectableBus"; private static final String CONNECTABLE_BUS_1 = "connectableBus1"; private static final String CONNECTABLE_BUS_2 = "connectableBus2"; - private static final String OPERATING_STATUS = "operatingStatus"; private static final String FICTITIOUS = "fictitious"; private static final String NODE = "node"; private static final String NODE_1 = "node1"; @@ -154,7 +153,6 @@ private void createLineMappings() { lineMappings.addColumnMapping("bus2", new ColumnMapping<>(String.class, LineAttributes::getBus2, LineAttributes::setBus2)); lineMappings.addColumnMapping(CONNECTABLE_BUS_1, new ColumnMapping<>(String.class, LineAttributes::getConnectableBus1, LineAttributes::setConnectableBus1)); lineMappings.addColumnMapping(CONNECTABLE_BUS_2, new ColumnMapping<>(String.class, LineAttributes::getConnectableBus2, LineAttributes::setConnectableBus2)); - lineMappings.addColumnMapping(OPERATING_STATUS, new ColumnMapping<>(String.class, LineAttributes::getOperatingStatus, LineAttributes::setOperatingStatus)); lineMappings.addColumnMapping("r", new ColumnMapping<>(Double.class, LineAttributes::getR, LineAttributes::setR)); lineMappings.addColumnMapping("x", new ColumnMapping<>(Double.class, LineAttributes::getX, LineAttributes::setX)); lineMappings.addColumnMapping("g1", new ColumnMapping<>(Double.class, LineAttributes::getG1, LineAttributes::setG1)); @@ -238,7 +236,6 @@ private void createGeneratorMappings() { } ((MinMaxReactiveLimitsAttributes) attributes.getReactiveLimits()).setMaxQ(value); })); - generatorMappings.addColumnMapping("activePowerControl", new ColumnMapping<>(ActivePowerControlAttributes.class, GeneratorAttributes::getActivePowerControl, GeneratorAttributes::setActivePowerControl)); generatorMappings.addColumnMapping(REGULATION_TERMINAL, new ColumnMapping<>(TerminalRefAttributes.class, GeneratorAttributes::getRegulatingTerminal, GeneratorAttributes::setRegulatingTerminal)); generatorMappings.addColumnMapping("coordinatedReactiveControl", new ColumnMapping<>(CoordinatedReactiveControlAttributes.class, GeneratorAttributes::getCoordinatedReactiveControl, GeneratorAttributes::setCoordinatedReactiveControl)); generatorMappings.addColumnMapping("remoteReactivePowerControl", new ColumnMapping<>(RemoteReactivePowerControlAttributes.class, GeneratorAttributes::getRemoteReactivePowerControl, GeneratorAttributes::setRemoteReactivePowerControl)); @@ -248,7 +245,6 @@ private void createGeneratorMappings() { generatorMappings.addColumnMapping(ALIAS_BY_TYPE, new ColumnMapping<>(Map.class, GeneratorAttributes::getAliasByType, GeneratorAttributes::setAliasByType)); generatorMappings.addColumnMapping(ALIASES_WITHOUT_TYPE, new ColumnMapping<>(Set.class, GeneratorAttributes::getAliasesWithoutType, GeneratorAttributes::setAliasesWithoutType)); generatorMappings.addColumnMapping(POSITION, new ColumnMapping<>(ConnectablePositionAttributes.class, GeneratorAttributes::getPosition, GeneratorAttributes::setPosition)); - generatorMappings.addColumnMapping("generatorStartup", new ColumnMapping<>(GeneratorStartupAttributes.class, GeneratorAttributes::getGeneratorStartupAttributes, GeneratorAttributes::setGeneratorStartupAttributes)); generatorMappings.addColumnMapping("generatorShortCircuit", new ColumnMapping<>(GeneratorShortCircuitAttributes.class, GeneratorAttributes::getGeneratorShortCircuitAttributes, GeneratorAttributes::setGeneratorShortCircuitAttributes)); } @@ -373,7 +369,6 @@ private void createBatteryMappings() { } ((MinMaxReactiveLimitsAttributes) attributes.getReactiveLimits()).setMaxQ(value); })); - batteryMappings.addColumnMapping("activePowerControl", new ColumnMapping<>(ActivePowerControlAttributes.class, BatteryAttributes::getActivePowerControl, BatteryAttributes::setActivePowerControl)); batteryMappings.addColumnMapping("node", new ColumnMapping<>(Integer.class, BatteryAttributes::getNode, BatteryAttributes::setNode)); batteryMappings.addColumnMapping(PROPERTIES, new ColumnMapping<>(Map.class, BatteryAttributes::getProperties, BatteryAttributes::setProperties)); batteryMappings.addColumnMapping(ALIAS_BY_TYPE, new ColumnMapping<>(Map.class, BatteryAttributes::getAliasByType, BatteryAttributes::setAliasByType)); @@ -438,7 +433,6 @@ private void createDanglingLineMappings() { danglingLineMappings.addColumnMapping(POSITION, new ColumnMapping<>(ConnectablePositionAttributes.class, DanglingLineAttributes::getPosition, DanglingLineAttributes::setPosition)); danglingLineMappings.addColumnMapping(SELECTED_OPERATIONAL_LIMITS_GROUP_ID_COLUMN, new ColumnMapping<>(String.class, DanglingLineAttributes::getSelectedOperationalLimitsGroupId, DanglingLineAttributes::setSelectedOperationalLimitsGroupId)); danglingLineMappings.addColumnMapping(TIE_LINE_ID, new ColumnMapping<>(String.class, DanglingLineAttributes::getTieLineId, DanglingLineAttributes::setTieLineId)); - danglingLineMappings.addColumnMapping(OPERATING_STATUS, new ColumnMapping<>(String.class, DanglingLineAttributes::getOperatingStatus, DanglingLineAttributes::setOperatingStatus)); } private void createTieLineMappings() { @@ -449,7 +443,6 @@ private void createTieLineMappings() { tieLineMappings.addColumnMapping(PROPERTIES, new ColumnMapping<>(Map.class, TieLineAttributes::getProperties, TieLineAttributes::setProperties)); tieLineMappings.addColumnMapping(ALIAS_BY_TYPE, new ColumnMapping<>(Map.class, TieLineAttributes::getAliasByType, TieLineAttributes::setAliasByType)); tieLineMappings.addColumnMapping(ALIASES_WITHOUT_TYPE, new ColumnMapping<>(Set.class, TieLineAttributes::getAliasesWithoutType, TieLineAttributes::setAliasesWithoutType)); - tieLineMappings.addColumnMapping(OPERATING_STATUS, new ColumnMapping<>(String.class, TieLineAttributes::getOperatingStatus, TieLineAttributes::setOperatingStatus)); } public TableMapping getTieLineMappings() { @@ -600,7 +593,6 @@ private void createHvdcLineMappings() { hvdcLineMappings.addColumnMapping("converterStationId2", new ColumnMapping<>(String.class, HvdcLineAttributes::getConverterStationId2, HvdcLineAttributes::setConverterStationId2)); hvdcLineMappings.addColumnMapping("hvdcAngleDroopActivePowerControl", new ColumnMapping<>(HvdcAngleDroopActivePowerControlAttributes.class, HvdcLineAttributes::getHvdcAngleDroopActivePowerControl, HvdcLineAttributes::setHvdcAngleDroopActivePowerControl)); hvdcLineMappings.addColumnMapping("hvdcOperatorActivePowerRange", new ColumnMapping<>(HvdcOperatorActivePowerRangeAttributes.class, HvdcLineAttributes::getHvdcOperatorActivePowerRange, HvdcLineAttributes::setHvdcOperatorActivePowerRange)); - hvdcLineMappings.addColumnMapping(OPERATING_STATUS, new ColumnMapping<>(String.class, HvdcLineAttributes::getOperatingStatus, HvdcLineAttributes::setOperatingStatus)); } public TableMapping getTwoWindingsTransformerMappings() { @@ -615,7 +607,6 @@ private void createTwoWindingsTransformerMappings() { twoWindingsTransformerMappings.addColumnMapping("bus2", new ColumnMapping<>(String.class, TwoWindingsTransformerAttributes::getBus2, TwoWindingsTransformerAttributes::setBus2)); twoWindingsTransformerMappings.addColumnMapping(CONNECTABLE_BUS_1, new ColumnMapping<>(String.class, TwoWindingsTransformerAttributes::getConnectableBus1, TwoWindingsTransformerAttributes::setConnectableBus1)); twoWindingsTransformerMappings.addColumnMapping(CONNECTABLE_BUS_2, new ColumnMapping<>(String.class, TwoWindingsTransformerAttributes::getConnectableBus2, TwoWindingsTransformerAttributes::setConnectableBus2)); - twoWindingsTransformerMappings.addColumnMapping(OPERATING_STATUS, new ColumnMapping<>(String.class, TwoWindingsTransformerAttributes::getOperatingStatus, TwoWindingsTransformerAttributes::setOperatingStatus)); twoWindingsTransformerMappings.addColumnMapping("r", new ColumnMapping<>(Double.class, TwoWindingsTransformerAttributes::getR, TwoWindingsTransformerAttributes::setR)); twoWindingsTransformerMappings.addColumnMapping("x", new ColumnMapping<>(Double.class, TwoWindingsTransformerAttributes::getX, TwoWindingsTransformerAttributes::setX)); twoWindingsTransformerMappings.addColumnMapping("g", new ColumnMapping<>(Double.class, TwoWindingsTransformerAttributes::getG, TwoWindingsTransformerAttributes::setG)); @@ -818,7 +809,6 @@ public TableMapping getThreeWindingsTransformerMappings() { private void createThreeWindingsTransformerMappings() { threeWindingsTransformerMappings.addColumnMapping("name", new ColumnMapping<>(String.class, ThreeWindingsTransformerAttributes::getName, ThreeWindingsTransformerAttributes::setName)); - threeWindingsTransformerMappings.addColumnMapping(OPERATING_STATUS, new ColumnMapping<>(String.class, ThreeWindingsTransformerAttributes::getOperatingStatus, ThreeWindingsTransformerAttributes::setOperatingStatus)); threeWindingsTransformerMappings.addColumnMapping("p1", new ColumnMapping<>(Double.class, ThreeWindingsTransformerAttributes::getP1, ThreeWindingsTransformerAttributes::setP1)); threeWindingsTransformerMappings.addColumnMapping("q1", new ColumnMapping<>(Double.class, ThreeWindingsTransformerAttributes::getQ1, ThreeWindingsTransformerAttributes::setQ1)); threeWindingsTransformerMappings.addColumnMapping("p2", new ColumnMapping<>(Double.class, ThreeWindingsTransformerAttributes::getP2, ThreeWindingsTransformerAttributes::setP2)); diff --git a/network-store-server/src/main/java/com/powsybl/network/store/server/NetworkStoreRepository.java b/network-store-server/src/main/java/com/powsybl/network/store/server/NetworkStoreRepository.java index 6f2b351b..bf670d3b 100644 --- a/network-store-server/src/main/java/com/powsybl/network/store/server/NetworkStoreRepository.java +++ b/network-store-server/src/main/java/com/powsybl/network/store/server/NetworkStoreRepository.java @@ -6,7 +6,6 @@ */ package com.powsybl.network.store.server; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -32,19 +31,18 @@ import org.springframework.stereotype.Repository; import javax.sql.DataSource; -import java.io.IOException; -import java.io.UncheckedIOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.time.Instant; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static com.powsybl.network.store.server.Mappings.*; import static com.powsybl.network.store.server.QueryCatalog.*; +import static com.powsybl.network.store.server.Utils.bindAttributes; +import static com.powsybl.network.store.server.Utils.bindValues; /** * @author Geoffroy Jamgotchian @@ -56,12 +54,13 @@ public class NetworkStoreRepository { private static final Logger LOGGER = LoggerFactory.getLogger(NetworkStoreRepository.class); @Autowired - public NetworkStoreRepository(DataSource dataSource, ObjectMapper mapper, Mappings mappings) { + public NetworkStoreRepository(DataSource dataSource, ObjectMapper mapper, Mappings mappings, ExtensionHandler extensionHandler) { this.dataSource = dataSource; this.mappings = mappings; this.mapper = mapper.registerModule(new JavaTimeModule()) .configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false) .configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false); + this.extensionHandler = extensionHandler; } private final DataSource dataSource; @@ -70,69 +69,11 @@ public NetworkStoreRepository(DataSource dataSource, ObjectMapper mapper, Mappin private final Mappings mappings; - private static final int BATCH_SIZE = 1000; + private final ExtensionHandler extensionHandler; - private static final String SUBSTATION_ID = "substationid"; - - private static boolean isCustomTypeJsonified(Class clazz) { - return !( - Integer.class.equals(clazz) || Long.class.equals(clazz) - || Float.class.equals(clazz) || Double.class.equals(clazz) - || String.class.equals(clazz) || Boolean.class.equals(clazz) - || UUID.class.equals(clazz) - || Date.class.isAssignableFrom(clazz) // java.util.Date and java.sql.Date - ); - } - - private void bindValues(PreparedStatement statement, List values) throws SQLException { - int idx = 0; - for (Object o : values) { - if (o instanceof Instant) { - Instant d = (Instant) o; - statement.setDate(++idx, new java.sql.Date(d.toEpochMilli())); - } else if (o == null || !isCustomTypeJsonified(o.getClass())) { - statement.setObject(++idx, o); - } else { - try { - statement.setObject(++idx, mapper.writeValueAsString(o)); - } catch (JsonProcessingException e) { - throw new UncheckedIOException(e); - } - } - } - } + static final int BATCH_SIZE = 1000; - private void bindAttributes(ResultSet resultSet, int columnIndex, ColumnMapping columnMapping, IdentifiableAttributes attributes) { - try { - Object value = null; - if (columnMapping.getClassR() == null || isCustomTypeJsonified(columnMapping.getClassR())) { - String str = resultSet.getString(columnIndex); - if (str != null) { - if (columnMapping.getClassMapKey() != null && columnMapping.getClassMapValue() != null) { - value = mapper.readValue(str, mapper.getTypeFactory().constructMapType(Map.class, columnMapping.getClassMapKey(), columnMapping.getClassMapValue())); - } else { - if (columnMapping.getClassR() == null) { - throw new PowsyblException("Invalid mapping config"); - } - if (columnMapping.getClassR() == Instant.class) { - value = resultSet.getTimestamp(columnIndex).toInstant(); - } else { - value = mapper.readValue(str, columnMapping.getClassR()); - } - } - } - } else { - value = resultSet.getObject(columnIndex, columnMapping.getClassR()); - } - if (value != null) { - columnMapping.set(attributes, value); - } - } catch (SQLException e) { - throw new UncheckedSqlException(e); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } + private static final String SUBSTATION_ID = "substationid"; // network @@ -184,7 +125,7 @@ public Optional> getNetwork(UUID uuid, int variantNu NetworkAttributes attributes = new NetworkAttributes(); MutableInt columnIndex = new MutableInt(2); networkMapping.getColumnsMapping().forEach((columnName, columnMapping) -> { - bindAttributes(resultSet, columnIndex.getValue(), columnMapping, attributes); + bindAttributes(resultSet, columnIndex.getValue(), columnMapping, attributes, mapper); columnIndex.increment(); }); return Optional.of(Resource.networkBuilder() @@ -261,7 +202,7 @@ private void createNetworks(Connection connection, List> resources) { } values.add(attributes.getUuid()); values.add(resource.getVariantNum()); - bindValues(preparedStmt, values); + bindValues(preparedStmt, values, mapper); preparedStmt.addBatch(); } preparedStmt.executeBatch(); @@ -332,6 +273,12 @@ public void deleteNetwork(UUID uuid) { preparedStmt.setObject(1, uuid); preparedStmt.executeUpdate(); } + + // Delete of the extensions (which are not Identifiables objects) + try (var preparedStmt = connection.prepareStatement(QueryExtensionCatalog.buildDeleteExtensionsQuery())) { + preparedStmt.setObject(1, uuid); + preparedStmt.executeUpdate(); + } } catch (SQLException e) { throw new UncheckedSqlException(e); } @@ -384,6 +331,13 @@ public void deleteNetwork(UUID uuid, int variantNum) { preparedStmt.setInt(2, variantNum); preparedStmt.executeUpdate(); } + + // Delete of the extensions (which are not Identifiables objects) + try (var preparedStmt = connection.prepareStatement(QueryExtensionCatalog.buildDeleteExtensionsVariantQuery())) { + preparedStmt.setObject(1, uuid); + preparedStmt.setInt(2, variantNum); + preparedStmt.executeUpdate(); + } } catch (SQLException e) { throw new UncheckedSqlException(e); } @@ -492,6 +446,15 @@ public void cloneNetworkElements(Connection connection, UUID uuid, UUID targetUu preparedStmt.setInt(4, sourceVariantNum); preparedStmt.execute(); } + + // Copy of the Extensions (which are not Identifiables objects) + try (var preparedStmt = connection.prepareStatement(QueryExtensionCatalog.buildCloneExtensionsQuery())) { + preparedStmt.setObject(1, targetUuid); + preparedStmt.setInt(2, targetVariantNum); + preparedStmt.setObject(3, uuid); + preparedStmt.setInt(4, sourceVariantNum); + preparedStmt.execute(); + } } public void cloneNetwork(UUID networkUuid, String sourceVariantId, String targetVariantId, boolean mayOverwrite) { @@ -527,7 +490,7 @@ public void createIdentifiables(UUID networkU for (var mapping : tableMapping.getColumnsMapping().values()) { values.add(mapping.get(attributes)); } - bindValues(preparedStmt, values); + bindValues(preparedStmt, values, mapper); preparedStmt.addBatch(); } preparedStmt.executeBatch(); @@ -536,6 +499,7 @@ public void createIdentifiables(UUID networkU } catch (SQLException e) { throw new UncheckedSqlException(e); } + extensionHandler.insertExtensions(extensionHandler.getExtensionsFromEquipments(networkUuid, resources)); } private Optional> getIdentifiable(UUID networkUuid, int variantNum, String equipmentId, @@ -550,7 +514,7 @@ private Optional> getIdentifiable T attributes = (T) tableMapping.getAttributesSupplier().get(); MutableInt columnIndex = new MutableInt(1); tableMapping.getColumnsMapping().forEach((columnName, columnMapping) -> { - bindAttributes(resultSet, columnIndex.getValue(), columnMapping, attributes); + bindAttributes(resultSet, columnIndex.getValue(), columnMapping, attributes, mapper); columnIndex.increment(); }); Resource.Builder resourceBuilder = (Resource.Builder) tableMapping.getResourceBuilderSupplier().get(); @@ -569,6 +533,8 @@ private Optional> getIdentifiable } private Resource completeResourceInfos(Resource resource, UUID networkUuid, int variantNum, String equipmentId) { + Map> extensionAttributes = extensionHandler.getExtensions(networkUuid, variantNum, EQUIPMENT_ID_COLUMN, equipmentId); + extensionHandler.insertExtensionsInEquipments(networkUuid, List.of((Resource) resource), extensionAttributes); switch (resource.getType()) { case GENERATOR: return completeGeneratorInfos(resource, networkUuid, variantNum, equipmentId); @@ -645,7 +611,7 @@ private List> getIdentifiablesInt T attributes = (T) tableMapping.getAttributesSupplier().get(); MutableInt columnIndex = new MutableInt(2); tableMapping.getColumnsMapping().forEach((columnName, columnMapping) -> { - bindAttributes(resultSet, columnIndex.getValue(), columnMapping, attributes); + bindAttributes(resultSet, columnIndex.getValue(), columnMapping, attributes, mapper); columnIndex.increment(); }); Resource.Builder resourceBuilder = (Resource.Builder) tableMapping.getResourceBuilderSupplier().get(); @@ -661,19 +627,24 @@ private List> getIdentifiablesInt private List> getIdentifiables(UUID networkUuid, int variantNum, TableMapping tableMapping) { + List> identifiables; try (var connection = dataSource.getConnection()) { var preparedStmt = connection.prepareStatement(QueryCatalog.buildGetIdentifiablesQuery(tableMapping.getTable(), tableMapping.getColumnsMapping().keySet())); preparedStmt.setObject(1, networkUuid); preparedStmt.setInt(2, variantNum); - return getIdentifiablesInternal(variantNum, preparedStmt, tableMapping); + identifiables = getIdentifiablesInternal(variantNum, preparedStmt, tableMapping); } catch (SQLException e) { throw new UncheckedSqlException(e); } + Map> extensions = extensionHandler.getExtensions(networkUuid, variantNum, EQUIPMENT_TYPE_COLUMN, tableMapping.getResourceType().toString()); + extensionHandler.insertExtensionsInEquipments(networkUuid, identifiables, extensions); + return identifiables; } private List> getIdentifiablesInContainer(UUID networkUuid, int variantNum, String containerId, Set containerColumns, TableMapping tableMapping) { + List> identifiables; try (var connection = dataSource.getConnection()) { var preparedStmt = connection.prepareStatement(QueryCatalog.buildGetIdentifiablesInContainerQuery(tableMapping.getTable(), tableMapping.getColumnsMapping().keySet(), containerColumns)); preparedStmt.setObject(1, networkUuid); @@ -681,10 +652,14 @@ private List> getIdentifiablesInC for (int i = 0; i < containerColumns.size(); i++) { preparedStmt.setString(3 + i, containerId); } - return getIdentifiablesInternal(variantNum, preparedStmt, tableMapping); + identifiables = getIdentifiablesInternal(variantNum, preparedStmt, tableMapping); } catch (SQLException e) { throw new UncheckedSqlException(e); } + List equipmentsIds = identifiables.stream().map(Resource::getId).toList(); + Map> extensions = extensionHandler.getExtensionsWithInClause(networkUuid, variantNum, EQUIPMENT_ID_COLUMN, equipmentsIds); + extensionHandler.insertExtensionsInEquipments(networkUuid, identifiables, extensions); + return identifiables; } private List> getIdentifiablesInVoltageLevel(UUID networkUuid, int variantNum, String voltageLevelId, TableMapping tableMapping) { @@ -711,7 +686,7 @@ public void updateIdentifiables(U values.add(resource.getVariantNum()); values.add(resource.getId()); values.add(resource.getAttributes().getContainerIds().iterator().next()); - bindValues(preparedStmt, values); + bindValues(preparedStmt, values, mapper); preparedStmt.addBatch(); } preparedStmt.executeBatch(); @@ -720,6 +695,7 @@ public void updateIdentifiables(U } catch (SQLException e) { throw new UncheckedSqlException(e); } + extensionHandler.updateExtensions(networkUuid, resources); } public void updateInjectionsSv(UUID networkUuid, List> resources, String tableName) { @@ -735,7 +711,7 @@ public void updateInjectionsSv(UUID networkUuid, List values.add(networkUuid); values.add(resource.getVariantNum()); values.add(resource.getId()); - bindValues(preparedStmt, values); + bindValues(preparedStmt, values, mapper); preparedStmt.addBatch(); } preparedStmt.executeBatch(); @@ -787,13 +763,14 @@ public void updateIdentifiables(UUID networkU values.add(networkUuid); values.add(resource.getVariantNum()); values.add(resource.getId()); - bindValues(preparedStmt, values); + bindValues(preparedStmt, values, mapper); preparedStmt.addBatch(); } preparedStmt.executeBatch(); } } }); + extensionHandler.updateExtensions(networkUuid, resources); } public void deleteIdentifiable(UUID networkUuid, int variantNum, String id, String tableName) { @@ -807,6 +784,7 @@ public void deleteIdentifiable(UUID networkUuid, int variantNum, String id, Stri } catch (SQLException e) { throw new UncheckedSqlException(e); } + extensionHandler.deleteExtensions(networkUuid, variantNum, id); } // substation @@ -854,7 +832,7 @@ public void updateVoltageLevelsSv(UUID networkUuid, List> getIdentifiable(UUID networkUu if (columnIndex == null) { throw new PowsyblException("Column '" + columnName.toLowerCase() + "' of table '" + tableName + "' not found"); } - bindAttributes(resultSet, columnIndex, columnMapping, attributes); + bindAttributes(resultSet, columnIndex, columnMapping, attributes, mapper); }); Resource resource = new Resource.Builder<>(tableMapping.getResourceType()) @@ -1815,7 +1793,7 @@ public void insertTemporaryLimits(Map limitsInfos) { values.add(temporaryLimit.getValue()); values.add(temporaryLimit.getAcceptableDuration()); values.add(temporaryLimit.isFictitious()); - bindValues(preparedStmt, values); + bindValues(preparedStmt, values, mapper); preparedStmt.addBatch(); } } @@ -1848,7 +1826,7 @@ public void insertPermanentLimits(Map limitsInfos) { values.add(permanentLimit.getSide()); values.add(permanentLimit.getLimitType().toString()); values.add(permanentLimit.getValue()); - bindValues(preparedStmt, values); + bindValues(preparedStmt, values, mapper); preparedStmt.addBatch(); } } @@ -1994,7 +1972,7 @@ public void insertReactiveCapabilityCurvePoints(Map> tapChangerSteps) { values.add(tapChangerStep.getG()); values.add(tapChangerStep.getB()); values.add(tapChangerStep.getAlpha()); - bindValues(preparedStmt, values); + bindValues(preparedStmt, values, mapper); preparedStmt.addBatch(); } } diff --git a/network-store-server/src/main/java/com/powsybl/network/store/server/QueryCatalog.java b/network-store-server/src/main/java/com/powsybl/network/store/server/QueryCatalog.java index 94baa013..940c4653 100644 --- a/network-store-server/src/main/java/com/powsybl/network/store/server/QueryCatalog.java +++ b/network-store-server/src/main/java/com/powsybl/network/store/server/QueryCatalog.java @@ -19,7 +19,7 @@ */ public final class QueryCatalog { - private static final String MINIMAL_VALUE_REQUIREMENT_ERROR = "Function should not be called without at least one value."; + static final String MINIMAL_VALUE_REQUIREMENT_ERROR = "Function should not be called without at least one value."; static final String VARIANT_ID_COLUMN = "variantId"; static final String UUID_COLUMN = "uuid"; diff --git a/network-store-server/src/main/java/com/powsybl/network/store/server/QueryExtensionCatalog.java b/network-store-server/src/main/java/com/powsybl/network/store/server/QueryExtensionCatalog.java new file mode 100644 index 00000000..22ae682d --- /dev/null +++ b/network-store-server/src/main/java/com/powsybl/network/store/server/QueryExtensionCatalog.java @@ -0,0 +1,85 @@ +/** + * 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 com.powsybl.network.store.server; + +import static com.powsybl.network.store.server.QueryCatalog.*; + +/** + * @author Antoine Bouhours + */ +public final class QueryExtensionCatalog { + static final String EXTENSION_TABLE = "extension"; + + private QueryExtensionCatalog() { + } + + public static String buildCloneExtensionsQuery() { + return "insert into " + EXTENSION_TABLE + "(" + EQUIPMENT_ID_COLUMN + ", " + EQUIPMENT_TYPE_COLUMN + + ", " + NETWORK_UUID_COLUMN + ", " + VARIANT_NUM_COLUMN + ", name, value_) select " + + EQUIPMENT_ID_COLUMN + ", " + EQUIPMENT_TYPE_COLUMN + + ", ?, ?, name, value_ from " + EXTENSION_TABLE + " where " + NETWORK_UUID_COLUMN + + " = ? and " + VARIANT_NUM_COLUMN + " = ?"; + } + + public static String buildGetExtensionsQuery(String columnNameForWhereClause) { + return "select " + EQUIPMENT_ID_COLUMN + ", " + + EQUIPMENT_TYPE_COLUMN + ", " + + NETWORK_UUID_COLUMN + ", " + + VARIANT_NUM_COLUMN + ", " + + "name, value_ " + + "from " + EXTENSION_TABLE + " where " + + NETWORK_UUID_COLUMN + " = ? and " + + VARIANT_NUM_COLUMN + " = ? and " + + columnNameForWhereClause + " = ?"; + } + + public static String buildGetExtensionsWithInClauseQuery(String columnNameForInClause, int numberOfValues) { + if (numberOfValues < 1) { + throw new IllegalArgumentException(MINIMAL_VALUE_REQUIREMENT_ERROR); + } + return "select " + EQUIPMENT_ID_COLUMN + ", " + + EQUIPMENT_TYPE_COLUMN + ", " + + NETWORK_UUID_COLUMN + ", " + + VARIANT_NUM_COLUMN + ", " + + "name, value_ " + + "from " + EXTENSION_TABLE + " where " + + NETWORK_UUID_COLUMN + " = ? and " + + VARIANT_NUM_COLUMN + " = ? and " + + columnNameForInClause + " in (" + + "?, ".repeat(numberOfValues - 1) + "?)"; + } + + public static String buildInsertExtensionsQuery() { + return "insert into " + EXTENSION_TABLE + "(" + + EQUIPMENT_ID_COLUMN + ", " + EQUIPMENT_TYPE_COLUMN + ", " + + NETWORK_UUID_COLUMN + " ," + + VARIANT_NUM_COLUMN + ", name, value_)" + + " values (?, ?, ?, ?, ?, ?)"; + } + + public static String buildDeleteExtensionsVariantEquipmentINQuery(int numberOfValues) { + if (numberOfValues < 1) { + throw new IllegalArgumentException(MINIMAL_VALUE_REQUIREMENT_ERROR); + } + return "delete from " + EXTENSION_TABLE + " where " + + NETWORK_UUID_COLUMN + " = ? and " + + VARIANT_NUM_COLUMN + " = ? and " + + EQUIPMENT_ID_COLUMN + " in (" + + "?, ".repeat(numberOfValues - 1) + "?)"; + } + + public static String buildDeleteExtensionsVariantQuery() { + return "delete from " + EXTENSION_TABLE + " where " + + NETWORK_UUID_COLUMN + " = ? and " + + VARIANT_NUM_COLUMN + " = ?"; + } + + public static String buildDeleteExtensionsQuery() { + return "delete from " + EXTENSION_TABLE + " where " + + NETWORK_UUID_COLUMN + " = ?"; + } +} diff --git a/network-store-server/src/main/java/com/powsybl/network/store/server/Utils.java b/network-store-server/src/main/java/com/powsybl/network/store/server/Utils.java new file mode 100644 index 00000000..d6a26128 --- /dev/null +++ b/network-store-server/src/main/java/com/powsybl/network/store/server/Utils.java @@ -0,0 +1,92 @@ +/** + * 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 com.powsybl.network.store.server; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.powsybl.commons.PowsyblException; +import com.powsybl.network.store.model.IdentifiableAttributes; +import com.powsybl.network.store.server.exceptions.UncheckedSqlException; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * @author Geoffroy Jamgotchian + */ +public final class Utils { + private Utils() throws IllegalAccessException { + throw new IllegalAccessException("Utility class can not be initialize."); + } + + private static boolean isCustomTypeJsonified(Class clazz) { + return !( + Integer.class.equals(clazz) || Long.class.equals(clazz) + || Float.class.equals(clazz) || Double.class.equals(clazz) + || String.class.equals(clazz) || Boolean.class.equals(clazz) + || UUID.class.equals(clazz) + || Date.class.isAssignableFrom(clazz) // java.util.Date and java.sql.Date + ); + } + + static void bindValues(PreparedStatement statement, List values, ObjectMapper mapper) throws SQLException { + int idx = 0; + for (Object o : values) { + if (o instanceof Instant d) { + statement.setDate(++idx, new java.sql.Date(d.toEpochMilli())); + } else if (o == null || !isCustomTypeJsonified(o.getClass())) { + statement.setObject(++idx, o); + } else { + try { + statement.setObject(++idx, mapper.writeValueAsString(o)); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + } + } + } + + static void bindAttributes(ResultSet resultSet, int columnIndex, ColumnMapping columnMapping, IdentifiableAttributes attributes, ObjectMapper mapper) { + try { + Object value = null; + if (columnMapping.getClassR() == null || isCustomTypeJsonified(columnMapping.getClassR())) { + String str = resultSet.getString(columnIndex); + if (str != null) { + if (columnMapping.getClassMapKey() != null && columnMapping.getClassMapValue() != null) { + value = mapper.readValue(str, mapper.getTypeFactory().constructMapType(Map.class, columnMapping.getClassMapKey(), columnMapping.getClassMapValue())); + } else { + if (columnMapping.getClassR() == null) { + throw new PowsyblException("Invalid mapping config"); + } + if (columnMapping.getClassR() == Instant.class) { + value = resultSet.getTimestamp(columnIndex).toInstant(); + } else { + value = mapper.readValue(str, columnMapping.getClassR()); + } + } + } + } else { + value = resultSet.getObject(columnIndex, columnMapping.getClassR()); + } + if (value != null) { + columnMapping.set(attributes, value); + } + } catch (SQLException e) { + throw new UncheckedSqlException(e); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/network-store-server/src/main/resources/db/changelog/changesets/changelog_20240306T120000Z.xml b/network-store-server/src/main/resources/db/changelog/changesets/changelog_20240306T120000Z.xml new file mode 100644 index 00000000..fb81b7cf --- /dev/null +++ b/network-store-server/src/main/resources/db/changelog/changesets/changelog_20240306T120000Z.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/network-store-server/src/main/resources/db/changelog/changesets/migrationExtensions_20240306T120000Z.sql b/network-store-server/src/main/resources/db/changelog/changesets/migrationExtensions_20240306T120000Z.sql new file mode 100644 index 00000000..678c63d8 --- /dev/null +++ b/network-store-server/src/main/resources/db/changelog/changesets/migrationExtensions_20240306T120000Z.sql @@ -0,0 +1,83 @@ +INSERT INTO extension (networkuuid, variantnum, equipmentid, equipmentType, name, value_) +SELECT networkuuid, variantnum, id, 'BATTERY', 'activePowerControl', + jsonb_set( + activepowercontrol::jsonb, + '{extensionName}', + '"activePowerControl"' + ) +FROM battery +WHERE activepowercontrol IS NOT NULL; + +INSERT INTO extension (networkuuid, variantnum, equipmentid, equipmentType, name, value_) +SELECT networkuuid, variantnum, id, 'GENERATOR', 'activePowerControl', + jsonb_set( + activepowercontrol::jsonb, + '{extensionName}', + '"activePowerControl"'::jsonb + ) +FROM generator +WHERE activepowercontrol IS NOT NULL; + +INSERT INTO extension (networkuuid, variantnum, equipmentid, equipmentType, name, value_) +SELECT networkuuid, variantnum, id, 'GENERATOR', 'startup', + jsonb_set( + generatorstartup::jsonb, + '{extensionName}', + '"startup"'::jsonb + ) +FROM generator +WHERE generatorstartup IS NOT NULL; + +INSERT INTO extension (networkuuid, variantnum, equipmentid, equipmentType, name, value_) +SELECT networkuuid, variantnum, id, 'LINE', 'operatingStatus', + jsonb_build_object( + 'extensionName', 'operatingStatus', + 'operatingStatus', operatingStatus + ) +FROM line +WHERE operatingStatus IS NOT NULL; + +INSERT INTO extension (networkuuid, variantnum, equipmentid, equipmentType, name, value_) +SELECT networkuuid, variantnum, id, 'HVDC_LINE', 'operatingStatus', + jsonb_build_object( + 'extensionName', 'operatingStatus', + 'operatingStatus', operatingStatus + ) +FROM hvdcline +WHERE operatingStatus IS NOT NULL; + +INSERT INTO extension (networkuuid, variantnum, equipmentid, equipmentType, name, value_) +SELECT networkuuid, variantnum, id, 'TIE_LINE', 'operatingStatus', + jsonb_build_object( + 'extensionName', 'operatingStatus', + 'operatingStatus', operatingStatus + ) +FROM tieline +WHERE operatingStatus IS NOT NULL; + +INSERT INTO extension (networkuuid, variantnum, equipmentid, equipmentType, name, value_) +SELECT networkuuid, variantnum, id, 'DANGLING_LINE', 'operatingStatus', + jsonb_build_object( + 'extensionName', 'operatingStatus', + 'operatingStatus', operatingStatus + ) +FROM danglingline +WHERE operatingStatus IS NOT NULL; + +INSERT INTO extension (networkuuid, variantnum, equipmentid, equipmentType, name, value_) +SELECT networkuuid, variantnum, id, 'TWO_WINDINGS_TRANSFORMER', 'operatingStatus', + jsonb_build_object( + 'extensionName', 'operatingStatus', + 'operatingStatus', operatingStatus + ) +FROM twowindingstransformer +WHERE operatingStatus IS NOT NULL; + +INSERT INTO extension (networkuuid, variantnum, equipmentid, equipmentType, name, value_) +SELECT networkuuid, variantnum, id, 'THREE_WINDINGS_TRANSFORMER', 'operatingStatus', + jsonb_build_object( + 'extensionName', 'operatingStatus', + 'operatingStatus', operatingStatus + ) +FROM threewindingstransformer +WHERE operatingStatus IS NOT NULL; \ No newline at end of file diff --git a/network-store-server/src/main/resources/db/changelog/db.changelog-master.yaml b/network-store-server/src/main/resources/db/changelog/db.changelog-master.yaml index abc83960..65423dd1 100644 --- a/network-store-server/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/network-store-server/src/main/resources/db/changelog/db.changelog-master.yaml @@ -43,3 +43,7 @@ databaseChangeLog: - include: file: changesets/changelog_20240325T120001Z.xml relativeToChangelogFile: true + + - include: + file: changesets/changelog_20240306T120000Z.xml + relativeToChangelogFile: true diff --git a/network-store-server/src/test/java/com/powsybl/network/store/server/ExtensionHandlerTest.java b/network-store-server/src/test/java/com/powsybl/network/store/server/ExtensionHandlerTest.java new file mode 100644 index 00000000..86479fa5 --- /dev/null +++ b/network-store-server/src/test/java/com/powsybl/network/store/server/ExtensionHandlerTest.java @@ -0,0 +1,292 @@ +/** + * 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 com.powsybl.network.store.server; + +import com.powsybl.network.store.model.*; +import com.powsybl.network.store.server.dto.OwnerInfo; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.*; + +import static org.junit.Assert.*; + +/** + * @author Antoine Bouhours + */ +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +public class ExtensionHandlerTest { + + @DynamicPropertySource + static void makeTestDbSuffix(DynamicPropertyRegistry registry) { + UUID uuid = UUID.randomUUID(); + registry.add("testDbSuffix", () -> uuid); + } + + @Autowired + private ExtensionHandler extensionHandler; + + private static final UUID NETWORK_UUID = UUID.fromString("7928181c-7977-4592-ba19-88027e4254e4"); + + @Test + public void insertExtensionsInBatteryTest() { + + String equipmentIdA = "idBatteryA"; + String equipmentIdB = "idBatteryB"; + + OwnerInfo infoBatteryA = new OwnerInfo( + equipmentIdA, + ResourceType.BATTERY, + NETWORK_UUID, + Resource.INITIAL_VARIANT_NUM + ); + OwnerInfo infoBatteryB = new OwnerInfo( + equipmentIdB, + ResourceType.BATTERY, + NETWORK_UUID, + Resource.INITIAL_VARIANT_NUM + ); + OwnerInfo infoBatteryX = new OwnerInfo( + "badID", + ResourceType.BATTERY, + NETWORK_UUID, + Resource.INITIAL_VARIANT_NUM + ); + + Resource resBatteryA = Resource.batteryBuilder() + .id(equipmentIdA) + .attributes(BatteryAttributes.builder() + .voltageLevelId("vl1") + .name("batteryA") + .targetP(250) + .targetQ(100) + .maxP(500) + .minP(100) + .reactiveLimits(MinMaxReactiveLimitsAttributes.builder().maxQ(10).minQ(11).build()) + .build()) + .build(); + + Resource resBatteryB = Resource.batteryBuilder() + .id(equipmentIdB) + .attributes(BatteryAttributes.builder() + .voltageLevelId("vl1") + .name("batteryB") + .targetP(250) + .targetQ(100) + .maxP(500) + .minP(100) + .reactiveLimits(MinMaxReactiveLimitsAttributes.builder().maxQ(5).minQ(6).build()) + .build()) + .build(); + + List> batteries = new ArrayList<>(); + batteries.add(resBatteryA); + batteries.add(resBatteryB); + + assertEquals(resBatteryA.getId(), infoBatteryA.getEquipmentId()); + assertEquals(resBatteryB.getId(), infoBatteryB.getEquipmentId()); + assertNotEquals(resBatteryA.getId(), infoBatteryX.getEquipmentId()); + + Map extensionAttributesMapA = Map.of("activePowerControl", ActivePowerControlAttributes.builder().droop(6.0).participate(true).participationFactor(1.5).build(), + "operatingStatus", OperatingStatusAttributes.builder().operatingStatus("test12").build()); + Map extensionAttributesMapB = Map.of("activePowerControl", ActivePowerControlAttributes.builder().droop(5.0).participate(false).participationFactor(0.5).build(), + "operatingStatus", OperatingStatusAttributes.builder().operatingStatus("test23").build()); + + Map> mapA = new HashMap<>(); + mapA.put(infoBatteryA, extensionAttributesMapA); + Map> mapB = new HashMap<>(); + mapB.put(infoBatteryB, extensionAttributesMapB); + + assertEquals(resBatteryA.getAttributes().getExtensionAttributes(), new HashMap<>()); + assertNull(resBatteryA.getAttributes().getExtensionAttributes().get("activePowerControl")); + assertNull(resBatteryA.getAttributes().getExtensionAttributes().get("operatingStatus")); + assertEquals(resBatteryB.getAttributes().getExtensionAttributes(), new HashMap<>()); + assertNull(resBatteryB.getAttributes().getExtensionAttributes().get("activePowerControl")); + assertNull(resBatteryB.getAttributes().getExtensionAttributes().get("operatingStatus")); + + extensionHandler.insertExtensionsInEquipments(NETWORK_UUID, batteries, new HashMap<>()); + + assertEquals(resBatteryA.getAttributes().getExtensionAttributes(), new HashMap<>()); + assertNull(resBatteryA.getAttributes().getExtensionAttributes().get("activePowerControl")); + assertNull(resBatteryA.getAttributes().getExtensionAttributes().get("operatingStatus")); + assertEquals(resBatteryB.getAttributes().getExtensionAttributes(), new HashMap<>()); + assertNull(resBatteryB.getAttributes().getExtensionAttributes().get("activePowerControl")); + assertNull(resBatteryB.getAttributes().getExtensionAttributes().get("operatingStatus")); + + extensionHandler.insertExtensionsInEquipments(NETWORK_UUID, batteries, mapA); + assertNotNull(resBatteryA.getAttributes().getExtensionAttributes().get("activePowerControl")); + assertNotNull(resBatteryA.getAttributes().getExtensionAttributes().get("operatingStatus")); + assertEquals(resBatteryB.getAttributes().getExtensionAttributes(), new HashMap<>()); + assertNull(resBatteryB.getAttributes().getExtensionAttributes().get("activePowerControl")); + assertNull(resBatteryB.getAttributes().getExtensionAttributes().get("operatingStatus")); + + extensionHandler.insertExtensionsInEquipments(NETWORK_UUID, batteries, mapB); + assertNotNull(resBatteryA.getAttributes().getExtensionAttributes().get("activePowerControl")); + assertNotNull(resBatteryA.getAttributes().getExtensionAttributes().get("operatingStatus")); + assertNotNull(resBatteryB.getAttributes().getExtensionAttributes().get("activePowerControl")); + assertNotNull(resBatteryB.getAttributes().getExtensionAttributes().get("operatingStatus")); + } + + @Test + public void insertExtensionsTest() { + String equipmentIdA = "idBatteryA"; + + OwnerInfo infoBatteryA = new OwnerInfo( + equipmentIdA, + ResourceType.BATTERY, + NETWORK_UUID, + Resource.INITIAL_VARIANT_NUM + ); + Map extensionAttributesMapA = Map.of("activePowerControl", ActivePowerControlAttributes.builder().droop(6.0).participate(true).participationFactor(1.5).build(), + "operatingStatus", OperatingStatusAttributes.builder().operatingStatus("test23").build()); + + Map> mapA = new HashMap<>(); + mapA.put(infoBatteryA, extensionAttributesMapA); + + extensionHandler.insertExtensions(mapA); + + Map> extensions = extensionHandler.getExtensions(NETWORK_UUID, 0, "equipmentId", "idBatteryA"); + Map extensionAttributes = extensions.get(infoBatteryA); + assertEquals(2, extensionAttributes.size()); + assertNotNull(extensionAttributes.get("activePowerControl")); + ActivePowerControlAttributes activePowerControl = (ActivePowerControlAttributes) extensionAttributes.get("activePowerControl"); + assertTrue(activePowerControl.isParticipate()); + assertEquals(6.0, activePowerControl.getDroop(), 0.1); + assertEquals(1.5, activePowerControl.getParticipationFactor(), 0.1); + assertNotNull(extensionAttributes.get("operatingStatus")); + assertEquals(ActivePowerControlAttributes.class, activePowerControl.getClass()); + assertEquals(OperatingStatusAttributes.class, extensionAttributes.get("operatingStatus").getClass()); + } + + @Test + public void getExtensionsWithInClauseTest() { + String equipmentIdA = "idBatteryA"; + String equipmentIdB = "idBatteryB"; + + OwnerInfo infoBatteryA = new OwnerInfo( + equipmentIdA, + ResourceType.BATTERY, + NETWORK_UUID, + Resource.INITIAL_VARIANT_NUM + ); + OwnerInfo infoBatteryB = new OwnerInfo( + equipmentIdB, + ResourceType.BATTERY, + NETWORK_UUID, + Resource.INITIAL_VARIANT_NUM + ); + Map extensionAttributesMapA = Map.of("activePowerControl", ActivePowerControlAttributes.builder().droop(6.0).participate(true).participationFactor(1.5).build(), + "operatingStatus", OperatingStatusAttributes.builder().operatingStatus("test12").build()); + Map extensionAttributesMapB = Map.of("activePowerControl", ActivePowerControlAttributes.builder().droop(5.0).participate(false).participationFactor(0.5).build(), + "operatingStatus", OperatingStatusAttributes.builder().operatingStatus("test23").build()); + + Map> mapA = new HashMap<>(); + mapA.put(infoBatteryA, extensionAttributesMapA); + Map> mapB = new HashMap<>(); + mapB.put(infoBatteryB, extensionAttributesMapB); + + extensionHandler.insertExtensions(mapA); + extensionHandler.insertExtensions(mapB); + + Map> extensions = extensionHandler.getExtensionsWithInClause(NETWORK_UUID, 0, "equipmentId", List.of("idBatteryA", "idBatteryB")); + assertEquals(2, extensions.size()); + assertNotNull(extensions.get(infoBatteryA)); + Map expBatteryA = extensionHandler.getExtensions(NETWORK_UUID, 0, "equipmentId", equipmentIdA).get(infoBatteryA); + assertEquals(expBatteryA, extensions.get(infoBatteryA)); + assertNotNull(extensions.get(infoBatteryB)); + Map expBatteryB = extensionHandler.getExtensions(NETWORK_UUID, 0, "equipmentId", equipmentIdB).get(infoBatteryB); + assertEquals(expBatteryB, extensions.get(infoBatteryB)); + } + + @Test + public void deleteExtensionsTest() { + String equipmentIdA = "idBatteryA"; + String equipmentIdB = "idBatteryB"; + + OwnerInfo infoBatteryA = new OwnerInfo( + equipmentIdA, + ResourceType.BATTERY, + NETWORK_UUID, + Resource.INITIAL_VARIANT_NUM + ); + OwnerInfo infoBatteryB = new OwnerInfo( + equipmentIdB, + ResourceType.BATTERY, + NETWORK_UUID, + Resource.INITIAL_VARIANT_NUM + ); + Map extensionAttributesMapA = Map.of("activePowerControl", ActivePowerControlAttributes.builder().droop(6.0).participate(true).participationFactor(1.5).build(), + "operatingStatus", OperatingStatusAttributes.builder().operatingStatus("test12").build()); + Map extensionAttributesMapB = Map.of("activePowerControl", ActivePowerControlAttributes.builder().droop(5.0).participate(false).participationFactor(0.5).build(), + "operatingStatus", OperatingStatusAttributes.builder().operatingStatus("test23").build()); + + Map> mapA = new HashMap<>(); + mapA.put(infoBatteryA, extensionAttributesMapA); + Map> mapB = new HashMap<>(); + mapB.put(infoBatteryB, extensionAttributesMapB); + + extensionHandler.insertExtensions(mapA); + extensionHandler.insertExtensions(mapB); + + extensionHandler.deleteExtensions(NETWORK_UUID, 0, "idBatteryA"); + Map> extensions = extensionHandler.getExtensionsWithInClause(NETWORK_UUID, 0, "equipmentId", List.of("idBatteryA", "idBatteryB")); + assertEquals(1, extensions.size()); + Resource batteryB = Resource.batteryBuilder().id("idBatteryB").attributes(new BatteryAttributes()).build(); + extensionHandler.deleteExtensions(NETWORK_UUID, List.of(batteryB)); + extensions = extensionHandler.getExtensionsWithInClause(NETWORK_UUID, 0, "equipmentId", List.of("idBatteryA", "idBatteryB")); + assertEquals(0, extensions.size()); + } + + @Test + public void updateExtensionsTest() { + String equipmentIdA = "idBatteryA"; + + OwnerInfo infoBatteryA = new OwnerInfo( + equipmentIdA, + ResourceType.BATTERY, + NETWORK_UUID, + Resource.INITIAL_VARIANT_NUM + ); + Map extensionAttributesMapA = Map.of("activePowerControl", ActivePowerControlAttributes.builder().droop(6.0).participate(true).participationFactor(1.5).build()); + + Map> mapA = new HashMap<>(); + mapA.put(infoBatteryA, extensionAttributesMapA); + + extensionHandler.insertExtensions(mapA); + + Map> extensions = extensionHandler.getExtensions(NETWORK_UUID, 0, "equipmentId", "idBatteryA"); + Map extensionAttributes = extensions.get(infoBatteryA); + assertEquals(1, extensionAttributes.size()); + assertNotNull(extensionAttributes.get("activePowerControl")); + ActivePowerControlAttributes activePowerControl = (ActivePowerControlAttributes) extensionAttributes.get("activePowerControl"); + assertTrue(activePowerControl.isParticipate()); + assertEquals(6.0, activePowerControl.getDroop(), 0.1); + assertEquals(1.5, activePowerControl.getParticipationFactor(), 0.1); + + extensionAttributesMapA = Map.of("activePowerControl", ActivePowerControlAttributes.builder().droop(10.0).participate(false).participationFactor(2.0).build()); + BatteryAttributes batteryAttributes = new BatteryAttributes(); + batteryAttributes.setExtensionAttributes(extensionAttributesMapA); + Resource batteryA = Resource.batteryBuilder().id("idBatteryA").attributes(batteryAttributes).build(); + extensionHandler.updateExtensions(NETWORK_UUID, List.of(batteryA)); + extensions = extensionHandler.getExtensions(NETWORK_UUID, 0, "equipmentId", "idBatteryA"); + extensionAttributes = extensions.get(infoBatteryA); + activePowerControl = (ActivePowerControlAttributes) extensionAttributes.get("activePowerControl"); + assertFalse(activePowerControl.isParticipate()); + assertEquals(10.0, activePowerControl.getDroop(), 0.1); + assertEquals(2.0, activePowerControl.getParticipationFactor(), 0.1); + } +} diff --git a/network-store-server/src/test/java/com/powsybl/network/store/server/NetworkStoreRepositoryTest.java b/network-store-server/src/test/java/com/powsybl/network/store/server/NetworkStoreRepositoryTest.java index 598ab0ec..fa42e7f2 100644 --- a/network-store-server/src/test/java/com/powsybl/network/store/server/NetworkStoreRepositoryTest.java +++ b/network-store-server/src/test/java/com/powsybl/network/store/server/NetworkStoreRepositoryTest.java @@ -567,7 +567,6 @@ public void insertTapChangerStepsInThreeWindingsTranformerTest() { .attributes(ThreeWindingsTransformerAttributes.builder() .name("id3WTransformerA") .ratedU0(1) - .operatingStatus("IN_OPERATION") .leg1(LegAttributes.builder() .ratioTapChangerAttributes(RatioTapChangerAttributes.builder() .lowTapPosition(20) @@ -584,7 +583,6 @@ public void insertTapChangerStepsInThreeWindingsTranformerTest() { .attributes(ThreeWindingsTransformerAttributes.builder() .name("id3WTransformerB") .ratedU0(1) - .operatingStatus("IN_OPERATION") .leg1(new LegAttributes()) .leg2(LegAttributes.builder() .phaseTapChangerAttributes(PhaseTapChangerAttributes.builder() diff --git a/pom.xml b/pom.xml index 7fc85652..3e6e4401 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 1.0 - 2.8.0 + 2.9.0