From f5f34babdf3e91687291b5dd20e3d19c4250090d Mon Sep 17 00:00:00 2001 From: massifben <105049157+massifben@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:51:46 +0100 Subject: [PATCH] feat(#447): RSR-1116 - update LN Mod.stVal based on COMPAS-LNodeStatus --- .../sct/commons/DataTypeTemplatesService.java | 2 +- .../sct/commons/LNodeStatusService.java | 100 ++++ .../compas/sct/commons/util/PrivateUtils.java | 10 + .../sct/commons/LNodeStatusServiceTest.java | 188 +++++++ .../sct/commons/testhelpers/SclHelper.java | 473 +++++++++--------- .../sct/commons/util/PrivateUtilsTest.java | 14 + .../resources/scl-lnodestatus/lnodestatus.scd | 125 +++++ .../lnodestatus_without_mod_stval.scd | 101 ++++ 8 files changed, 787 insertions(+), 226 deletions(-) create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/LNodeStatusService.java create mode 100644 sct-commons/src/test/java/org/lfenergy/compas/sct/commons/LNodeStatusServiceTest.java create mode 100644 sct-commons/src/test/resources/scl-lnodestatus/lnodestatus.scd create mode 100644 sct-commons/src/test/resources/scl-lnodestatus/lnodestatus_without_mod_stval.scd diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/DataTypeTemplatesService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/DataTypeTemplatesService.java index c9ebe73f9..3e6c643ab 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/DataTypeTemplatesService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/DataTypeTemplatesService.java @@ -109,7 +109,7 @@ public Optional findDoLinkedToDa(TDataTypeTemplates dtt, String lN // Prepare DataObject DataObject dataObject = new DataObject(tdo.getName(), tdoType.getCdc(), doLinkedToDaFilter.sdoNames()); // Search first DA from last DoType - return sdoOrDAService.findDA(lastDoType, tda1 -> tda1.getName().equals(doLinkedToDaFilter.daName())) + return sdoOrDAService.findDA(lastDoType, tda -> tda.getName().equals(doLinkedToDaFilter.daName())) .flatMap(tda -> { // Prepare DataAttribute DataAttribute dataAttribute = new DataAttribute(); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/LNodeStatusService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/LNodeStatusService.java new file mode 100644 index 000000000..febcf3c33 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/LNodeStatusService.java @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2024 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons; + +import lombok.RequiredArgsConstructor; +import org.lfenergy.compas.scl2007b4.model.*; +import org.lfenergy.compas.sct.commons.domain.DataAttribute; +import org.lfenergy.compas.sct.commons.domain.DoLinkedToDa; +import org.lfenergy.compas.sct.commons.domain.DoLinkedToDaFilter; +import org.lfenergy.compas.sct.commons.dto.SclReportItem; +import org.lfenergy.compas.sct.commons.util.CommonConstants; +import org.lfenergy.compas.sct.commons.util.PrivateUtils; +import org.lfenergy.compas.sct.commons.util.SclConstructorHelper; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +@RequiredArgsConstructor +public class LNodeStatusService { + + private static final String LNODE_STATUS_PRIVATE_TYPE = "COMPAS-LNodeStatus"; + private final LdeviceService ldeviceService; + private final LnService lnService; + private final DataTypeTemplatesService dataTypeTemplatesService; + + public List updateLnModStValBasedOnLNodeStatus(SCL scl) { + return scl.getSubstation().stream() + .flatMap(tSubstation -> tSubstation.getVoltageLevel().stream()) + .flatMap(tVoltageLevel -> tVoltageLevel.getBay().stream()) + .flatMap(tBay -> tBay.getFunction().stream()) + .flatMap(tFunction -> tFunction.getLNode().stream()) + .map(tlNode -> updateSingleLnModStValBasedOnLNodeStatus(scl, tlNode)) + .filter(Objects::nonNull) + .toList(); + } + + private SclReportItem updateSingleLnModStValBasedOnLNodeStatus(SCL scl, TLNode tlNode) { + String lNodeLNS = PrivateUtils.extractStringPrivate(tlNode, LNODE_STATUS_PRIVATE_TYPE).orElse(null); + if (!"on".equals(lNodeLNS) && !"off".equals(lNodeLNS)) { + return SclReportItem.error(lNodePath(tlNode), "The private %s of the LNode has invalid value. Expecting one of [on, off] but got : %s".formatted(LNODE_STATUS_PRIVATE_TYPE, lNodeLNS)); + } + TAnyLN anyLn = findLn(scl, tlNode.getIedName(), tlNode.getLdInst(), tlNode.getLnClass().getFirst(), tlNode.getLnInst(), tlNode.getPrefix()).orElse(null); + if (anyLn == null) { + return SclReportItem.error(lNodePath(tlNode), "LNode in Substation section does not have a matching LN in IED section"); + } + String anyLnLNS = PrivateUtils.extractStringPrivate(anyLn, LNODE_STATUS_PRIVATE_TYPE).orElse(null); + if (anyLnLNS == null) { + return SclReportItem.error(lnPath(tlNode), "LN does not have a private %s".formatted(LNODE_STATUS_PRIVATE_TYPE)); + } + if (!anyLnLNS.contains(lNodeLNS)) { + return SclReportItem.error(lnPath(tlNode), "Cannot set DAI Mod.stVal to %s, because LN private %s is set to %s".formatted(lNodeLNS, LNODE_STATUS_PRIVATE_TYPE, anyLnLNS)); + } + TDAI daiModStVal = lnService.getDaiModStVal(anyLn).orElse(null); + if (daiModStVal == null) { + return null; // do nothing if DAI Mod.stVal is missing + } + List modStValEnumValues = getModStValEnumValues(scl.getDataTypeTemplates(), anyLn.getLnType()).toList(); + if (!modStValEnumValues.contains(lNodeLNS)) { + return SclReportItem.error(lnPath(tlNode), "Cannot set DAI Mod.stVal to '%s' because value is not in EnumType %s".formatted(lNodeLNS, modStValEnumValues)); + } + daiModStVal.getVal().clear(); + daiModStVal.getVal().add(SclConstructorHelper.newVal(lNodeLNS)); + return null; // no error + } + + private static String lnPath(TLNode tlNode) { + return "IED(%s)/LD(%s)/LN[%s,%s,%s]".formatted(tlNode.getIedName(), tlNode.getLdInst(), tlNode.getLnClass().getFirst(), tlNode.getLnInst(), tlNode.getPrefix()); + } + + private static String lNodePath(TLNode tlNode) { + return "LNode(iedName=%s, ldInst=%s, lnClass=%s, lnInst=%s, prefix=%s)".formatted(tlNode.getIedName(), tlNode.getLdInst(), tlNode.getLnClass().getFirst(), tlNode.getLnInst(), tlNode.getPrefix()); + } + + private Stream getModStValEnumValues(TDataTypeTemplates dataTypeTemplates, String lnType) { + return dataTypeTemplatesService.findDoLinkedToDa(dataTypeTemplates, lnType, DoLinkedToDaFilter.from(CommonConstants.MOD_DO_NAME, CommonConstants.STVAL_DA_NAME)) + .map(DoLinkedToDa::dataAttribute) + .filter(dataAttribute -> TPredefinedBasicTypeEnum.ENUM.equals(dataAttribute.getBType())) + .map(DataAttribute::getType) + .flatMap(enumId -> + dataTypeTemplates.getEnumType().stream() + .filter(tEnumType -> tEnumType.getId().equals(enumId)) + .findFirst()) + .stream() + .flatMap(tEnumType -> tEnumType.getEnumVal().stream()) + .map(TEnumVal::getValue); + } + + private Optional findLn(SCL scl, String iedName, String ldInst, String lnClass, String lnInst, String prefix) { + return scl.getIED().stream() + .filter(tied -> iedName.equals(tied.getName())) + .findFirst() + .flatMap(tied -> ldeviceService.findLdevice(tied, tlDevice -> ldInst.equals(tlDevice.getInst()))) + .flatMap(tlDevice -> lnService.findAnyLn(tlDevice, tAnyLN -> lnService.matchesLn(tAnyLN, lnClass, lnInst, prefix))); + + } +} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/PrivateUtils.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/PrivateUtils.java index 049f1e48a..8d9cbfde1 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/PrivateUtils.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/PrivateUtils.java @@ -7,6 +7,7 @@ import jakarta.xml.bind.JAXBElement; import lombok.NonNull; import lombok.experimental.UtilityClass; +import org.apache.commons.lang3.StringUtils; import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.dto.PrivateLinkedToStds; import org.lfenergy.compas.sct.commons.exception.ScdException; @@ -320,4 +321,13 @@ public static void copyCompasICDHeaderFromLNodePrivateIntoSTDPrivate(TPrivate st } + public static Optional extractStringPrivate(TBaseElement tBaseElement, String privateType) { + return tBaseElement.getPrivate().stream() + .filter(tPrivate -> privateType.equals(tPrivate.getType())) + .flatMap(tPrivate -> tPrivate.getContent().stream()) + .filter(String.class::isInstance) + .map(String.class::cast) + .filter(StringUtils::isNotBlank) + .findFirst(); } +} diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/LNodeStatusServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/LNodeStatusServiceTest.java new file mode 100644 index 000000000..65431e392 --- /dev/null +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/LNodeStatusServiceTest.java @@ -0,0 +1,188 @@ +// SPDX-FileCopyrightText: 2024 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.lfenergy.compas.scl2007b4.model.SCL; +import org.lfenergy.compas.scl2007b4.model.TAnyLN; +import org.lfenergy.compas.scl2007b4.model.TLN; +import org.lfenergy.compas.sct.commons.dto.SclReportItem; +import org.lfenergy.compas.sct.commons.testhelpers.SclHelper; +import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; + +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Named.named; +import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.*; + +class LNodeStatusServiceTest { + + private LNodeStatusService lNodeStatusService; + + @BeforeEach + void setUp() { + lNodeStatusService = new LNodeStatusService(new LdeviceService(), new LnService(), new DataTypeTemplatesService()); + } + + @ParameterizedTest + @MethodSource("provideUpdateModStVal") + void updateLnStatusBasedOnPrivateLNodeStatus_should_update_Mod_stVal(String ldInst, String lnClass, String lnInst, String expected) { + // Given + SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd"); + // When + List sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl); + // Then + assertThat(sclReportItems).isEmpty(); + assertThat(findDai(scl, "IED_NAME_1", ldInst, lnClass, lnInst, "", "Mod", "stVal")) + .map(SclHelper::getValue) + .hasValue(expected); + } + + public static Stream provideUpdateModStVal() { + return Stream.of( + Arguments.of(named("LN 'on' à mettre à 'on'", "LDEVICE_1"), "PDIS", "1", "on"), + Arguments.of(named("LN 'off;on' à mettre à 'on'", "LDEVICE_1"), "PDIS", "2", "on"), + Arguments.of(named("LN 'off' à mettre à 'off'", "LDEVICE_1"), "PDIS", "3", "off"), + Arguments.of(named("LN 'off;on' à mettre à 'off'", "LDEVICE_1"), "PDIS", "3", "off"), + Arguments.of(named("LN0 'on' à mettre à 'on'", "LDEVICE_1"), "LLN0", "", "on"), + Arguments.of(named("LN0 'off;on' à mettre à 'on'", "LDEVICE_2"), "LLN0", "", "on"), + Arguments.of(named("LN0 'off' à mettre à 'off'", "LDEVICE_3"), "LLN0", "", "off"), + Arguments.of(named("LN0 'off;on' à mettre à 'off'", "LDEVICE_4"), "LLN0", "", "off") + ); + } + + @Test + void updateLnStatusBasedOnPrivateLNodeStatus_do_nothing_if_DAI_Mod_stVal_is_missing() { + // Given + SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus_without_mod_stval.scd"); + // When + List sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl); + // Then + assertThat(sclReportItems).isEmpty(); + assertThat(getLDevices(scl.getIED().getFirst()) + .flatMap(tlDevice -> Stream.concat(Stream.of(tlDevice.getLN0()), tlDevice.getLN().stream()))) + .flatMap(TAnyLN::getDOI) + .isEmpty(); + } + + @Test + void updateLnStatusBasedOnPrivateLNodeStatus_when_no_compasLNodeStatus_in_LNode_should_return_error() { + // Given + SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd"); + scl.getSubstation().getFirst().getVoltageLevel().getFirst().getBay().getFirst().getFunction().getFirst().getLNode().getFirst() + .getPrivate().clear(); + // When + List sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl); + // Then + assertThat(sclReportItems).containsExactly( + SclReportItem.error("LNode(iedName=IED_NAME_1, ldInst=LDEVICE_1, lnClass=PDIS, lnInst=1, prefix=)", + "The private COMPAS-LNodeStatus of the LNode has invalid value. Expecting one of [on, off] but got : null") + ); + } + + @Test + void updateLnStatusBasedOnPrivateLNodeStatus_when_invalid_compasLNodeStatus_value_in_LNode_should_return_error() { + // Given + SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd"); + scl.getSubstation().getFirst().getVoltageLevel().getFirst().getBay().getFirst().getFunction().getFirst().getLNode().getFirst() + .getPrivate().getFirst().getContent().set(0, "helloworld"); + // When + List sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl); + // Then + assertThat(sclReportItems).containsExactly( + SclReportItem.error("LNode(iedName=IED_NAME_1, ldInst=LDEVICE_1, lnClass=PDIS, lnInst=1, prefix=)", + "The private COMPAS-LNodeStatus of the LNode has invalid value. Expecting one of [on, off] but got : helloworld") + ); + } + + @Test + void updateLnStatusBasedOnPrivateLNodeStatus_when_LNode_does_not_match_any_LN_should_return_error() { + // Given + SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd"); + ((TLN) findAnyLn(scl, "IED_NAME_1", "LDEVICE_1", "PDIS", "1", "")) + .setPrefix("helloworld"); + // When + List sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl); + // Then + assertThat(sclReportItems).containsExactly( + SclReportItem.error("LNode(iedName=IED_NAME_1, ldInst=LDEVICE_1, lnClass=PDIS, lnInst=1, prefix=)", + "LNode in Substation section does not have a matching LN in IED section") + ); + } + + @Test + void updateLnStatusBasedOnPrivateLNodeStatus_when_no_compasLNodeStatus_in_LN_should_return_error() { + // Given + SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd"); + findAnyLn(scl, "IED_NAME_1", "LDEVICE_1", "PDIS", "1", "") + .getPrivate().clear(); + // When + List sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl); + // Then + assertThat(sclReportItems).containsExactly( + SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_1)/LN[PDIS,1,]", + "LN does not have a private COMPAS-LNodeStatus") + ); + } + + @Test + void updateLnStatusBasedOnPrivateLNodeStatus_when_compasLNodeStatus_is_on_in_LNode_but_off_in_LN_should_return_error() { + // Given + SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd"); + findAnyLn(scl, "IED_NAME_1", "LDEVICE_1", "LLN0", "", "") + .getPrivate().getFirst().getContent().set(0, "off"); + findAnyLn(scl, "IED_NAME_1", "LDEVICE_1", "PDIS", "1", "") + .getPrivate().getFirst().getContent().set(0, "off"); + // When + List sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl); + // Then + assertThat(sclReportItems).containsExactlyInAnyOrder( + SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_1)/LN[LLN0,,]", + "Cannot set DAI Mod.stVal to on, because LN private COMPAS-LNodeStatus is set to off"), + SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_1)/LN[PDIS,1,]", + "Cannot set DAI Mod.stVal to on, because LN private COMPAS-LNodeStatus is set to off") + ); + } + + @Test + void updateLnStatusBasedOnPrivateLNodeStatus_when_compasLNodeStatus_is_off_in_LNode_but_on_in_LN_should_return_error() { + // Given + SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd"); + findAnyLn(scl, "IED_NAME_1", "LDEVICE_4", "LLN0", "", "") + .getPrivate().getFirst().getContent().set(0, "on"); + findAnyLn(scl, "IED_NAME_1", "LDEVICE_1", "PDIS", "4", "") + .getPrivate().getFirst().getContent().set(0, "on"); + // When + List sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl); + // Then + assertThat(sclReportItems).containsExactlyInAnyOrder( + SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_4)/LN[LLN0,,]", + "Cannot set DAI Mod.stVal to off, because LN private COMPAS-LNodeStatus is set to on"), + SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_1)/LN[PDIS,4,]", + "Cannot set DAI Mod.stVal to off, because LN private COMPAS-LNodeStatus is set to on") + ); + } + + @Test + void updateLnStatusBasedOnPrivateLNodeStatus_when_Mod_stVal_enumType_does_not_contains_value_should_return_error() { + // Given + SCL scl = SclTestMarshaller.getSCLFromFile("/scl-lnodestatus/lnodestatus.scd"); + scl.getDataTypeTemplates().getEnumType().getFirst().getEnumVal().removeIf(tEnumVal -> tEnumVal.getValue().equals("on")); + // When + List sclReportItems = lNodeStatusService.updateLnModStValBasedOnLNodeStatus(scl); + // Then + assertThat(sclReportItems).contains( + SclReportItem.error("IED(IED_NAME_1)/LD(LDEVICE_1)/LN[PDIS,1,]", + "Cannot set DAI Mod.stVal to 'on' because value is not in EnumType [off, test]") + ); + } + +} diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/SclHelper.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/SclHelper.java index 0d843c335..eb24171e4 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/SclHelper.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/SclHelper.java @@ -21,6 +21,7 @@ import java.util.Optional; import java.util.stream.Stream; +import static org.apache.commons.lang3.StringUtils.trimToEmpty; import static org.assertj.core.api.Assertions.assertThat; import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newConnectedAp; import static org.lfenergy.compas.sct.commons.util.Utils.lnClassEquals; @@ -99,257 +100,279 @@ public static LNAdapter findLn(SCL scl, String iedName, String ldInst, String ln ); } - public static IDataParentAdapter findDoiOrSdi(AbstractLNAdapter lnAdapter, String dataTypeRef) { - if (dataTypeRef.length() < 1) { - Assertions.fail("dataTypeRef must at least contain a DO, but got: " + dataTypeRef); + public static TAnyLN findAnyLn(SCL scl, String iedName, String ldInst, String lnClass, String lnInst, String prefix) { + TIED ied = scl.getIED().stream().filter(tied -> tied.getName().equals(iedName)) + .findFirst().orElseThrow(() -> new AssertionFailedError("IED with name=%s not found".formatted(iedName))); + TLDevice tlDevice = getLDevices(ied) + .filter(tlDevice1 -> tlDevice1.getInst().equals(ldInst)) + .findFirst().orElseThrow(() -> new AssertionFailedError("LDevice with inst=%s not found".formatted(ldInst))); + if (lnClass.equals(TLLN0Enum.LLN_0.value()) && tlDevice.isSetLN0()) { + return tlDevice.getLN0(); } - String[] names = dataTypeRef.split("\\."); - IDataParentAdapter parentAdapter = lnAdapter.getDOIAdapterByName(names[0]); - for (int i = 1; i < names.length; i++) { - parentAdapter = parentAdapter.getStructuredDataAdapterByName(names[i]); - } - return parentAdapter; + return tlDevice.getLN().stream() + .filter(tln -> Utils.lnClassEquals(tln.getLnClass(), lnClass) && trimToEmpty(tln.getInst()).equals(trimToEmpty(lnInst)) && trimToEmpty(tln.getPrefix()).equals(trimToEmpty(prefix))) + .findFirst() + .orElseThrow(() -> new AssertionFailedError("LN (lnClass=%s, lnInst=%s, lnPrefix=%s) not found".formatted(lnClass, lnInst, prefix))); } - public static Optional findDai(SCL scl, String iedName, String ldInst, String doiName, String daiName) { - return scl.getIED().stream().filter(tied -> tied.getName().equals(iedName)) - .flatMap(tied -> tied.getAccessPoint().stream()) - .flatMap(tAccessPoint -> tAccessPoint.getServer().getLDevice().stream()) - .filter(tlDevice -> tlDevice.getInst().equals(ldInst)) - .flatMap(tlDevice -> tlDevice.getLN0().getDOI().stream()) - .filter(tdoi -> tdoi.getName().equals(doiName)) - .flatMap(tdoi -> tdoi.getSDIOrDAI().stream().map(tUnNaming -> (TDAI)tUnNaming)) - .filter(tdai -> tdai.getName().equals(daiName)) - .findFirst(); + public static Stream getLDevices(TIED tied){ + return tied.getAccessPoint().stream() + .filter(TAccessPoint::isSetServer) + .flatMap(tAccessPoint -> tAccessPoint.getServer().getLDevice().stream()); } - public static AbstractDAIAdapter findDai(AbstractLNAdapter lnAdapter, String dataTypeRef) { - String[] names = dataTypeRef.split("\\."); - if (names.length < 2) { - Assertions.fail("dataTypeRef must at least contain a DO and a DA name, but got: " + dataTypeRef); - } - - IDataParentAdapter parentAdapter = findDoiOrSdi(lnAdapter, String.join(".", Arrays.asList(names).subList(0, names.length - 1))); - return parentAdapter.getDataAdapterByName(names[names.length - 1]); +public static IDataParentAdapter findDoiOrSdi(AbstractLNAdapter lnAdapter, String dataTypeRef) { + if (dataTypeRef.length() < 1) { + Assertions.fail("dataTypeRef must at least contain a DO, but got: " + dataTypeRef); } - - public static String getValue(AbstractDAIAdapter daiAdapter) { - return getValue(daiAdapter.getCurrentElem()); + String[] names = dataTypeRef.split("\\."); + IDataParentAdapter parentAdapter = lnAdapter.getDOIAdapterByName(names[0]); + for (int i = 1; i < names.length; i++) { + parentAdapter = parentAdapter.getStructuredDataAdapterByName(names[i]); } + return parentAdapter; +} - public static String getValue(TDAI tdai) { - if (!tdai.isSetVal()) { - Assertions.fail("No value found for DAI " + tdai.getName()); - } else if (tdai.getVal().size() > 1) { - Assertions.fail("Expecting a single value for for DAI " + tdai.getName()); - } - return tdai.getVal().get(0).getValue(); - } +public static Optional findDai(SCL scl, String iedName, String ldInst, String doiName, String daiName) { + return findDai( scl, iedName, ldInst, "LLN0", "", "", doiName, daiName); +} +public static Optional findDai(SCL scl, String iedName, String ldInst, String lnClass, String lnInst, String lnPrefix, String doiName, String daiName) { + return findAnyLn(scl, iedName, ldInst, lnClass, lnInst, lnPrefix) + .getDOI().stream() + .filter(tdoi -> tdoi.getName().equals(doiName)) + .flatMap(tdoi -> tdoi.getSDIOrDAI().stream().map(tUnNaming -> (TDAI) tUnNaming)) + .filter(tdai -> tdai.getName().equals(daiName)) + .findFirst(); +} - public static LDeviceAdapter findLDeviceByLdName(SCL scl, String ldName) { - return new SclRootAdapter(scl).streamIEDAdapters() - .flatMap(IEDAdapter::streamLDeviceAdapters) - .filter(lDeviceAdapter -> ldName.equals(lDeviceAdapter.getLdName())) - .findFirst() - .orElseThrow(() -> new AssertionFailedError("LDevice with ldName=%s not found in SCD".formatted(ldName))); +public static AbstractDAIAdapter findDai(AbstractLNAdapter lnAdapter, String dataTypeRef) { + String[] names = dataTypeRef.split("\\."); + if (names.length < 2) { + Assertions.fail("dataTypeRef must at least contain a DO and a DA name, but got: " + dataTypeRef); } - public static DataSetAdapter findDataSet(SCL scl, String iedName, String ldInst, String dataSetName) { - LN0Adapter ln0 = findLn0(scl, iedName, ldInst); - return ln0.findDataSetByName(dataSetName) - .orElseThrow(() -> new AssertionFailedError(String.format("DataSet.name=%s not found in IED.name=%s,LDevice.inst=%s,LN0", - dataSetName, iedName, ldInst))); - } + IDataParentAdapter parentAdapter = findDoiOrSdi(lnAdapter, String.join(".", Arrays.asList(names).subList(0, names.length - 1))); + return parentAdapter.getDataAdapterByName(names[names.length - 1]); +} - public static T findControlBlock(SCL scl, String iedName, String ldInst, String cbName, Class controlBlockClass) { - LN0Adapter ln0 = findLn0(scl, iedName, ldInst); - return ln0.getTControlsByType(controlBlockClass).stream() - .filter(t -> cbName.equals(t.getName())) - .findFirst() - .orElseThrow(() -> new AssertionFailedError(String.format("%s name=%s not found in IED.name=%s,LDevice.inst=%s,LN0", - controlBlockClass.getSimpleName(), cbName, iedName, ldInst))); - } +public static String getValue(AbstractDAIAdapter daiAdapter) { + return getValue(daiAdapter.getCurrentElem()); +} - public static void assertControlBlockExists(SCL scd, String iedName, String ldInst, String cbName, - String datSet, String id, ControlBlockEnum controlBlockEnum) { - TControl controlBlock = findControlBlock(scd, iedName, ldInst, cbName, controlBlockEnum.getControlBlockClass()); - assertThat(controlBlock.getDatSet()).isEqualTo(datSet); - assertThat(getControlBlockId(controlBlock)).isEqualTo(id); +public static String getValue(TDAI tdai) { + if (!tdai.isSetVal()) { + Assertions.fail("No value found for DAI " + tdai.getName()); + } else if (tdai.getVal().size() > 1) { + Assertions.fail("Expecting a single value for for DAI " + tdai.getName()); } + return tdai.getVal().get(0).getValue(); +} - private static String getControlBlockId(TControl tControl) { - if (tControl instanceof TGSEControl tgseControl) { - return tgseControl.getAppID(); - } - if (tControl instanceof TSampledValueControl tSampledValueControl) { - return tSampledValueControl.getSmvID(); - } - if (tControl instanceof TReportControl tReportControl) { - return tReportControl.getRptID(); - } - throw new AssertionFailedError("Cannot get Id for ControlBlock of type " + tControl.getClass().getSimpleName()); - } - public static Stream streamAllDataSets(SCL scl) { - return streamAllLn0Adapters(scl) - .map(ln0Adapter -> ln0Adapter.getCurrentElem().getDataSet()) - .flatMap(List::stream); - } +public static LDeviceAdapter findLDeviceByLdName(SCL scl, String ldName) { + return new SclRootAdapter(scl).streamIEDAdapters() + .flatMap(IEDAdapter::streamLDeviceAdapters) + .filter(lDeviceAdapter -> ldName.equals(lDeviceAdapter.getLdName())) + .findFirst() + .orElseThrow(() -> new AssertionFailedError("LDevice with ldName=%s not found in SCD".formatted(ldName))); +} - public static Stream streamAllLn0Adapters(SCL scl) { - return new SclRootAdapter(scl) - .streamIEDAdapters() - .flatMap(IEDAdapter::streamLDeviceAdapters) - .filter(LDeviceAdapter::hasLN0) - .map(LDeviceAdapter::getLN0Adapter); - } +public static DataSetAdapter findDataSet(SCL scl, String iedName, String ldInst, String dataSetName) { + LN0Adapter ln0 = findLn0(scl, iedName, ldInst); + return ln0.findDataSetByName(dataSetName) + .orElseThrow(() -> new AssertionFailedError(String.format("DataSet.name=%s not found in IED.name=%s,LDevice.inst=%s,LN0", + dataSetName, iedName, ldInst))); +} - public static Stream streamAllExtRef(SCL scl) { - return streamAllLn0Adapters(scl) - .filter(AbstractLNAdapter::hasInputs) - .map(LN0Adapter::getInputsAdapter) - .map(InputsAdapter::getCurrentElem) - .map(TInputs::getExtRef) - .flatMap(List::stream); - } +public static T findControlBlock(SCL scl, String iedName, String ldInst, String cbName, Class controlBlockClass) { + LN0Adapter ln0 = findLn0(scl, iedName, ldInst); + return ln0.getTControlsByType(controlBlockClass).stream() + .filter(t -> cbName.equals(t.getName())) + .findFirst() + .orElseThrow(() -> new AssertionFailedError(String.format("%s name=%s not found in IED.name=%s,LDevice.inst=%s,LN0", + controlBlockClass.getSimpleName(), cbName, iedName, ldInst))); +} - public static String getDaiValue(AbstractLNAdapter ln, String doiName, String daiName) { - return ln.getDOIAdapterByName(doiName).getDataAdapterByName(daiName).getCurrentElem().getVal().get(0).getValue(); - } +public static void assertControlBlockExists(SCL scd, String iedName, String ldInst, String cbName, + String datSet, String id, ControlBlockEnum controlBlockEnum) { + TControl controlBlock = findControlBlock(scd, iedName, ldInst, cbName, controlBlockEnum.getControlBlockClass()); + assertThat(controlBlock.getDatSet()).isEqualTo(datSet); + assertThat(getControlBlockId(controlBlock)).isEqualTo(id); +} - public static Stream streamAllConnectedApGseP(SCL scd, String pType) { - return scd.getCommunication().getSubNetwork().stream() - .map(TSubNetwork::getConnectedAP) - .flatMap(List::stream) - .map(TConnectedAP::getGSE) - .flatMap(List::stream) - .map(TControlBlock::getAddress) - .map(TAddress::getP) - .flatMap(List::stream) - .filter(tp -> pType.equals(tp.getType())) - .map(TP::getValue); +private static String getControlBlockId(TControl tControl) { + if (tControl instanceof TGSEControl tgseControl) { + return tgseControl.getAppID(); } - - public static SclRootAdapter createSclRootAdapterWithIed(String iedName) { - SCL scl = new SCL(); - scl.setHeader(new THeader()); - TIED ied = new TIED(); - ied.setName(iedName); - scl.getIED().add(ied); - return new SclRootAdapter(scl); + if (tControl instanceof TSampledValueControl tSampledValueControl) { + return tSampledValueControl.getSmvID(); } - - public static SclRootAdapter createSclRootWithConnectedAp(String iedName, String apName) { - SclRootAdapter sclRootAdapter = createSclRootAdapterWithIed(iedName); - SCL scl = sclRootAdapter.getCurrentElem(); - scl.setCommunication(new TCommunication()); - TSubNetwork subNetwork = new TSubNetwork(); - scl.getCommunication().getSubNetwork().add(subNetwork); - subNetwork.getConnectedAP().add(newConnectedAp(iedName, apName)); - return sclRootAdapter; + if (tControl instanceof TReportControl tReportControl) { + return tReportControl.getRptID(); } + throw new AssertionFailedError("Cannot get Id for ControlBlock of type " + tControl.getClass().getSimpleName()); +} - public static TExtRef createExtRefExample(String cbName, TServiceType tServiceType) { - TExtRef tExtRef = new TExtRef(); - tExtRef.setIedName("IED_NAME_2"); - tExtRef.setServiceType(tServiceType); - tExtRef.setSrcLDInst("Inst_2"); - tExtRef.setSrcLNInst("LN"); - tExtRef.setSrcPrefix("Prefix"); - tExtRef.setSrcCBName(cbName); - return tExtRef; - } +public static Stream streamAllDataSets(SCL scl) { + return streamAllLn0Adapters(scl) + .map(ln0Adapter -> ln0Adapter.getCurrentElem().getDataSet()) + .flatMap(List::stream); +} - public static SclRootAdapter createIedsInScl(String lnClass, String doName) { - // DataTypeTemplate - TDO tdo = new TDO(); - tdo.setName(doName); - tdo.setType("REF"); - TLNodeType tlNodeType = new TLNodeType(); - tlNodeType.setId("T1"); - tlNodeType.getLnClass().add(lnClass); - tlNodeType.getDO().add(tdo); - - TDA tda = new TDA(); - tda.setName("setSrcRef"); - tda.setValImport(true); - tda.setBType(TPredefinedBasicTypeEnum.OBJ_REF); - tda.setFc(TFCEnum.SP); - - TDOType tdoType = new TDOType(); - tdoType.setId("REF"); - tdoType.getSDOOrDA().add(tda); - - TDataTypeTemplates tDataTypeTemplates = new TDataTypeTemplates(); - tDataTypeTemplates.getLNodeType().add(tlNodeType); - tDataTypeTemplates.getDOType().add(tdoType); - - - //ied Client - TDOI tdoi = new TDOI(); - tdoi.setName(doName); - TLDevice tlDevice = new TLDevice(); - tlDevice.setInst("LD_ADD"); - TInputs tInputs = new TInputs(); - LN0 ln0 = new LN0(); - ln0.setInputs(tInputs); - tlDevice.setLN0(ln0); - - TLDevice tlDevice1 = new TLDevice(); - tlDevice1.setLN0(new LN0()); - tlDevice1.setInst(LD_SUIED); - TLN tln1 = new TLN(); - tln1.getLnClass().add(lnClass); - tln1.setLnType("T1"); - tln1.getDOI().add(tdoi); - tlDevice1.getLN().add(tln1); - TServer tServer1 = new TServer(); - tServer1.getLDevice().add(tlDevice1); - tServer1.getLDevice().add(tlDevice); - TAccessPoint tAccessPoint1 = new TAccessPoint(); - tAccessPoint1.setName("AP_NAME"); - tAccessPoint1.setServer(tServer1); - TIED tied1 = new TIED(); - tied1.setName(IED_NAME_1); - tied1.getAccessPoint().add(tAccessPoint1); - - //ied Source - TLDevice tlDevice2 = new TLDevice(); - tlDevice2.setInst("Inst_2"); - tlDevice2.setLdName("LD_Name"); - tlDevice2.setLN0(new LN0()); - TServer tServer2 = new TServer(); - tServer2.getLDevice().add(tlDevice2); - TAccessPoint tAccessPoint2 = new TAccessPoint(); - tAccessPoint2.setName("AP_NAME"); - tAccessPoint2.setServer(tServer2); - TIED tied2 = new TIED(); - tied2.setName(IED_NAME_2); - tied2.getAccessPoint().add(tAccessPoint2); - //SCL file - SCL scd = new SCL(); - scd.getIED().add(tied1); - scd.getIED().add(tied2); - THeader tHeader = new THeader(); - tHeader.setRevision("1"); - scd.setHeader(tHeader); - scd.setDataTypeTemplates(tDataTypeTemplates); - - return new SclRootAdapter(scd); - } +public static Stream streamAllLn0Adapters(SCL scl) { + return new SclRootAdapter(scl) + .streamIEDAdapters() + .flatMap(IEDAdapter::streamLDeviceAdapters) + .filter(LDeviceAdapter::hasLN0) + .map(LDeviceAdapter::getLN0Adapter); +} - public static List getDaiValues(LDeviceAdapter lDeviceAdapter, String lnClass, String doName, String daName) { - return getDAIAdapters(lDeviceAdapter, lnClass, doName, daName) - .map(daiAdapter -> daiAdapter.getCurrentElem().getVal()) - .flatMap(List::stream) - .toList(); - } +public static Stream streamAllExtRef(SCL scl) { + return streamAllLn0Adapters(scl) + .filter(AbstractLNAdapter::hasInputs) + .map(LN0Adapter::getInputsAdapter) + .map(InputsAdapter::getCurrentElem) + .map(TInputs::getExtRef) + .flatMap(List::stream); +} - public static Stream getDAIAdapters(LDeviceAdapter lDeviceAdapter, String lnClass, String doName, String daName) { - return lDeviceAdapter.getLNAdapters().stream() - .filter(lnAdapter -> lnClassEquals(lnAdapter.getCurrentElem().getLnClass(), lnClass)) - .map(lnAdapter -> lnAdapter.getDOIAdapterByName(doName)) - .map(doiAdapter -> (DOIAdapter.DAIAdapter) doiAdapter.getDataAdapterByName(daName)); - } +public static String getDaiValue(AbstractLNAdapter ln, String doiName, String daiName) { + return ln.getDOIAdapterByName(doiName).getDataAdapterByName(daiName).getCurrentElem().getVal().get(0).getValue(); +} + +public static Stream streamAllConnectedApGseP(SCL scd, String pType) { + return scd.getCommunication().getSubNetwork().stream() + .map(TSubNetwork::getConnectedAP) + .flatMap(List::stream) + .map(TConnectedAP::getGSE) + .flatMap(List::stream) + .map(TControlBlock::getAddress) + .map(TAddress::getP) + .flatMap(List::stream) + .filter(tp -> pType.equals(tp.getType())) + .map(TP::getValue); +} + +public static SclRootAdapter createSclRootAdapterWithIed(String iedName) { + SCL scl = new SCL(); + scl.setHeader(new THeader()); + TIED ied = new TIED(); + ied.setName(iedName); + scl.getIED().add(ied); + return new SclRootAdapter(scl); +} + +public static SclRootAdapter createSclRootWithConnectedAp(String iedName, String apName) { + SclRootAdapter sclRootAdapter = createSclRootAdapterWithIed(iedName); + SCL scl = sclRootAdapter.getCurrentElem(); + scl.setCommunication(new TCommunication()); + TSubNetwork subNetwork = new TSubNetwork(); + scl.getCommunication().getSubNetwork().add(subNetwork); + subNetwork.getConnectedAP().add(newConnectedAp(iedName, apName)); + return sclRootAdapter; +} + +public static TExtRef createExtRefExample(String cbName, TServiceType tServiceType) { + TExtRef tExtRef = new TExtRef(); + tExtRef.setIedName("IED_NAME_2"); + tExtRef.setServiceType(tServiceType); + tExtRef.setSrcLDInst("Inst_2"); + tExtRef.setSrcLNInst("LN"); + tExtRef.setSrcPrefix("Prefix"); + tExtRef.setSrcCBName(cbName); + return tExtRef; +} + +public static SclRootAdapter createIedsInScl(String lnClass, String doName) { + // DataTypeTemplate + TDO tdo = new TDO(); + tdo.setName(doName); + tdo.setType("REF"); + TLNodeType tlNodeType = new TLNodeType(); + tlNodeType.setId("T1"); + tlNodeType.getLnClass().add(lnClass); + tlNodeType.getDO().add(tdo); + + TDA tda = new TDA(); + tda.setName("setSrcRef"); + tda.setValImport(true); + tda.setBType(TPredefinedBasicTypeEnum.OBJ_REF); + tda.setFc(TFCEnum.SP); + + TDOType tdoType = new TDOType(); + tdoType.setId("REF"); + tdoType.getSDOOrDA().add(tda); + + TDataTypeTemplates tDataTypeTemplates = new TDataTypeTemplates(); + tDataTypeTemplates.getLNodeType().add(tlNodeType); + tDataTypeTemplates.getDOType().add(tdoType); + + + //ied Client + TDOI tdoi = new TDOI(); + tdoi.setName(doName); + TLDevice tlDevice = new TLDevice(); + tlDevice.setInst("LD_ADD"); + TInputs tInputs = new TInputs(); + LN0 ln0 = new LN0(); + ln0.setInputs(tInputs); + tlDevice.setLN0(ln0); + + TLDevice tlDevice1 = new TLDevice(); + tlDevice1.setLN0(new LN0()); + tlDevice1.setInst(LD_SUIED); + TLN tln1 = new TLN(); + tln1.getLnClass().add(lnClass); + tln1.setLnType("T1"); + tln1.getDOI().add(tdoi); + tlDevice1.getLN().add(tln1); + TServer tServer1 = new TServer(); + tServer1.getLDevice().add(tlDevice1); + tServer1.getLDevice().add(tlDevice); + TAccessPoint tAccessPoint1 = new TAccessPoint(); + tAccessPoint1.setName("AP_NAME"); + tAccessPoint1.setServer(tServer1); + TIED tied1 = new TIED(); + tied1.setName(IED_NAME_1); + tied1.getAccessPoint().add(tAccessPoint1); + + //ied Source + TLDevice tlDevice2 = new TLDevice(); + tlDevice2.setInst("Inst_2"); + tlDevice2.setLdName("LD_Name"); + tlDevice2.setLN0(new LN0()); + TServer tServer2 = new TServer(); + tServer2.getLDevice().add(tlDevice2); + TAccessPoint tAccessPoint2 = new TAccessPoint(); + tAccessPoint2.setName("AP_NAME"); + tAccessPoint2.setServer(tServer2); + TIED tied2 = new TIED(); + tied2.setName(IED_NAME_2); + tied2.getAccessPoint().add(tAccessPoint2); + //SCL file + SCL scd = new SCL(); + scd.getIED().add(tied1); + scd.getIED().add(tied2); + THeader tHeader = new THeader(); + tHeader.setRevision("1"); + scd.setHeader(tHeader); + scd.setDataTypeTemplates(tDataTypeTemplates); + + return new SclRootAdapter(scd); +} + +public static List getDaiValues(LDeviceAdapter lDeviceAdapter, String lnClass, String doName, String daName) { + return getDAIAdapters(lDeviceAdapter, lnClass, doName, daName) + .map(daiAdapter -> daiAdapter.getCurrentElem().getVal()) + .flatMap(List::stream) + .toList(); +} + +public static Stream getDAIAdapters(LDeviceAdapter lDeviceAdapter, String lnClass, String doName, String daName) { + return lDeviceAdapter.getLNAdapters().stream() + .filter(lnAdapter -> lnClassEquals(lnAdapter.getCurrentElem().getLnClass(), lnClass)) + .map(lnAdapter -> lnAdapter.getDOIAdapterByName(doName)) + .map(doiAdapter -> (DOIAdapter.DAIAdapter) doiAdapter.getDataAdapterByName(daName)); +} } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/PrivateUtilsTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/PrivateUtilsTest.java index 5a9b20954..6b8ab3469 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/PrivateUtilsTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/PrivateUtilsTest.java @@ -630,4 +630,18 @@ void createPrivate_compas_Topo_should_succeed(){ .map(JAXBElement::getValue) .containsExactly(tCompasTopo1, tCompasTopo2); } + + @Test + void extractStringPrivate_should_succeed(){ + // Given + TIED tied = new TIED(); + TPrivate tPrivate = new TPrivate(); + tPrivate.setType("MyCustomType"); + tPrivate.getContent().add("hello World"); + tied.getPrivate().add(tPrivate); + // When + Optional result = PrivateUtils.extractStringPrivate(tied, "MyCustomType"); + // Then + assertThat(result).hasValue("hello World"); + } } diff --git a/sct-commons/src/test/resources/scl-lnodestatus/lnodestatus.scd b/sct-commons/src/test/resources/scl-lnodestatus/lnodestatus.scd new file mode 100644 index 000000000..120833d95 --- /dev/null +++ b/sct-commons/src/test/resources/scl-lnodestatus/lnodestatus.scd @@ -0,0 +1,125 @@ + + + + + + + + SCD + +
+ + + 90 + + + + on + + + on + + + off + + + off + + + + + on + + + on + + + off + + + off + + + + + + + + + + + + on + + + + + + on;off + + + + + + on + + + + + + on;off + + + + + + off + + + + + + + + on + + + + + + + + on;off + + + + + + + + off + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scl-lnodestatus/lnodestatus_without_mod_stval.scd b/sct-commons/src/test/resources/scl-lnodestatus/lnodestatus_without_mod_stval.scd new file mode 100644 index 000000000..da7204fdc --- /dev/null +++ b/sct-commons/src/test/resources/scl-lnodestatus/lnodestatus_without_mod_stval.scd @@ -0,0 +1,101 @@ + + + + + + + + SCD + +
+ + + 90 + + + + on + + + on + + + off + + + off + + + + + on + + + on + + + off + + + off + + + + + + + + + + + + on + + + on;off + + + on + + + on;off + + + off + + + + + on + + + + + on;off + + + + + off + + + + + + + + + + + + + + + + + on + off + test + + +