From 9d607e78bd0e109b6e2bc0931bfffae20566598d Mon Sep 17 00:00:00 2001 From: Aliou DIAITE Date: Fri, 1 Dec 2023 11:24:50 +0100 Subject: [PATCH 1/9] feat(#337): set security policy Signed-off-by: Aliou DIAITE --- SECURITY.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..b1d39cddd --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ + + +# Security Policy + +## Reporting a Vulnerability + +Please go to [Security Advisories](https://github.com/com-pas/compas-sct/security/advisories) to privately report a +security vulnerability, +our contributors will try to respond within a week of your report with a rough plan for a fix and new tests. \ No newline at end of file From db95c4535a6d83575003c2b03b3c29bafc6a84b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 07:32:20 +0000 Subject: [PATCH 2/9] build(deps): bump actions/setup-java from 3 to 4 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/automate_javadoc.yml | 2 +- .github/workflows/build-project.yml | 2 +- .github/workflows/release-project.yml | 2 +- .github/workflows/sonarcloud-analysis.yml | 2 +- .github/workflows/sonarcloud-build.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/automate_javadoc.yml b/.github/workflows/automate_javadoc.yml index 1efba5dd7..a9756bdb0 100644 --- a/.github/workflows/automate_javadoc.yml +++ b/.github/workflows/automate_javadoc.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '17' diff --git a/.github/workflows/build-project.yml b/.github/workflows/build-project.yml index dac72e702..7ca1b9905 100644 --- a/.github/workflows/build-project.yml +++ b/.github/workflows/build-project.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '17' diff --git a/.github/workflows/release-project.yml b/.github/workflows/release-project.yml index e9375a939..20c9b7eee 100644 --- a/.github/workflows/release-project.yml +++ b/.github/workflows/release-project.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '17' diff --git a/.github/workflows/sonarcloud-analysis.yml b/.github/workflows/sonarcloud-analysis.yml index 1c3a34a3e..a2a67067d 100644 --- a/.github/workflows/sonarcloud-analysis.yml +++ b/.github/workflows/sonarcloud-analysis.yml @@ -62,7 +62,7 @@ jobs: restore-keys: ${{ runner.os }}-sonar - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '17' diff --git a/.github/workflows/sonarcloud-build.yml b/.github/workflows/sonarcloud-build.yml index b6162ab4d..efabf9e5e 100644 --- a/.github/workflows/sonarcloud-build.yml +++ b/.github/workflows/sonarcloud-build.yml @@ -27,7 +27,7 @@ jobs: restore-keys: ${{ runner.os }}-sonar - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '17' From 5804ec20e35b4187ad957493c7f8ab282c5be25b Mon Sep 17 00:00:00 2001 From: Aliou DIAITE Date: Fri, 1 Dec 2023 17:20:02 +0100 Subject: [PATCH 3/9] feat(#359): use a POJO for allowed FCDA list in CB Report for HMI creation Signed-off-by: Aliou DIAITE --- sct-commons/pom.xml | 19 +++++ .../compas/sct/commons/HmiService.java | 8 +- .../compas/sct/commons/api/HmiEditor.java | 8 +- .../commons/scl/ldevice/LDeviceAdapter.java | 31 +++++-- .../sct/commons/util/FcdaCsvHelper.java | 51 +----------- .../xsd/CB_REPORT_SUPERVISION_Config_file.xsd | 72 ++++++++++++++++ .../compas/sct/commons/HmiServiceTest.java | 83 ++++++++++++++----- .../sct/commons/util/FcdaCsvHelperTest.java | 28 +------ 8 files changed, 188 insertions(+), 112 deletions(-) create mode 100644 sct-commons/src/main/resources/xsd/CB_REPORT_SUPERVISION_Config_file.xsd diff --git a/sct-commons/pom.xml b/sct-commons/pom.xml index 3b9236fa4..c807d1237 100644 --- a/sct-commons/pom.xml +++ b/sct-commons/pom.xml @@ -195,6 +195,25 @@ false + + cb_po + + xjc + + + + + ${project.basedir}/src/main/resources/xsd/CB_REPORT_SUPERVISION_Config_file.xsd + + + + ${project.basedir}/src/main/resources/binding_configuration.xjb + + org.lfenergy.compas.sct.commons.model.cb_po + true + false + + diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/HmiService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/HmiService.java index ef9477aa8..8969e7ad3 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/HmiService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/HmiService.java @@ -5,21 +5,19 @@ package org.lfenergy.compas.sct.commons; import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.scl2007b4.model.TFCDA; import org.lfenergy.compas.sct.commons.api.HmiEditor; +import org.lfenergy.compas.sct.commons.model.cb_po.PO; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; -import java.util.List; - public class HmiService implements HmiEditor { @Override - public void createAllHmiReportControlBlocks(SCL scd, List fcdas) { + public void createAllHmiReportControlBlocks(SCL scd, PO po) { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); sclRootAdapter.streamIEDAdapters() .flatMap(IEDAdapter::streamLDeviceAdapters) - .forEach(lDeviceAdapter -> lDeviceAdapter.createHmiReportControlBlocks(fcdas)); + .forEach(lDeviceAdapter -> lDeviceAdapter.createHmiReportControlBlocks(po)); } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/HmiEditor.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/HmiEditor.java index 548cac764..d90a55626 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/HmiEditor.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/HmiEditor.java @@ -5,10 +5,8 @@ package org.lfenergy.compas.sct.commons.api; import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.scl2007b4.model.TFCDA; import org.lfenergy.compas.scl2007b4.model.TReportControl; - -import java.util.List; +import org.lfenergy.compas.sct.commons.model.cb_po.PO; /** * Service class that will be used to manage elements related to the HMI {@link TReportControl Report Control Blocks}. @@ -20,7 +18,7 @@ public interface HmiEditor { /** * Create the DataSet and ReportControl Blocks for the HMI with the given FCDAs. * - * @param fcdas List of FCDA for which we must create the DataSet and ReportControl Blocks + * @param po object containing list of FCDA for which we must create the DataSet and ReportControl Blocks */ - void createAllHmiReportControlBlocks(SCL scd, List fcdas); + void createAllHmiReportControlBlocks(SCL scd, PO po); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ldevice/LDeviceAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ldevice/LDeviceAdapter.java index 06d037c1f..15d8ae935 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ldevice/LDeviceAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ldevice/LDeviceAdapter.java @@ -10,6 +10,8 @@ import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.dto.*; import org.lfenergy.compas.sct.commons.exception.ScdException; +import org.lfenergy.compas.sct.commons.model.cb_po.PO; +import org.lfenergy.compas.sct.commons.model.cb_po.TFCDAFilter; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.scl.dtt.DataTypeTemplateAdapter; import org.lfenergy.compas.sct.commons.scl.ied.ControlBlockAdapter; @@ -81,20 +83,20 @@ public LDeviceAdapter(IEDAdapter parentAdapter, TLDevice currentElem) { * Create DataSet and ReportControl Blocks for the HMI with the given FCDAs. * DataSet and ReportControl are created in LN0, even if FCDA refers to another LN. * - * @param fcdas List of FCDA for which we must create the DataSet and ReportControl + * @param po object containing list of FCDA for which we must create the DataSet and ReportControl */ - public void createHmiReportControlBlocks(List fcdas) { + public void createHmiReportControlBlocks(PO po) { LN0Adapter ln0 = getLN0Adapter(); if (!ln0.getDaiModStValValue().map(ActiveStatus::fromValue).map(ActiveStatus.ON::equals).orElse(false)) return; - fcdas.stream() - .filter(fcda -> getInst().equals(fcda.getLdInst()) && fcda.isSetLnClass()) - .forEach(fcda -> (fcda.getLnClass().get(0).equals(TLLN0Enum.LLN_0.value()) ? + po.getFCDAs().getFCDA().stream() + .filter(tfcdaFilter -> getInst().equals(tfcdaFilter.getLdInst()) && tfcdaFilter.isSetLnClass()) + .forEach(tfcdaFilter -> (tfcdaFilter.getLnClass().equals(TLLN0Enum.LLN_0.value()) ? Optional.of(ln0) // ln0 Mod stVal "ON" has already been checked, no need to check it again : - findLnAdapter(fcda.getLnClass().get(0), fcda.getLnInst(), fcda.getPrefix()).filter(lnAdapter -> lnAdapter.getDaiModStValValue().map(ActiveStatus::fromValue).map(ActiveStatus.ON::equals).orElse(true))) - .map(sourceLn -> sourceLn.getDAI(new DataAttributeRef(fcda), false)) - .filter(das -> das.stream().anyMatch(da -> fcda.getFc() == da.getFc())) // getDAI does not filter on DA. - .ifPresent(dataAttributeRefs -> createHmiReportCB(ln0, fcda))); + findLnAdapter(tfcdaFilter.getLnClass(), tfcdaFilter.getLnInst(), tfcdaFilter.getPrefix()).filter(lnAdapter -> lnAdapter.getDaiModStValValue().map(ActiveStatus::fromValue).map(ActiveStatus.ON::equals).orElse(true))) + .map(sourceLn -> sourceLn.getDAI(new DataAttributeRef(toFCDA(tfcdaFilter)), false)) + .filter(das -> das.stream().anyMatch(da -> TFCEnum.fromValue(tfcdaFilter.getFc().value()) == da.getFc())) // getDAI does not filter on DA. + .ifPresent(dataAttributeRefs -> createHmiReportCB(ln0, toFCDA(tfcdaFilter)))); } private void createHmiReportCB(LN0Adapter ln0, TFCDA fcda) { @@ -514,4 +516,15 @@ public List getExtRefBayReferenceForActifLDEPF(fi return extRefBayReferenceList; } + private TFCDA toFCDA(TFCDAFilter tfcdaFilter) { + TFCDA tfcda = new TFCDA(); + tfcda.setLdInst(tfcdaFilter.getLdInst()); + tfcda.getLnClass().add(tfcdaFilter.getLnClass()); + tfcda.setPrefix(tfcdaFilter.getPrefix()); + tfcda.setLnInst(tfcdaFilter.getLnInst()); + tfcda.setDoName(tfcdaFilter.getDoName()); + tfcda.setFc(TFCEnum.fromValue(tfcdaFilter.getFc().value())); + return tfcda; + } + } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/FcdaCsvHelper.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/FcdaCsvHelper.java index 659e11fb4..d4a20ad51 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/FcdaCsvHelper.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/FcdaCsvHelper.java @@ -4,33 +4,25 @@ package org.lfenergy.compas.sct.commons.util; -import com.opencsv.bean.CsvBindByPosition; import lombok.Getter; import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.scl2007b4.model.TFCDA; -import org.lfenergy.compas.scl2007b4.model.TFCEnum; import org.lfenergy.compas.sct.commons.dto.FcdaForDataSetsCreation; import java.io.Reader; import java.util.HashSet; -import java.util.List; import java.util.Set; /** * This class is a helper method to load FCDA from a CSV files for use with - * {@link org.lfenergy.compas.sct.commons.HmiService#createAllHmiReportControlBlocks(SCL, List)} - * {@link org.lfenergy.compas.sct.commons.ExtRefService#createDataSetAndControlBlocks(SCL, Set)} - * {@link org.lfenergy.compas.sct.commons.ExtRefService#createDataSetAndControlBlocks(SCL, String, Set)} - * {@link org.lfenergy.compas.sct.commons.ExtRefService#createDataSetAndControlBlocks(SCL, String, String, Set)} + * {@link org.lfenergy.compas.sct.commons.ControlBlockService#createDataSetAndControlBlocks(SCL, Set)} + * {@link org.lfenergy.compas.sct.commons.ControlBlockService#createDataSetAndControlBlocks(SCL, String, Set)} + * {@link org.lfenergy.compas.sct.commons.ControlBlockService#createDataSetAndControlBlocks(SCL, String, String, Set)} * Use the getter to access the list of parsed FCDA. * * @see CsvUtils */ public class FcdaCsvHelper { - @Getter - private final List fcdaForHmiReportControls; - @Getter private final Set fcdaForDataSets; @@ -39,46 +31,11 @@ public class FcdaCsvHelper { * Provide the CSV files as a Reader. For example, you can create a reader like this : * new InputStreamReader(getClass().getClassLoader().getResourceAsStream(fileName), StandardCharsets.UTF_8); * - * @param csvSourceForHmiReportControl a reader that provides the FCDA datas for HMI ReportControl Blocks as CSV * @param csvSourceForDataSetAndControlBlocks a reader that provides the FCDA datas for DataSets and Control Blocks creation as CSV */ - public FcdaCsvHelper(Reader csvSourceForHmiReportControl, Reader csvSourceForDataSetAndControlBlocks) { - fcdaForHmiReportControls = CsvUtils.parseRows(csvSourceForHmiReportControl, FcdaForHmiReportControl.class).stream() - .map(fcdaForHmiReportControl -> - SclConstructorHelper.newFcda(fcdaForHmiReportControl.ldInst, fcdaForHmiReportControl.lnClass, fcdaForHmiReportControl.lnInst, fcdaForHmiReportControl.prefix, fcdaForHmiReportControl.doName, null, fcdaForHmiReportControl.fc) - ) - .toList(); + public FcdaCsvHelper(Reader csvSourceForDataSetAndControlBlocks) { fcdaForDataSets = new HashSet<>(CsvUtils.parseRows(csvSourceForDataSetAndControlBlocks, FcdaForDataSetsCreation.class)); } - public static class FcdaForHmiReportControl { - @CsvBindByPosition(position = 0) - private String ldInst; - @CsvBindByPosition(position = 1) - private String prefix; - @CsvBindByPosition(position = 2) - private String lnClass; - @CsvBindByPosition(position = 3) - private String lnInst; - @CsvBindByPosition(position = 4) - private String doName; - @CsvBindByPosition(position = 5) - private TFCEnum fc; - } - - /* @NoArgsConstructor - @AllArgsConstructor - @Getter - @EqualsAndHashCode - public static class FcdaForDataSets { - @CsvBindByPosition(position = 0) - private String lnClass; - @CsvBindByPosition(position = 1) - private String doName; - @CsvBindByPosition(position = 2) - private String daName; - @CsvBindByPosition(position = 3) - private String fc; - } */ } diff --git a/sct-commons/src/main/resources/xsd/CB_REPORT_SUPERVISION_Config_file.xsd b/sct-commons/src/main/resources/xsd/CB_REPORT_SUPERVISION_Config_file.xsd new file mode 100644 index 000000000..8f4264086 --- /dev/null +++ b/sct-commons/src/main/resources/xsd/CB_REPORT_SUPERVISION_Config_file.xsd @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/HmiServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/HmiServiceTest.java index b468c1560..1a041bcb4 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/HmiServiceTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/HmiServiceTest.java @@ -4,23 +4,25 @@ package org.lfenergy.compas.sct.commons; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.lfenergy.compas.scl2007b4.model.*; +import org.lfenergy.compas.sct.commons.model.cb_po.FCDAs; +import org.lfenergy.compas.sct.commons.model.cb_po.PO; +import org.lfenergy.compas.sct.commons.model.cb_po.TFCDAFilter; +import org.lfenergy.compas.sct.commons.model.cb_po.Tfc; import org.lfenergy.compas.sct.commons.scl.ied.DataSetAdapter; import org.lfenergy.compas.sct.commons.scl.ln.LN0Adapter; import org.lfenergy.compas.sct.commons.scl.ln.LNAdapter; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; -import org.lfenergy.compas.sct.commons.util.CommonConstants; import org.lfenergy.compas.sct.commons.util.ActiveStatus; +import org.lfenergy.compas.sct.commons.util.CommonConstants; import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.*; -import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newFcda; @ExtendWith(MockitoExtension.class) class HmiServiceTest { @@ -28,18 +30,27 @@ class HmiServiceTest { @InjectMocks HmiService hmiService; + private final PO po = new PO(); + + @BeforeEach + void setUp() { + FCDAs fcdAs = new FCDAs(); + po.setFCDAs(fcdAs); + } + @Test void createAllIhmReportControlBlocks_with_fc_ST_should_create_dataset_and_controlblock() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-hmi-create-report-cb/scd_create_dataset_and_controlblocks_for_hmi.xml"); - TFCDA fcda = newFcda("LdInst11", "ANCR", "1", null, "DoName1", null, TFCEnum.ST); + TFCDAFilter tfcdaFilter = createFCDAFilter("LdInst11", "ANCR", "1", null, "DoName1", Tfc.ST); + po.getFCDAs().getFCDA().add(tfcdaFilter); // When - hmiService.createAllHmiReportControlBlocks(scd, List.of(fcda)); + hmiService.createAllHmiReportControlBlocks(scd, po); // Then // Check DataSet is created DataSetAdapter dataSet = findDataSet(scd, "IedName1", "LdInst11", "DS_LDINST11_DQPO"); assertThat(dataSet.getCurrentElem().getFCDA()).hasSize(1).first() - .usingRecursiveComparison().isEqualTo(fcda); + .usingRecursiveComparison().isEqualTo(toFCDA(tfcdaFilter)); // Check ControlBlock is created LN0Adapter ln0 = findLn0(scd, "IedName1", "LdInst11"); assertThat(ln0.getTControlsByType(TReportControl.class)).hasSize(1); @@ -58,14 +69,15 @@ void createAllIhmReportControlBlocks_with_fc_ST_should_create_dataset_and_contro void createAllIhmReportControlBlocks_with_fc_MX_should_create_dataset_and_controlblock() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-hmi-create-report-cb/scd_create_dataset_and_controlblocks_for_hmi.xml"); - TFCDA fcda = newFcda("LdInst11", "PVOC", "1", null, "DoName2", null, TFCEnum.MX); + TFCDAFilter tfcdaFilter = createFCDAFilter("LdInst11", "PVOC", "1", null, "DoName2", Tfc.MX); + po.getFCDAs().getFCDA().add(tfcdaFilter); // When - hmiService.createAllHmiReportControlBlocks(scd, List.of(fcda)); + hmiService.createAllHmiReportControlBlocks(scd, po); // Then // Check DataSet is created DataSetAdapter dataSet = findDataSet(scd, "IedName1", "LdInst11", "DS_LDINST11_CYPO"); assertThat(dataSet.getCurrentElem().getFCDA()).hasSize(1).first() - .usingRecursiveComparison().isEqualTo(fcda); + .usingRecursiveComparison().isEqualTo(toFCDA(tfcdaFilter)); // Check ControlBlock is created LN0Adapter ln0 = findLn0(scd, "IedName1", "LdInst11"); assertThat(ln0.getTControlsByType(TReportControl.class)).hasSize(1); @@ -85,14 +97,15 @@ void createAllIhmReportControlBlocks_with_fc_MX_should_create_dataset_and_contro void createAllIhmReportControlBlocks_with_FCDA_on_ln0_should_create_dataset_and_controlblock() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-hmi-create-report-cb/scd_create_dataset_and_controlblocks_for_hmi.xml"); - TFCDA fcda = newFcda("LdInst11", "LLN0", null, null, "DoName0", null, TFCEnum.ST); + TFCDAFilter tfcdaFilter = createFCDAFilter("LdInst11", "LLN0", null, null, "DoName0", Tfc.ST); + po.getFCDAs().getFCDA().add(tfcdaFilter); // When - hmiService.createAllHmiReportControlBlocks(scd, List.of(fcda)); + hmiService.createAllHmiReportControlBlocks(scd, po); // Then // Check DataSet is created DataSetAdapter dataSet = findDataSet(scd, "IedName1", "LdInst11", "DS_LDINST11_DQPO"); assertThat(dataSet.getCurrentElem().getFCDA()).hasSize(1).first() - .usingRecursiveComparison().isEqualTo(fcda); + .usingRecursiveComparison().isEqualTo(toFCDA(tfcdaFilter)); // Check ControlBlock is created LN0Adapter ln0 = findLn0(scd, "IedName1", "LdInst11"); assertThat(ln0.getTControlsByType(TReportControl.class)).hasSize(1); @@ -113,14 +126,15 @@ void createAllIhmReportControlBlocks_when_lDevice_ON_but_LN_Mod_StVal_missing_sh SCL scd = SclTestMarshaller.getSCLFromFile("/scd-hmi-create-report-cb/scd_create_dataset_and_controlblocks_for_hmi.xml"); LNAdapter ln = findLn(scd, "IedName1", "LdInst11", "ANCR", "1", null); ln.getCurrentElem().unsetDOI(); - TFCDA fcda = newFcda("LdInst11", "ANCR", "1", null, "DoName1", null, TFCEnum.ST); + TFCDAFilter tfcdaFilter = createFCDAFilter("LdInst11", "ANCR", "1", null, "DoName1", Tfc.ST); + po.getFCDAs().getFCDA().add(tfcdaFilter); // When - hmiService.createAllHmiReportControlBlocks(scd, List.of(fcda)); + hmiService.createAllHmiReportControlBlocks(scd, po); // Then // Check DataSet is created DataSetAdapter dataSet = findDataSet(scd, "IedName1", "LdInst11", "DS_LDINST11_DQPO"); assertThat(dataSet.getCurrentElem().getFCDA()).hasSize(1).first() - .usingRecursiveComparison().isEqualTo(fcda); + .usingRecursiveComparison().isEqualTo(toFCDA(tfcdaFilter)); // Check ControlBlock is created LN0Adapter ln0 = findLn0(scd, "IedName1", "LdInst11"); assertThat(ln0.getTControlsByType(TReportControl.class)).hasSize(1); @@ -137,9 +151,10 @@ void createAllIhmReportControlBlocks_when_lDevice_ON_but_LN_Mod_StVal_OFF_should LNAdapter ln = findLn(scd, "IedName1", "LdInst11", "ANCR", "1", null); ln.getDOIAdapterByName(CommonConstants.MOD_DO_NAME).getDataAdapterByName(CommonConstants.STVAL_DA_NAME).setVal("off"); assertThat(ln.getDaiModStValValue()).hasValue("off"); - TFCDA fcda = newFcda("LdInst11", "ANCR", "1", null, "DoName1", null, TFCEnum.ST); + TFCDAFilter tfcdaFilter = createFCDAFilter("LdInst11", "ANCR", "1", null, "DoName1", Tfc.ST); + po.getFCDAs().getFCDA().add(tfcdaFilter); // When - hmiService.createAllHmiReportControlBlocks(scd, List.of(fcda)); + hmiService.createAllHmiReportControlBlocks(scd, po); // Then assertThat(streamAllDataSets(scd)).isEmpty(); assertThat(streamAllControlBlocks(scd, TReportControl.class)).isEmpty(); @@ -152,9 +167,10 @@ void createAllIhmReportControlBlocks_when_lDevice_OFF_should_not_create_dataset( LN0Adapter ln0 = findLn0(scd, "IedName1", "LdInst11"); ln0.getDOIAdapterByName(CommonConstants.MOD_DO_NAME).getDataAdapterByName(CommonConstants.STVAL_DA_NAME).setVal("off"); assertThat(findLDevice(scd, "IedName1", "LdInst11").getLDeviceStatus()).hasValue(ActiveStatus.OFF.getValue()); - TFCDA fcda = newFcda("LdInst11", "ANCR", "1", null, "DoName1", null, TFCEnum.ST); + TFCDAFilter tfcdaFilter = createFCDAFilter("LdInst11", "ANCR", "1", null, "DoName1", Tfc.ST); + po.getFCDAs().getFCDA().add(tfcdaFilter); // When - hmiService.createAllHmiReportControlBlocks(scd, List.of(fcda)); + hmiService.createAllHmiReportControlBlocks(scd, po); // Then assertThat(streamAllDataSets(scd)).isEmpty(); assertThat(streamAllControlBlocks(scd, TReportControl.class)).isEmpty(); @@ -167,12 +183,35 @@ void createAllIhmReportControlBlocks_when_LDevice_has_no_status_should_not_creat LN0Adapter ln0 = findLn0(scd, "IedName1", "LdInst11"); ln0.getDOIAdapterByName(CommonConstants.MOD_DO_NAME).getDataAdapterByName(CommonConstants.STVAL_DA_NAME).getCurrentElem().unsetVal(); assertThat(findLDevice(scd, "IedName1", "LdInst11").getLDeviceStatus()).isEmpty(); - TFCDA fcda = newFcda("LdInst11", "ANCR", "1", null, "DoName1", null, TFCEnum.ST); + TFCDAFilter tfcdaFilter = createFCDAFilter("LdInst11", "ANCR", "1", null, "DoName1", Tfc.ST); + po.getFCDAs().getFCDA().add(tfcdaFilter); // When - hmiService.createAllHmiReportControlBlocks(scd, List.of(fcda)); + hmiService.createAllHmiReportControlBlocks(scd, po); // Then assertThat(streamAllDataSets(scd)).isEmpty(); assertThat(streamAllControlBlocks(scd, TReportControl.class)).isEmpty(); } + private static TFCDAFilter createFCDAFilter(String ldInst, String lnClass, String lnInst, String prefix, String doName, Tfc tfc) { + TFCDAFilter tfcdaFilter = new TFCDAFilter(); + tfcdaFilter.setLdInst("LdInst11"); + tfcdaFilter.setLnClass(lnClass); + tfcdaFilter.setPrefix(null); + tfcdaFilter.setDoName(doName); + tfcdaFilter.setLnInst(lnInst); + tfcdaFilter.setFc(tfc); + return tfcdaFilter; + } + + private static TFCDA toFCDA(TFCDAFilter tfcdaFilter) { + TFCDA tfcda = new TFCDA(); + tfcda.setLdInst(tfcdaFilter.getLdInst()); + tfcda.getLnClass().add(tfcdaFilter.getLnClass()); + tfcda.setPrefix(tfcdaFilter.getPrefix()); + tfcda.setLnInst(tfcdaFilter.getLnInst()); + tfcda.setDoName(tfcdaFilter.getDoName()); + tfcda.setFc(TFCEnum.fromValue(tfcdaFilter.getFc().value())); + return tfcda; + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/FcdaCsvHelperTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/FcdaCsvHelperTest.java index 9c5046080..d3b6ec399 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/FcdaCsvHelperTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/FcdaCsvHelperTest.java @@ -6,12 +6,9 @@ import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.Test; -import org.lfenergy.compas.scl2007b4.model.TFCDA; -import org.lfenergy.compas.scl2007b4.model.TFCEnum; import org.lfenergy.compas.sct.commons.dto.FcdaForDataSetsCreation; import java.io.StringReader; -import java.util.List; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -19,18 +16,12 @@ class FcdaCsvHelperTest { @Test - void constructor_should_initialize_list_of_FcdaForDataSets_and_FcdaForHmiReportControls() { + void constructor_should_initialize_list_of_FcdaForDataSets() { //Given - StringReader csvSourceForHmiReportControl = new StringReader("LDGRP1;;GAPC;12;Ind1;ST"); StringReader csvSourceForDataSetAndControlBlocks = new StringReader("GAPC;Ind1;stVal;ST"); //When - FcdaCsvHelper fcdaCsvHelper = new FcdaCsvHelper(csvSourceForHmiReportControl, csvSourceForDataSetAndControlBlocks); + FcdaCsvHelper fcdaCsvHelper = new FcdaCsvHelper(csvSourceForDataSetAndControlBlocks); //Then - List fcdaForHmiReportControls = fcdaCsvHelper.getFcdaForHmiReportControls(); - assertThat(fcdaForHmiReportControls) - .isNotNull() - .hasSize(1); - Set fcdaForDataSets = fcdaCsvHelper.getFcdaForDataSets(); assertThat(fcdaForDataSets) .isNotNull() @@ -38,27 +29,16 @@ void constructor_should_initialize_list_of_FcdaForDataSets_and_FcdaForHmiReportC } @Test - void get_return_list_of_FcdaForDataSets_and_FcdaForHmiReportControls() { + void get_return_list_of_FcdaForDataSets() { //Given - StringReader csvSourceForHmiReportControl = new StringReader(""" - LDGRP1;;GAPC;12;Ind1;ST - LDCMDSS2;;LLN0;;Health;MX - """); StringReader csvSourceForDataSetAndControlBlocks = new StringReader(""" GAPC;Ind1;stVal;ST LLN0;Health;ctVal;MX """); - FcdaCsvHelper fcdaCsvHelper = new FcdaCsvHelper(csvSourceForHmiReportControl, csvSourceForDataSetAndControlBlocks); + FcdaCsvHelper fcdaCsvHelper = new FcdaCsvHelper(csvSourceForDataSetAndControlBlocks); //When - List fcdaForHmiReportControls = fcdaCsvHelper.getFcdaForHmiReportControls(); Set fcdaForDataSets = fcdaCsvHelper.getFcdaForDataSets(); //Then - assertThat(fcdaForHmiReportControls).hasSize(2); - assertThat(fcdaForHmiReportControls.get(0)).usingRecursiveComparison().isEqualTo( - SclConstructorHelper.newFcda("LDGRP1", "GAPC", "12", null, "Ind1", null, TFCEnum.ST)); - assertThat(fcdaForHmiReportControls.get(1)).usingRecursiveComparison().isEqualTo( - SclConstructorHelper.newFcda("LDCMDSS2", "LLN0", null, null, "Health", null, TFCEnum.MX)); - assertThat(fcdaForDataSets).hasSize(2) .extracting(FcdaForDataSetsCreation::getLnClass, FcdaForDataSetsCreation::getDoName, FcdaForDataSetsCreation::getDaName, FcdaForDataSetsCreation::getFc) .containsExactly(Tuple.tuple("LLN0", "Health", "ctVal", "MX"), Tuple.tuple("GAPC", "Ind1", "stVal", "ST")); From 50862f238273ddad8d7b658024b2f171cdbbd25c Mon Sep 17 00:00:00 2001 From: Aliou DIAITE Date: Tue, 12 Dec 2023 18:33:53 +0100 Subject: [PATCH 4/9] feat(#352): remove compasflow and extref bindings Signed-off-by: Aliou DIAITE --- pom.xml | 2 +- ...fService.java => ExtRefEditorService.java} | 345 ++++++++++-------- .../compas/sct/commons/api/ExtRefEditor.java | 8 + .../compas/sct/commons/scl/ExtRefService.java | 106 ++++++ .../commons/scl/ied/AccessPointAdapter.java | 2 +- .../sct/commons/scl/ied/InputsAdapter.java | 79 +--- ...Test.java => ExtRefEditorServiceTest.java} | 192 ++++++++-- .../sct/commons/scl/ExtRefServiceTest.java | 161 ++++++++ .../scd_extref_flow_debind_success.xml | 101 +++++ ...extref_flow_debind_volatagelevelname_0.xml | 76 ++++ ..._flow_debind_volatagelevelname_unknown.xml | 76 ++++ .../scd_extref_flow_not_debind.xml | 101 +++++ ...w_not_debind_volatagelevelname_unknown.xml | 76 ++++ 13 files changed, 1076 insertions(+), 249 deletions(-) rename sct-commons/src/main/java/org/lfenergy/compas/sct/commons/{ExtRefService.java => ExtRefEditorService.java} (86%) create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java rename sct-commons/src/test/java/org/lfenergy/compas/sct/commons/{ExtRefServiceTest.java => ExtRefEditorServiceTest.java} (78%) create mode 100644 sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java create mode 100644 sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_success.xml create mode 100644 sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_volatagelevelname_0.xml create mode 100644 sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_volatagelevelname_unknown.xml create mode 100644 sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_not_debind.xml create mode 100644 sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_not_debind_volatagelevelname_unknown.xml diff --git a/pom.xml b/pom.xml index 93499459c..2665d6890 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ sct-coverage/** ../sct-coverage/target/site/jacoco-aggregate/jacoco.xml ${basedir}/${aggregate.report.dir} - 0.17.0 + 0.18.0 0.0.4 3.4.1 3.2.1 diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefEditorService.java similarity index 86% rename from sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefService.java rename to sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefEditorService.java index ac5f6f4ff..5773c8b2d 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefEditorService.java @@ -4,6 +4,7 @@ package org.lfenergy.compas.sct.commons; +import lombok.RequiredArgsConstructor; import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.api.ExtRefEditor; import org.lfenergy.compas.sct.commons.dto.*; @@ -12,6 +13,7 @@ import org.lfenergy.compas.sct.commons.model.epf.TCBscopeType; import org.lfenergy.compas.sct.commons.model.epf.TChannel; import org.lfenergy.compas.sct.commons.model.epf.TChannelType; +import org.lfenergy.compas.sct.commons.scl.ExtRefService; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; import org.lfenergy.compas.sct.commons.scl.ldevice.LDeviceAdapter; @@ -30,8 +32,164 @@ import static org.lfenergy.compas.sct.commons.util.CommonConstants.*; import static org.lfenergy.compas.sct.commons.util.Utils.isExtRefFeedBySameControlBlock; -public class ExtRefService implements ExtRefEditor { +@RequiredArgsConstructor +public class ExtRefEditorService implements ExtRefEditor { private static final String INVALID_OR_MISSING_ATTRIBUTES_IN_EXT_REF_BINDING_INFO = "Invalid or missing attributes in ExtRef binding info"; + private static final Map voltageCodification = Map.of( + "3", "HT", + "4", "HT", + "5", "THT", + "6", "THT", + "7", "THT" + ); + private final ExtRefService extRefService; + + /** + * Remove ExtRef which are fed by same Control Block + * + * @return list ExtRefs without duplication + */ + public static List filterDuplicatedExtRefs(List tExtRefs) { + List filteredList = new ArrayList<>(); + tExtRefs.forEach(tExtRef -> { + if (filteredList.stream().noneMatch(t -> isExtRefFeedBySameControlBlock(tExtRef, t))) + filteredList.add(tExtRef); + }); + return filteredList; + } + + /** + * Provides valid IED sources according to EPF configuration.
+ * EPF verification include:
+ * 1. COMPAS-Bay verification that should be closed to the provided Flow Kind
+ * 2. COMPAS-ICDHeader verification that should match the provided parameters
+ * 3. Active LDevice source object that should match the provided parameters
+ * 4. Active LNode source object that should match the provided parameters
+ * 5. Valid DataTypeTemplate Object hierarchy that should match the DO/DA/BDA parameters
+ * + * @param sclRootAdapter SCL scl object + * @param compasBay TCompasBay represent Bay Private + * @param channel TChannel represent parameters + * @return the IED sources matching the LDEPF parameters + */ + private static List getIedSources(SclRootAdapter sclRootAdapter, TCompasBay compasBay, TChannel channel) { + return sclRootAdapter.streamIEDAdapters() + .filter(iedAdapter -> (channel.getBayScope().equals(TCBscopeType.BAY_EXTERNAL) + && iedAdapter.getPrivateCompasBay().stream().noneMatch(bay -> bay.getUUID().equals(compasBay.getUUID()))) + || (channel.getBayScope().equals(TCBscopeType.BAY_INTERNAL) + && iedAdapter.getPrivateCompasBay().stream().anyMatch(bay -> bay.getUUID().equals(compasBay.getUUID())))) + .filter(iedAdapter -> doesIcdHeaderMatchLDEPFChannel(iedAdapter, channel)) + .filter(iedAdapter -> getActiveSourceLDeviceByLDEPFChannel(iedAdapter, channel) + .map(lDeviceAdapter -> getActiveLNSourceByLDEPFChannel(lDeviceAdapter, channel) + .map(lnAdapter -> isValidDataTypeTemplate(lnAdapter, channel)) + .orElse(false)) + .orElse(false)) + .map(IEDAdapter::getCurrentElem) + .limit(2) + .toList(); + } + + /** + * Verify if an Extref matches the EPF Channel or not. + * + * @param extRef TExtRef + * @param tChannel TChannel + * @return true if the TExtRef matches the EPF channel + */ + private static Boolean doesExtRefMatchLDEPFChannel(TExtRef extRef, TChannel tChannel) { + Boolean doesExtRefDescMatchAnalogChannel = tChannel.getChannelType().equals(TChannelType.ANALOG) + && extRef.getDesc().startsWith("DYN_LDEPF_ANALOG CHANNEL " + tChannel.getChannelNum() + "_1_AnalogueValue") + && extRef.getDesc().endsWith("_" + tChannel.getDAName() + "_1"); + Boolean doesExtRefDescMatchDigitalChannel = tChannel.getChannelType().equals(TChannelType.DIGITAL) + && extRef.getDesc().startsWith("DYN_LDEPF_DIGITAL CHANNEL " + tChannel.getChannelNum() + "_1_BOOLEEN") + && extRef.getDesc().endsWith("_" + tChannel.getDAName() + "_1"); + return extRef.isSetDesc() && (doesExtRefDescMatchAnalogChannel || doesExtRefDescMatchDigitalChannel) + && extRef.isSetPLN() && Utils.lnClassEquals(extRef.getPLN(), tChannel.getLNClass()) + && extRef.isSetPDO() && extRef.getPDO().equals(tChannel.getDOName()); + } + + /** + * Verify whether the IED satisfies the EPF channel for the private element `TCompasICDHeader` + * + * @param iedAdapter IEDAdapter + * @param channel TChannel + * @return true if the TCompasICDHeader matches the EPF channel + */ + private static boolean doesIcdHeaderMatchLDEPFChannel(IEDAdapter iedAdapter, TChannel channel) { + return iedAdapter.getCompasICDHeader() + .map(compasICDHeader -> compasICDHeader.getIEDType().value().equals(channel.getIEDType()) + && compasICDHeader.getIEDredundancy().value().equals(channel.getIEDRedundancy().value()) + && compasICDHeader.getIEDSystemVersioninstance().toString().equals(channel.getIEDSystemVersionInstance())) + .orElse(false); + } + + /** + * Provides Active LDevice according to EPF channel's inst attribute + * + * @param iedAdapter IEDAdapter + * @param channel TChannel + * @return LDeviceAdapter object that matches the EPF channel + */ + private static Optional getActiveSourceLDeviceByLDEPFChannel(IEDAdapter iedAdapter, TChannel channel) { + LdeviceService ldeviceService = new LdeviceService(); + return ldeviceService.findLdevice(iedAdapter.getCurrentElem(), tlDevice -> tlDevice.getInst().equals(channel.getLDInst())) + .filter(tlDevice -> ldeviceService.getLdeviceStatus(tlDevice).map(ActiveStatus.ON::equals).orElse(false)) + .map(tlDevice -> new LDeviceAdapter(iedAdapter, tlDevice)); + } + + /** + * Provides Active LN Object that satisfies the EPF channel attributes (lnClass, lnInst, prefix) + * + * @param lDeviceAdapter LDeviceAdapter + * @param channel TChannel + * @return AbstractLNAdapter object that matches the EPF channel + */ + private static Optional> getActiveLNSourceByLDEPFChannel(LDeviceAdapter lDeviceAdapter, TChannel channel) { + return lDeviceAdapter.getLNAdaptersIncludingLN0() + .stream() + .filter(lnAdapter -> lnAdapter.getLNClass().equals(channel.getLNClass()) + && lnAdapter.getLNInst().equals(channel.getLNInst()) + && trimToEmpty(channel.getLNPrefix()).equals(trimToEmpty(lnAdapter.getPrefix()))) + .findFirst() + .filter(lnAdapter -> lnAdapter.getDaiModStValValue() + .map(status -> status.equals(ActiveStatus.ON.getValue())) + .orElse(true)); + } + + /** + * Verify whether the LN satisfies the EPF channel parameters for Data Type Template elements. + * + * @param lnAdapter AbstractLNAdapter + * @param channel TChannel + * @return true if the LN matches the EPF channel + */ + private static boolean isValidDataTypeTemplate(AbstractLNAdapter lnAdapter, TChannel channel) { + if (isBlank(channel.getDOName())) { + return true; + } + String doName = isBlank(channel.getDOInst()) || channel.getDOInst().equals("0") ? channel.getDOName() : channel.getDOName() + channel.getDOInst(); + DoTypeName doTypeName = new DoTypeName(doName); + if (isNotBlank(channel.getSDOName())) { + doTypeName.getStructNames().add(channel.getSDOName()); + } + DaTypeName daTypeName = new DaTypeName(channel.getDAName()); + if (isNotBlank(channel.getBDAName())) { + daTypeName.setBType(TPredefinedBasicTypeEnum.STRUCT); + daTypeName.getStructNames().add(channel.getBDAName()); + } + if (isNotBlank(channel.getSBDAName())) { + daTypeName.getStructNames().add(channel.getSBDAName()); + } + return lnAdapter.getDataTypeTemplateAdapter().getLNodeTypeAdapterById(lnAdapter.getLnType()) + .filter(lNodeTypeAdapter -> { + try { + lNodeTypeAdapter.checkDoAndDaTypeName(doTypeName, daTypeName); + } catch (ScdException ex) { + return false; + } + return true; + }).isPresent(); + } @Override public void updateExtRefBinders(SCL scd, ExtRefInfo extRefInfo) throws ScdException { @@ -119,7 +277,7 @@ public List updateAllExtRefIedNames(SCL scd) { @Override public List manageBindingForLDEPF(SCL scd, EPF epf) { List sclReportItems = new ArrayList<>(); - if(!epf.isSetChannels()) return sclReportItems; + if (!epf.isSetChannels()) return sclReportItems; SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); sclRootAdapter.streamIEDAdapters() .filter(iedAdapter -> !iedAdapter.getName().contains("TEST")) @@ -187,153 +345,12 @@ private List checkIedUnityOfIcdSystemVersionUuid(SclRootAdapter s .toList(); } - /** - * Remove ExtRef which are fed by same Control Block - * - * @return list ExtRefs without duplication - */ - public static List filterDuplicatedExtRefs(List tExtRefs) { - List filteredList = new ArrayList<>(); - tExtRefs.forEach(tExtRef -> { - if (filteredList.stream().noneMatch(t -> isExtRefFeedBySameControlBlock(tExtRef, t))) - filteredList.add(tExtRef); - }); - return filteredList; - } - - /** - * Provides valid IED sources according to EPF configuration.
- * EPF verification include:
- * 1. COMPAS-Bay verification that should be closed to the provided Flow Kind
- * 2. COMPAS-ICDHeader verification that should match the provided parameters
- * 3. Active LDevice source object that should match the provided parameters
- * 4. Active LNode source object that should match the provided parameters
- * 5. Valid DataTypeTemplate Object hierarchy that should match the DO/DA/BDA parameters
- * @param sclRootAdapter SCL scl object - * @param compasBay TCompasBay represent Bay Private - * @param channel TChannel represent parameters - * @return the IED sources matching the LDEPF parameters - */ - private static List getIedSources(SclRootAdapter sclRootAdapter, TCompasBay compasBay, TChannel channel) { - return sclRootAdapter.streamIEDAdapters() - .filter(iedAdapter -> (channel.getBayScope().equals(TCBscopeType.BAY_EXTERNAL) - && iedAdapter.getPrivateCompasBay().stream().noneMatch(bay -> bay.getUUID().equals(compasBay.getUUID()))) - || (channel.getBayScope().equals(TCBscopeType.BAY_INTERNAL) - && iedAdapter.getPrivateCompasBay().stream().anyMatch(bay -> bay.getUUID().equals(compasBay.getUUID())))) - .filter(iedAdapter -> doesIcdHeaderMatchLDEPFChannel(iedAdapter, channel)) - .filter(iedAdapter -> getActiveSourceLDeviceByLDEPFChannel(iedAdapter, channel) - .map(lDeviceAdapter -> getActiveLNSourceByLDEPFChannel(lDeviceAdapter, channel) - .map(lnAdapter -> isValidDataTypeTemplate(lnAdapter, channel)) - .orElse(false)) - .orElse(false)) - .map(IEDAdapter::getCurrentElem) - .limit(2) - .toList(); - } - - /** - * Verify if an Extref matches the EPF Channel or not. - * @param extRef TExtRef - * @param tChannel TChannel - * @return true if the TExtRef matches the EPF channel - */ - private static Boolean doesExtRefMatchLDEPFChannel(TExtRef extRef, TChannel tChannel) { - Boolean doesExtRefDescMatchAnalogChannel = tChannel.getChannelType().equals(TChannelType.ANALOG) - && extRef.getDesc().startsWith("DYN_LDEPF_ANALOG CHANNEL " + tChannel.getChannelNum()+"_1_AnalogueValue") - && extRef.getDesc().endsWith("_" + tChannel.getDAName() + "_1"); - Boolean doesExtRefDescMatchDigitalChannel = tChannel.getChannelType().equals(TChannelType.DIGITAL) - && extRef.getDesc().startsWith("DYN_LDEPF_DIGITAL CHANNEL " + tChannel.getChannelNum()+"_1_BOOLEEN") - && extRef.getDesc().endsWith("_" + tChannel.getDAName() + "_1"); - return extRef.isSetDesc() && (doesExtRefDescMatchAnalogChannel || doesExtRefDescMatchDigitalChannel) - && extRef.isSetPLN() && Utils.lnClassEquals(extRef.getPLN(), tChannel.getLNClass()) - && extRef.isSetPDO() && extRef.getPDO().equals(tChannel.getDOName()); - } - - /** - * Verify whether the IED satisfies the EPF channel for the private element `TCompasICDHeader` - * @param iedAdapter IEDAdapter - * @param channel TChannel - * @return true if the TCompasICDHeader matches the EPF channel - */ - private static boolean doesIcdHeaderMatchLDEPFChannel(IEDAdapter iedAdapter, TChannel channel) { - return iedAdapter.getCompasICDHeader() - .map(compasICDHeader -> compasICDHeader.getIEDType().value().equals(channel.getIEDType()) - && compasICDHeader.getIEDredundancy().value().equals(channel.getIEDRedundancy().value()) - && compasICDHeader.getIEDSystemVersioninstance().toString().equals(channel.getIEDSystemVersionInstance())) - .orElse(false); - } - - /** - * Provides Active LDevice according to EPF channel's inst attribute - * @param iedAdapter IEDAdapter - * @param channel TChannel - * @return LDeviceAdapter object that matches the EPF channel - */ - private static Optional getActiveSourceLDeviceByLDEPFChannel(IEDAdapter iedAdapter, TChannel channel) { - LdeviceService ldeviceService = new LdeviceService(); - return ldeviceService.findLdevice(iedAdapter.getCurrentElem(), tlDevice -> tlDevice.getInst().equals(channel.getLDInst())) - .filter(tlDevice -> ldeviceService.getLdeviceStatus(tlDevice).map(ActiveStatus.ON::equals).orElse(false)) - .map(tlDevice -> new LDeviceAdapter(iedAdapter, tlDevice)); - } - - /** - * Provides Active LN Object that satisfies the EPF channel attributes (lnClass, lnInst, prefix) - * @param lDeviceAdapter LDeviceAdapter - * @param channel TChannel - * @return AbstractLNAdapter object that matches the EPF channel - */ - private static Optional> getActiveLNSourceByLDEPFChannel(LDeviceAdapter lDeviceAdapter, TChannel channel) { - return lDeviceAdapter.getLNAdaptersIncludingLN0() - .stream() - .filter(lnAdapter -> lnAdapter.getLNClass().equals(channel.getLNClass()) - && lnAdapter.getLNInst().equals(channel.getLNInst()) - && trimToEmpty(channel.getLNPrefix()).equals(trimToEmpty(lnAdapter.getPrefix()))) - .findFirst() - .filter(lnAdapter -> lnAdapter.getDaiModStValValue() - .map(status -> status.equals(ActiveStatus.ON.getValue())) - .orElse(true)); - } - - /** - * Verify whether the LN satisfies the EPF channel parameters for Data Type Template elements. - * @param lnAdapter AbstractLNAdapter - * @param channel TChannel - * @return true if the LN matches the EPF channel - */ - private static boolean isValidDataTypeTemplate(AbstractLNAdapter lnAdapter, TChannel channel) { - if(isBlank(channel.getDOName())){ - return true; - } - String doName = isBlank(channel.getDOInst()) || channel.getDOInst().equals("0") ? channel.getDOName() : channel.getDOName() + channel.getDOInst(); - DoTypeName doTypeName = new DoTypeName(doName); - if(isNotBlank(channel.getSDOName())){ - doTypeName.getStructNames().add(channel.getSDOName()); - } - DaTypeName daTypeName = new DaTypeName(channel.getDAName()); - if(isNotBlank(channel.getBDAName())){ - daTypeName.setBType(TPredefinedBasicTypeEnum.STRUCT); - daTypeName.getStructNames().add(channel.getBDAName()); - } - if(isNotBlank(channel.getSBDAName())){ - daTypeName.getStructNames().add(channel.getSBDAName()); - } - return lnAdapter.getDataTypeTemplateAdapter().getLNodeTypeAdapterById(lnAdapter.getLnType()) - .filter(lNodeTypeAdapter -> { - try { - lNodeTypeAdapter.checkDoAndDaTypeName(doTypeName, daTypeName); - } catch (ScdException ex) { - return false; - } - return true; - }).isPresent(); - } - private void updateLDEPFExtRefBinding(TExtRef extRef, TIED iedSource, TChannel setting) { extRef.setIedName(iedSource.getName()); extRef.setLdInst(setting.getLDInst()); extRef.getLnClass().add(setting.getLNClass()); extRef.setLnInst(setting.getLNInst()); - if(!isBlank(setting.getLNPrefix())) { + if (!isBlank(setting.getLNPrefix())) { extRef.setPrefix(setting.getLNPrefix()); } String doName = isBlank(setting.getDOInst()) || setting.getDOInst().equals("0") ? setting.getDOName() : setting.getDOName() + setting.getDOInst(); @@ -377,9 +394,6 @@ private Optional updateVal(AbstractLNAdapter lnAdapter, String return lnAdapter.getDOIAdapterByName(doName).updateDAI(daName, value); } - private record DoNameAndDaName(String doName, String daName) { - } - private String computeDaiValue(AbstractLNAdapter lnAdapter, TExtRef extRef, String daName) { if (LN_PREFIX_B.equals(lnAdapter.getPrefix()) || LN_PREFIX_A.equals(lnAdapter.getPrefix())) { return extRef.getIedName() + @@ -399,4 +413,41 @@ private String computeDaiValue(AbstractLNAdapter lnAdapter, TExtRef extRef, S } } + @Override + public void debindCompasFlowsAndExtRefsBasedOnVoltageLevel(SCL scd) { + LdeviceService ldeviceService = new LdeviceService(); + scd.getSubstation() + .stream() + .flatMap(tSubstation -> tSubstation.getVoltageLevel().stream()) + .map(TVoltageLevel::getName) + .filter(tVoltageLevelName -> !"0".equals(tVoltageLevelName)) + .forEach(tVoltageLevelName -> scd.getIED().stream() + .flatMap(ldeviceService::getLdevices) + .forEach(tlDevice -> { + String flowSource = voltageCodification.get(tVoltageLevelName); + TInputs tInputs = tlDevice.getLN0().getInputs(); + PrivateUtils.getPrivateStream(tInputs.getPrivate(), TCompasFlow.class) + .filter(TCompasFlow::isSetFlowSourceVoltageLevel) + .filter(TCompasFlow::isSetExtRefiedName) + .forEach(tCompasFlow -> { + if (flowSource == null) { + //debind all compas flow + extRefService.clearCompasFlowBinding(tCompasFlow); + } else if (!tCompasFlow.getFlowSourceVoltageLevel().equals(flowSource)) { + //debind extRef + extRefService.getMatchingExtRef(tInputs, tCompasFlow) + .forEach(extRefService::clearExtRefBinding); + //debind compas flow + extRefService.clearCompasFlowBinding(tCompasFlow); + + } + }); + }) + ); + } + + private record DoNameAndDaName(String doName, String daName) { + } + + } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ExtRefEditor.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ExtRefEditor.java index 5b49ffb81..4b656468f 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ExtRefEditor.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ExtRefEditor.java @@ -56,10 +56,18 @@ public interface ExtRefEditor { /** * ExtRef Binding For LDevice (inst=LDEPF) that matching EPF configuration + * * @param scd SCL * @param epf EPF * @return list of encountered errors */ List manageBindingForLDEPF(SCL scd, EPF epf); + /** + * Debinding of Private CompasFlows and ExtRef signals based on voltageLevel + * + * @param scd SCL file in which ExtRef and Private CompasFlow should be debind + */ + void debindCompasFlowsAndExtRefsBasedOnVoltageLevel(SCL scd); + } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java new file mode 100644 index 000000000..09da0767f --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java @@ -0,0 +1,106 @@ +/* + * // SPDX-FileCopyrightText: 2023 RTE FRANCE + * // + * // SPDX-License-Identifier: Apache-2.0 + */ + +package org.lfenergy.compas.sct.commons.scl; + +import org.lfenergy.compas.scl2007b4.model.TCompasFlow; +import org.lfenergy.compas.scl2007b4.model.TExtRef; +import org.lfenergy.compas.scl2007b4.model.TInputs; +import org.lfenergy.compas.sct.commons.util.PrivateUtils; +import org.lfenergy.compas.sct.commons.util.Utils; + +import java.util.stream.Stream; + +public class ExtRefService { + + /** + * List all ExtRefs in this Inputs + * + * @return list of ExtRefs. List is modifiable. + */ + public Stream getExtRefs(TInputs inputs) { + if (inputs == null || !inputs.isSetExtRef()) { + return Stream.empty(); + } + return inputs.getExtRef().stream(); + } + + /** + * Find CompasFlows that match given ExtRef + * + * @param inputs inputs containing Privates CompasFlow and TExtRefs + * @param tExtRef corresponding to CompasFlow we are searching + * @return list of matching CompasFlows + */ + public Stream getMatchingCompasFlows(TInputs inputs, TExtRef tExtRef) { + return PrivateUtils.extractCompasPrivates(inputs, TCompasFlow.class) + .filter(compasFlow -> isMatchingExtRef(compasFlow, tExtRef)); + } + + /** + * Retrieves ExtRefs corresponding to given CompasFlow + * + * @param inputs node containing CompasFlows and ExtRefs + * @param tCompasFlow corresponding to Extrefs we are searching + * @return stream of matching ExtRefs + */ + public Stream getMatchingExtRef(TInputs inputs, TCompasFlow tCompasFlow) { + return getExtRefs(inputs) + .filter(tExtRef -> isMatchingExtRef(tCompasFlow, tExtRef)); + } + + /** + * Debind ExtRef + * + * @param extRef to debind + */ + public void clearExtRefBinding(TExtRef extRef) { + extRef.setIedName(null); + extRef.setLdInst(null); + extRef.setPrefix(null); + extRef.setLnInst(null); + extRef.setDoName(null); + extRef.setDaName(null); + extRef.setServiceType(null); + extRef.setSrcLDInst(null); + extRef.setSrcPrefix(null); + extRef.setSrcLNInst(null); + extRef.setSrcCBName(null); + extRef.unsetLnClass(); + extRef.unsetSrcLNClass(); + } + + /** + * Debind CompasFlow + * + * @param tCompasFlow to debind + */ + public void clearCompasFlowBinding(TCompasFlow tCompasFlow) { + tCompasFlow.setExtRefiedName(null); + tCompasFlow.setExtRefldinst(null); + tCompasFlow.setExtReflnClass(null); + tCompasFlow.setExtReflnInst(null); + tCompasFlow.setExtRefprefix(null); + } + + /** + * Check if extRef matches CompasFlow + * + * @param compasFlow compasFlow + * @param extRef extRef + * @return true if all required attributes matches. Note that empty string, whitespaces only string and null values are considered as matching + * (missing attributes matches attribute with empty string value or whitespaces only). Return false otherwise. + */ + private boolean isMatchingExtRef(TCompasFlow compasFlow, TExtRef extRef) { + String extRefLnClass = extRef.isSetLnClass() ? extRef.getLnClass().get(0) : null; + return Utils.equalsOrBothBlank(compasFlow.getDataStreamKey(), extRef.getDesc()) + && Utils.equalsOrBothBlank(compasFlow.getExtRefiedName(), extRef.getIedName()) + && Utils.equalsOrBothBlank(compasFlow.getExtRefldinst(), extRef.getLdInst()) + && Utils.equalsOrBothBlank(compasFlow.getExtRefprefix(), extRef.getPrefix()) + && Utils.equalsOrBothBlank(compasFlow.getExtReflnClass(), extRefLnClass) + && Utils.equalsOrBothBlank(compasFlow.getExtReflnInst(), extRef.getLnInst()); + } +} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java index 9c64f66da..afa9958c8 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java @@ -19,7 +19,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.lfenergy.compas.sct.commons.ExtRefService.filterDuplicatedExtRefs; +import static org.lfenergy.compas.sct.commons.ExtRefEditorService.filterDuplicatedExtRefs; /** * A representation of the model object diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapter.java index b15ed6bfc..b25094698 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapter.java @@ -8,11 +8,12 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.lfenergy.compas.scl2007b4.model.*; -import org.lfenergy.compas.sct.commons.ExtRefService; +import org.lfenergy.compas.sct.commons.ExtRefEditorService; import org.lfenergy.compas.sct.commons.dto.DataAttributeRef; import org.lfenergy.compas.sct.commons.dto.FcdaForDataSetsCreation; import org.lfenergy.compas.sct.commons.dto.SclReportItem; import org.lfenergy.compas.sct.commons.exception.ScdException; +import org.lfenergy.compas.sct.commons.scl.ExtRefService; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.scl.ldevice.LDeviceAdapter; @@ -63,6 +64,8 @@ public class InputsAdapter extends SclElementAdapter { private static final int EXTREF_DESC_DA_NAME_POSITION = -2; + private final ExtRefService extRefService = new ExtRefService(); + /** * Constructor * @@ -101,13 +104,13 @@ public List updateAllExtRefIedNames(Map icdSy try { ActiveStatus lDeviceStatus = ActiveStatus.fromValue(optionalLDeviceStatus.get()); return switch (lDeviceStatus) { - case ON -> getExtRefs().stream() + case ON -> extRefService.getExtRefs(currentElem) .filter(tExtRef -> StringUtils.isNotBlank(tExtRef.getIedName()) && StringUtils.isNotBlank(tExtRef.getDesc())) .map(extRef -> updateExtRefIedName(extRef, icdSystemVersionToIed.get(extRef.getIedName()))) .flatMap(Optional::stream) .toList(); case OFF -> { - getExtRefs().forEach(this::clearBinding); + extRefService.getExtRefs(currentElem).forEach(extRefService::clearExtRefBinding); yield Collections.emptyList(); } }; @@ -123,19 +126,19 @@ public List updateAllExtRefIedNames(Map icdSy * @return Error if ExtRef could not be updated */ private Optional updateExtRefIedName(TExtRef extRef, IEDAdapter sourceIed) { - List matchingCompasFlows = getMatchingCompasFlows(extRef); + List matchingCompasFlows = extRefService.getMatchingCompasFlows(currentElem, extRef).toList(); if (!singleMatch(matchingCompasFlows)) { return fatalReportItem(extRef, matchingCompasFlows.isEmpty() ? MESSAGE_NO_MATCHING_COMPAS_FLOW : MESSAGE_TOO_MANY_MATCHING_COMPAS_FLOWS); } TCompasFlow compasFlow = matchingCompasFlows.get(0); if (compasFlow.getFlowStatus() == TCompasFlowStatus.INACTIVE) { - clearBinding(extRef); + extRefService.clearExtRefBinding(extRef); return Optional.empty(); } Optional sourceValidationError = validateExtRefSource(extRef, sourceIed); if (sourceValidationError.isPresent()) { - clearBinding(extRef); + extRefService.clearExtRefBinding(extRef); return sourceValidationError; } String sourceIedName = PrivateUtils.extractCompasPrivate(sourceIed.getCurrentElem(), TCompasICDHeader.class) @@ -146,18 +149,6 @@ private Optional updateExtRefIedName(TExtRef extRef, IEDAdapter s return Optional.empty(); } - /** - * List all ExtRefs in this Inputs - * - * @return list of ExtRefs. List is modifiable. - */ - private List getExtRefs() { - if (!currentElem.isSetExtRef()) { - return Collections.emptyList(); - } - return currentElem.getExtRef(); - } - private Optional validateExtRefSource(TExtRef extRef, IEDAdapter sourceIed) { if (sourceIed == null) { return warningReportItem(extRef, MESSAGE_EXTREF_IEDNAME_DOES_NOT_MATCH_ANY_SYSTEM_VERSION_UUID); @@ -192,22 +183,6 @@ private boolean singleMatch(List matchingCompasFlows) { return matchingCompasFlows.size() == 1; } - private void clearBinding(TExtRef extRef) { - extRef.setIedName(null); - extRef.setLdInst(null); - extRef.setPrefix(null); - extRef.setLnInst(null); - extRef.setDoName(null); - extRef.setDaName(null); - extRef.setServiceType(null); - extRef.setSrcLDInst(null); - extRef.setSrcPrefix(null); - extRef.setSrcLNInst(null); - extRef.setSrcCBName(null); - extRef.unsetLnClass(); - extRef.unsetSrcLNClass(); - } - private Optional warningReportItem(TExtRef extRef, String message) { return Optional.of(SclReportItem.warning(extRefXPath(extRef.getDesc()), message)); } @@ -221,36 +196,6 @@ private String extRefXPath(String extRefDesc) { Utils.xpathAttributeFilter("desc", extRefDesc)); } - /** - * Find CompasFlows that match given ExtRef - * - * @param extRef extRef to match - * @return list of matching CompasFlows - */ - private List getMatchingCompasFlows(TExtRef extRef) { - return PrivateUtils.extractCompasPrivates(currentElem, TCompasFlow.class) - .filter(compasFlow -> isMatchingExtRef(compasFlow, extRef)) - .toList(); - } - - /** - * Check if extRef matches CompasFlow - * - * @param compasFlow compasFlow - * @param extRef extRef - * @return true if all required attributes matches. Note that empty string, whitespaces only string and null values are considered as matching - * (missing attributes matches attribute with empty string value or whitespaces only). Return false otherwise. - */ - private boolean isMatchingExtRef(TCompasFlow compasFlow, TExtRef extRef) { - String extRefLnClass = extRef.isSetLnClass() ? extRef.getLnClass().get(0) : null; - return Utils.equalsOrBothBlank(compasFlow.getDataStreamKey(), extRef.getDesc()) - && Utils.equalsOrBothBlank(compasFlow.getExtRefiedName(), extRef.getIedName()) - && Utils.equalsOrBothBlank(compasFlow.getExtRefldinst(), extRef.getLdInst()) - && Utils.equalsOrBothBlank(compasFlow.getExtRefprefix(), extRef.getPrefix()) - && Utils.equalsOrBothBlank(compasFlow.getExtReflnClass(), extRefLnClass) - && Utils.equalsOrBothBlank(compasFlow.getExtReflnInst(), extRef.getLnInst()); - } - private LDeviceAdapter getLDeviceAdapter() { return parentAdapter.getParentAdapter(); } @@ -264,7 +209,7 @@ public List updateAllSourceDataSetsAndControlBlocks(Set updateAllSourceDataSetsAndControlBlocks(Set flowStatus == TCompasFlowStatus.ACTIVE || flowStatus == TCompasFlowStatus.UNTESTED) .isPresent(); @@ -429,7 +374,7 @@ private SclRootAdapter getSclRootAdapter() { * @return list ExtRefs without duplication */ public List filterDuplicatedExtRefs() { - return ExtRefService.filterDuplicatedExtRefs(getExtRefs()); + return ExtRefEditorService.filterDuplicatedExtRefs(extRefService.getExtRefs(currentElem).toList()); } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ExtRefServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ExtRefEditorServiceTest.java similarity index 78% rename from sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ExtRefServiceTest.java rename to sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ExtRefEditorServiceTest.java index 7cfb52394..df0e36403 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ExtRefServiceTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ExtRefEditorServiceTest.java @@ -5,9 +5,10 @@ package org.lfenergy.compas.sct.commons; import org.assertj.core.api.Assertions; +import org.assertj.core.groups.Tuple; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -15,41 +16,39 @@ import org.lfenergy.compas.sct.commons.dto.*; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.model.epf.*; +import org.lfenergy.compas.sct.commons.scl.ExtRefService; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.scl.ldevice.LDeviceAdapter; import org.lfenergy.compas.sct.commons.scl.ln.AbstractLNAdapter; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; import org.lfenergy.compas.sct.commons.util.PrivateUtils; -import org.mockito.InjectMocks; -import org.mockito.junit.jupiter.MockitoExtension; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.math.BigInteger; import java.util.List; -import java.util.Objects; import java.util.Optional; -import java.util.*; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.lfenergy.compas.sct.commons.ExtRefEditorService.filterDuplicatedExtRefs; import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.*; import static org.lfenergy.compas.sct.commons.util.CommonConstants.*; -@ExtendWith(MockitoExtension.class) -class ExtRefServiceTest { +class ExtRefEditorServiceTest { - @InjectMocks - ExtRefService extRefService; + ExtRefEditorService extRefEditorService; + + @BeforeEach + void init() { + extRefEditorService = new ExtRefEditorService(new ExtRefService()); + } @Test void updateAllExtRefIedNames_should_update_iedName_and_ExtRefIedName() { // Given : An ExtRef with a matching compas:Flow SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-iedname/scd_set_extref_iedname_success.xml"); // When - extRefService.updateAllExtRefIedNames(scd); + extRefEditorService.updateAllExtRefIedNames(scd); // Then TExtRef extRef = findExtRef(scd, "IED_NAME1", "LD_INST11", "STAT_LDSUIED_LPDO 1 Sortie_13_BOOLEAN_18_stVal_1"); assertThat(extRef.getIedName()).isEqualTo("IED_NAME2"); @@ -68,7 +67,7 @@ void updateAllExtRefIedNames_should_return_success_status() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-iedname/scd_set_extref_iedname_success.xml"); // When - List sclReportItems = extRefService.updateAllExtRefIedNames(scd); + List sclReportItems = extRefEditorService.updateAllExtRefIedNames(scd); // Then assertThat(sclReportItems.stream().noneMatch(SclReportItem::isError)) .overridingErrorMessage(String.valueOf(sclReportItems)) @@ -80,7 +79,7 @@ void updateAllExtRefIedNames_should_return_success_status() { void updateAllExtRefIedNames_should_report_errors(String testCase, SCL scl, SclReportItem... errors) { // Given : scl parameter // When - List sclReportItems = extRefService.updateAllExtRefIedNames(scl); + List sclReportItems = extRefEditorService.updateAllExtRefIedNames(scl); // Then : the sclReport should report all errors described in the comments in the SCD file assertThat(sclReportItems).isNotNull(); assertThat(sclReportItems.stream().noneMatch(SclReportItem::isError)).isFalse(); @@ -154,7 +153,7 @@ void updateAllExtRefIedNames_when_not_bindable_should_clear_binding() { // Given : see comments in SCD file SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-iedname/scd_set_extref_iedname_with_extref_errors.xml"); // When - extRefService.updateAllExtRefIedNames(scd); + extRefEditorService.updateAllExtRefIedNames(scd); // Then assertExtRefIsNotBound(findExtRef(scd, "IED_NAME1", "LD_INST12", "ExtRef target LDevice status is off")); assertExtRefIsNotBound(findExtRef(scd, "IED_NAME1", "LD_INST11", "Match compas:Flow but FlowStatus is INACTIVE")); @@ -169,7 +168,7 @@ void updateAllExtRefIedNames_when_lDevice_off_should_remove_binding() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-iedname/scd_set_extref_iedname_with_extref_errors.xml"); // When - List sclReportItems = extRefService.updateAllExtRefIedNames(scd); + List sclReportItems = extRefEditorService.updateAllExtRefIedNames(scd); // Then assertThat(sclReportItems).isNotNull(); LDeviceAdapter lDeviceAdapter = findLDeviceByLdName(scd, "IED_NAME1LD_INST12"); @@ -183,7 +182,7 @@ void updateAllExtRefIedNames_when_FlowStatus_INACTIVE_should_remove_binding() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-iedname/scd_set_extref_iedname_with_extref_errors.xml"); // When - List sclReportItems = extRefService.updateAllExtRefIedNames(scd); + List sclReportItems = extRefEditorService.updateAllExtRefIedNames(scd); // Then assertThat(sclReportItems).isNotNull(); LDeviceAdapter lDeviceAdapter = findLDeviceByLdName(scd, "IED_NAME1LD_INST11"); @@ -221,7 +220,7 @@ void filterDuplicatedExtRefs_should_remove_duplicated_extrefs() { List tExtRefList = List.of(tExtRef, tExtRefLnClass, createExtRefExample("CB", TServiceType.GOOSE), createExtRefExample("CB", TServiceType.GOOSE)); // When - List result = extRefService.filterDuplicatedExtRefs(tExtRefList); + List result = filterDuplicatedExtRefs(tExtRefList); // Then assertThat(result).hasSizeLessThan(tExtRefList.size()) .hasSize(2); @@ -241,7 +240,7 @@ void filterDuplicatedExtRefs_should_not_remove_not_duplicated_extrefs() { List tExtRefList = List.of(tExtRefIedName, tExtRefLdInst, tExtRefLnInst, tExtRefPrefix, createExtRefExample("CB_1", TServiceType.GOOSE), createExtRefExample("CB_1", TServiceType.SMV)); // When - List result = extRefService.filterDuplicatedExtRefs(tExtRefList); + List result = filterDuplicatedExtRefs(tExtRefList); // Then assertThat(result).hasSameSizeAs(tExtRefList) .hasSize(6); @@ -273,7 +272,7 @@ void manageBindingForLDEPF_whenFlowKindIsInternalAndAllExtRefInSameBay_should_re channels.getChannel().add(channel); epf.setChannels(channels); // When - List sclReportItems = extRefService.manageBindingForLDEPF(scd, epf); + List sclReportItems = extRefEditorService.manageBindingForLDEPF(scd, epf); // Then assertThat(sclReportItems).isEmpty(); TExtRef extRef1 = findExtRef(scd, "IED_NAME1", "LDEPF", "DYN_LDEPF_DIGITAL CHANNEL 1_1_BOOLEEN_1_general_1"); @@ -342,7 +341,7 @@ void manageBindingForLDEPF_when_internalBindingMatchEPFChannel_should_update_suc channels.getChannel().add(channel); epf.setChannels(channels); // When - List sclReportItems = extRefService.manageBindingForLDEPF(scd, epf); + List sclReportItems = extRefEditorService.manageBindingForLDEPF(scd, epf); // Then assertThat(sclReportItems).isEmpty(); SclTestMarshaller.assertIsMarshallable(new SclRootAdapter(scd).getCurrentElem()); @@ -405,7 +404,7 @@ void manageBindingForLDEPF_when_manyIedSourceFound_should_return_reportMassages( channels.getChannel().add(channel); epf.setChannels(channels); // When - List sclReportItems = extRefService.manageBindingForLDEPF(scd, epf); + List sclReportItems = extRefEditorService.manageBindingForLDEPF(scd, epf); // Then assertThat(sclReportItems).hasSize(2) .extracting(SclReportItem::message) @@ -493,7 +492,7 @@ void manageBindingForLDEPF_when_extRefMatchFlowKindInternalOrExternal_should_upd channels.getChannel().add(analogueChannel10WithBayExternalBayScope); epf.setChannels(channels); // When - List sclReportItems = extRefService.manageBindingForLDEPF(scd, epf); + List sclReportItems = extRefEditorService.manageBindingForLDEPF(scd, epf); // Then assertThat(sclReportItems).isEmpty(); SclTestMarshaller.assertIsMarshallable(scd); @@ -542,6 +541,76 @@ private void assertExtRefIsBoundAccordingTOLDEPF(TExtRef extRef, TChannel settin assertThat(extRef.getDoName()).isEqualTo(setting.getDOName()); } + @ParameterizedTest(name = "{0}") + @MethodSource("provideExtRefInfoInvalid") + void updateExtRefBinders(String testCase, ExtRefInfo extRefInfo, String message) { + //Given + SCL scd = createSclRootAdapterWithIed("IED_NAME").getCurrentElem(); + //When + //Then + assertThatThrownBy(() -> extRefEditorService.updateExtRefBinders(scd, extRefInfo)) + .isInstanceOf(ScdException.class) + .hasMessage(message); + } + + private static Stream provideExtRefInfoInvalid() { + ExtRefInfo withBindingInfo = new ExtRefInfo(); + withBindingInfo.setBindingInfo(new ExtRefBindingInfo()); + ExtRefInfo withSignalInfo = new ExtRefInfo(); + withSignalInfo.setSignalInfo(new ExtRefSignalInfo()); + ExtRefInfo withUnknownLD = new ExtRefInfo(); + withUnknownLD.setHolderIEDName("IED_NAME"); + withUnknownLD.setHolderLDInst("Unknown LD"); + ExtRefBindingInfo extRefBindingInfo = new ExtRefBindingInfo(); + ExtRefSignalInfo extRefSignalInfo = new ExtRefSignalInfo(); + withUnknownLD.setBindingInfo(extRefBindingInfo); + withUnknownLD.setSignalInfo(extRefSignalInfo); + return Stream.of( + Arguments.of("Should throw exception when BindingInfo is null", withSignalInfo, "ExtRef Signal and/or Binding information are missing"), + Arguments.of("Should throw exception when SignalInfo is null", withBindingInfo, "ExtRef Signal and/or Binding information are missing"), + Arguments.of("Should throw exception when LD is not present in IED", withUnknownLD, "Unknown LDevice (Unknown LD) in IED (IED_NAME)") + ); + } + + @Test + void updateExtRefBinders_should_thowException_when_AbstractLnAdapterUpdateExtRefBinders_Throws_Exception() { + //Given + SCL scd = createIedsInScl("ANCR", "do1").getCurrentElem(); + ExtRefInfo extRefInfo = new ExtRefInfo(); + extRefInfo.setHolderIEDName(IED_NAME_1); + extRefInfo.setHolderLDInst(LD_SUIED); + extRefInfo.setHolderLnClass("LLN0"); + ExtRefBindingInfo extRefBindingInfo = new ExtRefBindingInfo(); + ExtRefSignalInfo extRefSignalInfo = new ExtRefSignalInfo(); + extRefInfo.setBindingInfo(extRefBindingInfo); + extRefInfo.setSignalInfo(extRefSignalInfo); + //When + //Then + assertThatThrownBy(() -> extRefEditorService.updateExtRefBinders(scd, extRefInfo)) + .isInstanceOf(ScdException.class) + .hasMessage("ExtRef mandatory binding data are missing"); + } + + @Test + void updateExtRefBinders_should_succed_when_AbstractLnAdapterUpdateExtRefBinders_succed() { + //Given + SCL scd = SclTestMarshaller.getSCLFromFile("/ied-test-schema-conf/ied_unit_test.xml"); + ExtRefInfo extRefInfo = DTO.createExtRefInfo(); + extRefInfo.setHolderIEDName("IED_NAME"); + extRefInfo.setHolderLDInst("LD_INS2"); + extRefInfo.setHolderLnClass("ANCR"); + extRefInfo.setHolderLnInst("1"); + extRefInfo.setHolderLnPrefix(null); + extRefInfo.getSignalInfo().setPDO("StrVal.sdo2"); + extRefInfo.getSignalInfo().setPDA("antRef.bda1.bda2.bda3"); + extRefInfo.getSignalInfo().setIntAddr("INT_ADDR2"); + extRefInfo.getSignalInfo().setDesc(null); + extRefInfo.getSignalInfo().setPServT(null); + //When + //Then + assertDoesNotThrow(() -> extRefEditorService.updateExtRefBinders(scd, extRefInfo)); + } + @Test @Tag("issue-321") void updateExtRefSource_whenSignalInfoNullOrInvalid_shouldThrowScdException() { @@ -553,12 +622,12 @@ void updateExtRefSource_whenSignalInfoNullOrInvalid_shouldThrowScdException() { extRefInfo.setHolderLnClass(TLLN0Enum.LLN_0.value()); assertThat(extRefInfo.getSignalInfo()).isNull(); //When Then - assertThatThrownBy(() -> extRefService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class); // signal = null + assertThatThrownBy(() -> extRefEditorService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class); // signal = null //Given extRefInfo.setSignalInfo(new ExtRefSignalInfo()); assertThat(extRefInfo.getSignalInfo()).isNotNull(); //When Then - assertThatThrownBy(() -> extRefService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class);// signal invalid + assertThatThrownBy(() -> extRefEditorService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class);// signal invalid } @Test @@ -578,13 +647,13 @@ void updateExtRefSource_whenBindingInfoNullOrInvalid_shouldThrowScdException() { extRefInfo.setSignalInfo(extRefSignalInfo); assertThat(extRefInfo.getBindingInfo()).isNull(); //When Then - assertThatThrownBy(() -> extRefService.updateExtRefSource(scd, extRefInfo)) + assertThatThrownBy(() -> extRefEditorService.updateExtRefSource(scd, extRefInfo)) .isInstanceOf(ScdException.class); // binding = null //Given extRefInfo.setBindingInfo(new ExtRefBindingInfo()); assertThat(extRefInfo.getBindingInfo()).isNotNull(); //When Then - assertThatThrownBy(() -> extRefService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class);// binding invalid + assertThatThrownBy(() -> extRefEditorService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class);// binding invalid } @Test @@ -608,7 +677,7 @@ void updateExtRefSource_whenBindingInternalByIedName_shouldThrowScdException() { extRefBindingInfo.setLnClass(TLLN0Enum.LLN_0.value()); extRefInfo.setBindingInfo(new ExtRefBindingInfo()); //When Then - assertThatThrownBy(() -> extRefService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class); // CB not allowed + assertThatThrownBy(() -> extRefEditorService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class); // CB not allowed } @Test @@ -633,7 +702,7 @@ void updateExtRefSource_whenBindingInternaByServiceType_shouldThrowScdException( extRefBindingInfo.setServiceType(TServiceType.POLL); extRefInfo.setBindingInfo(new ExtRefBindingInfo()); //When Then - assertThatThrownBy(() -> extRefService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class); // CB not allowed + assertThatThrownBy(() -> extRefEditorService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class); // CB not allowed } @Test @@ -660,12 +729,12 @@ void updateExtRefSource_whenSourceInfoNullOrInvalid_shouldThrowScdException() { assertThat(extRefInfo.getSourceInfo()).isNull(); //When Then - assertThatThrownBy(() -> extRefService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class); // signal = null + assertThatThrownBy(() -> extRefEditorService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class); // signal = null //Given extRefInfo.setSourceInfo(new ExtRefSourceInfo()); assertThat(extRefInfo.getSourceInfo()).isNotNull(); //When Then - assertThatThrownBy(() -> extRefService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class);// signal invalid + assertThatThrownBy(() -> extRefEditorService.updateExtRefSource(scd, extRefInfo)).isInstanceOf(ScdException.class);// signal invalid } @Test @@ -696,11 +765,68 @@ void updateExtRefSource_whenBindingExternalBinding_shouldThrowScdException() { extRefInfo.setSourceInfo(sourceInfo); //When - TExtRef extRef = assertDoesNotThrow(() -> extRefService.updateExtRefSource(scd, extRefInfo)); + TExtRef extRef = assertDoesNotThrow(() -> extRefEditorService.updateExtRefSource(scd, extRefInfo)); //Then assertThat(extRef.getSrcCBName()).isEqualTo(extRefInfo.getSourceInfo().getSrcCBName()); assertThat(extRef.getSrcLDInst()).isEqualTo(extRefInfo.getBindingInfo().getLdInst()); assertThat(extRef.getSrcLNClass()).contains(extRefInfo.getBindingInfo().getLnClass()); } + @ParameterizedTest(name = "{0}") + @MethodSource("provideFlowAndExtRefForDebinding") + void debindCompasFlowsAndExtRefsBasedOnVoltageLevel(String testCase, SCL scd, Tuple extRef1, Tuple flow1, Tuple extRef2, Tuple flow2) { + //Given + //Then + extRefEditorService.debindCompasFlowsAndExtRefsBasedOnVoltageLevel(scd); + //When + TInputs tInputs = findInputs(scd); + assertThat(tInputs.getExtRef().stream().filter(tExtRef -> tExtRef.getDesc().equals("Desc_1"))) + .extracting(TExtRef::getIedName, TExtRef::getLdInst) + .containsExactly(extRef1); + assertThat(PrivateUtils.getPrivateStream(tInputs.getPrivate(), TCompasFlow.class).filter(tCompasFlow -> tCompasFlow.getDataStreamKey().equals("Desc_1"))) + .extracting(TCompasFlow::getExtRefiedName, TCompasFlow::getExtRefldinst, TCompasFlow::getExtReflnClass, TCompasFlow::getExtReflnInst) + .containsExactly(flow1); + assertThat(tInputs.getExtRef().stream().filter(tExtRef -> tExtRef.getDesc().equals("Desc_2"))) + .extracting(TExtRef::getIedName, TExtRef::getLdInst) + .containsExactly(extRef2); + assertThat(PrivateUtils.getPrivateStream(tInputs.getPrivate(), TCompasFlow.class).filter(tCompasFlow -> tCompasFlow.getDataStreamKey().equals("Desc_2"))) + .extracting(TCompasFlow::getExtRefiedName, TCompasFlow::getExtRefldinst, TCompasFlow::getExtReflnClass, TCompasFlow::getExtReflnInst) + .containsExactly(flow2); + } + + private static Stream provideFlowAndExtRefForDebinding(){ + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-flow-debind/scd_extref_flow_debind_success.xml"); + SCL scdVoltageLevel0 = SclTestMarshaller.getSCLFromFile("/scd-extref-flow-debind/scd_extref_flow_debind_volatagelevelname_0.xml"); + SCL scdVoltageLevelUnknown = SclTestMarshaller.getSCLFromFile("/scd-extref-flow-debind/scd_extref_flow_debind_volatagelevelname_unknown.xml"); + SCL scdUnsetExtRefIedName = SclTestMarshaller.getSCLFromFile("/scd-extref-flow-debind/scd_extref_flow_not_debind.xml"); + SCL scdVLevelUnknownUnsetFlowSourceVoltageLevel = SclTestMarshaller.getSCLFromFile("/scd-extref-flow-debind/scd_extref_flow_not_debind_volatagelevelname_unknown.xml"); + Tuple tupleExtRef1 = Tuple.tuple("System_Version_IED_NAME1", "LD_INST11"); + Tuple tupleExtRef2 = Tuple.tuple("System_Version_IED_NAME2", "LD_INST21"); + Tuple tupleExtRefDebind = Tuple.tuple(null, null); + Tuple tupleFlow1 = Tuple.tuple("System_Version_IED_NAME1", "LD_INST11", "LLN0", null); + Tuple tupleFlow2 = Tuple.tuple("System_Version_IED_NAME2", "LD_INST21", "ANCR", "1"); + Tuple tupleFlowNoExtRefIedName = Tuple.tuple(null, "LD_INST21", "ANCR", "1"); + Tuple tupleFlowDebind = Tuple.tuple(null, null, null, null); + + return Stream.of( + Arguments.of("case known voltageLevel should debind THT flow and corresponding ExtRef", scd, tupleExtRef1, tupleFlow1, tupleExtRefDebind, tupleFlowDebind), + Arguments.of("case voltageLevel 0 should do nothing", scdVoltageLevel0, tupleExtRef1, tupleFlow1, tupleExtRef2, tupleFlow2), + Arguments.of("case unknown voltageLevel should debind all", scdVoltageLevelUnknown, tupleExtRef1, tupleFlowDebind, tupleExtRef2, tupleFlowDebind), + Arguments.of("case known voltageLevel should not debind because no ExtRefIedName", scdUnsetExtRefIedName, tupleExtRef1, tupleFlow1, tupleExtRef2, tupleFlowNoExtRefIedName), + Arguments.of("case unknown voltageLevel should not debind because unset FlowSourceVoltageLevel", scdVLevelUnknownUnsetFlowSourceVoltageLevel, tupleExtRef1, tupleFlow1, tupleExtRef2, tupleFlow2) + ); + + } + + + private TInputs findInputs(SCL scd) { + IedService iedService = new IedService(); + LdeviceService ldeviceService = new LdeviceService(); + return iedService.findIed(scd, tied -> tied.getName().equals("IED_NAME1")) + .flatMap(tied -> ldeviceService.findLdevice(tied, tlDevice -> tlDevice.getInst().equals("LD_INST11"))) + .map(tlDevice -> tlDevice.getLN0().getInputs()) + .orElseThrow(); + + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java new file mode 100644 index 000000000..d0812915b --- /dev/null +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java @@ -0,0 +1,161 @@ +/* + * // SPDX-FileCopyrightText: 2023 RTE FRANCE + * // + * // SPDX-License-Identifier: Apache-2.0 + */ + +package org.lfenergy.compas.sct.commons.scl; + +import org.assertj.core.groups.Tuple; +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.*; +import org.lfenergy.compas.sct.commons.util.PrivateEnum; + +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + + +class ExtRefServiceTest { + + private static final ObjectFactory objectFactory = new ObjectFactory(); + ExtRefService extRefService = new ExtRefService(); + + @ParameterizedTest(name = "{0}") + @MethodSource("provideTAnyLns") + void getExtRefs(String testCase, TInputs tInputs, int size) { + //Given + //When + //Then + assertThat(extRefService.getExtRefs(tInputs)).hasSize(size); + } + + private static Stream provideTAnyLns() { + TInputs tInputsEmpty = new TInputs(); + TInputs tInputs = new TInputs(); + TExtRef tExtRef1 = new TExtRef(); + TExtRef tExtRef2 = new TExtRef(); + tInputs.getExtRef().add(tExtRef1); + tInputs.getExtRef().add(tExtRef2); + + return Stream.of( + Arguments.of("Ln without Inputs node should return empty stream", null, 0), + Arguments.of("Ln with empty Inputs should return empty stream", tInputsEmpty, 0), + Arguments.of("Ln0 with Inputs node should return stream 2 extrefs", tInputs, 2)); + } + + @Test + void getMatchingCompasFlows() { + //Given + TInputs tInputs = new TInputs(); + TExtRef tExtRef1 = createExtRef("Desc_1", "IED_Name_1", "LD_INST_1"); + tInputs.getExtRef().add(tExtRef1); + TCompasFlow tCompasFlow1 = createCompasFlow("Desc_1", "IED_Name_1", "LD_INST_1"); + TCompasFlow tCompasFlow2 = createCompasFlow("Desc_2", "IED_Name_2", "LD_INST_2"); + tInputs.getPrivate().add(createPrivateCompasFlow(List.of(tCompasFlow1, tCompasFlow2))); + //When + Stream matchingCompasFlows = extRefService.getMatchingCompasFlows(tInputs, tExtRef1); + //Then + assertThat(matchingCompasFlows).hasSize(1) + .map(TCompasFlow::getDataStreamKey, TCompasFlow::getExtRefiedName) + .containsExactly(Tuple.tuple("Desc_1", "IED_Name_1")); + } + + @Test + void getMatchingTextRef() { + //Given + TLN tln = new TLN(); + TInputs tInputs = new TInputs(); + TExtRef tExtRef1 = createExtRef("Desc_1", "IED_Name_1", "LD_INST_1"); + TExtRef tExtRef2 = createExtRef("Desc_2", "IED_Name_2", "LD_INST_2"); + tInputs.getExtRef().add(tExtRef1); + tInputs.getExtRef().add(tExtRef2); + TCompasFlow tCompasFlow = createCompasFlow("Desc_1", "IED_Name_1", "LD_INST_1"); + tln.setInputs(tInputs); + //When + Stream tExtRefStream = extRefService.getMatchingExtRef(tInputs, tCompasFlow); + //Then + assertThat(tExtRefStream).hasSize(1) + .map(TExtRef::getIedName, TExtRef::getLdInst, TExtRef::getDesc) + .containsExactly(Tuple.tuple("IED_Name_1", "LD_INST_1", "Desc_1")); + } + + @Test + void getMatchingTextRef_success_when_lnclass_null() { + //Given + TLN tln = new TLN(); + TInputs tInputs = new TInputs(); + TExtRef tExtRef1 = createExtRef("Desc_1", "IED_Name_1", "LD_INST_1"); + tExtRef1.getLnClass().clear(); + TExtRef tExtRef2 = createExtRef("Desc_2", "IED_Name_2", "LD_INST_2"); + tInputs.getExtRef().add(tExtRef1); + tInputs.getExtRef().add(tExtRef2); + TCompasFlow tCompasFlow = createCompasFlow("Desc_1", "IED_Name_1", "LD_INST_1"); + tCompasFlow.setExtReflnClass(null); + tln.setInputs(tInputs); + //When + Stream tExtRefStream = extRefService.getMatchingExtRef(tInputs, tCompasFlow); + //Then + assertThat(tExtRefStream).hasSize(1) + .map(TExtRef::getIedName, TExtRef::getLdInst, TExtRef::getDesc) + .containsExactly(Tuple.tuple("IED_Name_1", "LD_INST_1", "Desc_1")); + } + + @Test + void clearBinding() { + //Given + TExtRef tExtRef = createExtRef("Desc_1", "IED_Name_1", "LD_INST_1"); + //When + extRefService.clearExtRefBinding(tExtRef); + //Then + assertThat(tExtRef) + .extracting(TExtRef::getIedName, TExtRef::getLdInst, TExtRef::getLnInst) + .containsOnlyNulls(); + assertThat(tExtRef.getDesc()).isEqualTo("Desc_1"); + } + + @Test + void clearCompasFlowBinding() { + //Given + TCompasFlow compasFlow = createCompasFlow("Desc_1", "IED_Name_1", "LD_INST_1"); + //When + extRefService.clearCompasFlowBinding(compasFlow); + //Then + assertThat(compasFlow) + .extracting(TCompasFlow::getExtRefiedName, TCompasFlow::getExtRefldinst, TCompasFlow::getExtReflnClass, TCompasFlow::getExtReflnInst, TCompasFlow::getExtRefprefix) + .containsOnlyNulls(); + assertThat(compasFlow.getDataStreamKey()).isEqualTo("Desc_1"); + } + + private static TPrivate createPrivateCompasFlow(List compasFlows) { + TPrivate tPrivate = new TPrivate(); + tPrivate.setType(PrivateEnum.COMPAS_FLOW.getPrivateType()); + tPrivate.getContent().addAll(compasFlows.stream().map(objectFactory::createFlow).toList()); + return tPrivate; + } + + private TExtRef createExtRef(String desc, String iedName, String ldInst) { + TExtRef tExtRef1 = new TExtRef(); + tExtRef1.setDesc(desc); + tExtRef1.setIedName(iedName); + tExtRef1.setLdInst(ldInst); + tExtRef1.getLnClass().add("LN"); + tExtRef1.setLnInst("1"); + return tExtRef1; + } + + private TCompasFlow createCompasFlow(String dataStreamKey, String extRefIedName, String extRefLdInst) { + TCompasFlow tCompasFlow = new TCompasFlow(); + tCompasFlow.setDataStreamKey(dataStreamKey); + tCompasFlow.setExtRefiedName(extRefIedName); + tCompasFlow.setExtRefldinst(extRefLdInst); + tCompasFlow.setExtReflnClass("LN"); + tCompasFlow.setExtReflnInst("1"); + return tCompasFlow; + } + +} \ No newline at end of file diff --git a/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_success.xml b/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_success.xml new file mode 100644 index 000000000..df00a06ea --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_success.xml @@ -0,0 +1,101 @@ + + + + + + +
+ + + 90 + + + + + + + + + + + + + + + + on + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_volatagelevelname_0.xml b/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_volatagelevelname_0.xml new file mode 100644 index 000000000..e55aa6aef --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_volatagelevelname_0.xml @@ -0,0 +1,76 @@ + + + + + + +
+ + + 90 + + + + + + + + + + + + + + + + on + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_volatagelevelname_unknown.xml b/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_volatagelevelname_unknown.xml new file mode 100644 index 000000000..ff48290d5 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_debind_volatagelevelname_unknown.xml @@ -0,0 +1,76 @@ + + + + + + +
+ + + 90 + + + + + + + + + + + + + + + + on + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_not_debind.xml b/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_not_debind.xml new file mode 100644 index 000000000..07c30b052 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_not_debind.xml @@ -0,0 +1,101 @@ + + + + + + +
+ + + 90 + + + + + + + + + + + + + + + + on + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_not_debind_volatagelevelname_unknown.xml b/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_not_debind_volatagelevelname_unknown.xml new file mode 100644 index 000000000..44eb48233 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-flow-debind/scd_extref_flow_not_debind_volatagelevelname_unknown.xml @@ -0,0 +1,76 @@ + + + + + + +
+ + + 90 + + + + + + + + + + + + + + + + on + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + From 4cdaf2047eb50af9f1c3dad3741bd6597f938447 Mon Sep 17 00:00:00 2001 From: Aliou DIAITE Date: Fri, 15 Dec 2023 16:32:20 +0100 Subject: [PATCH 5/9] feat(#352): remove compasflow and extref bindings patch for LLN0 without inputs node Signed-off-by: Aliou DIAITE --- .../org/lfenergy/compas/sct/commons/ExtRefEditorService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefEditorService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefEditorService.java index 5773c8b2d..d99f14fb9 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefEditorService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefEditorService.java @@ -423,6 +423,8 @@ public void debindCompasFlowsAndExtRefsBasedOnVoltageLevel(SCL scd) { .filter(tVoltageLevelName -> !"0".equals(tVoltageLevelName)) .forEach(tVoltageLevelName -> scd.getIED().stream() .flatMap(ldeviceService::getLdevices) + .filter(TLDevice::isSetLN0) + .filter(tlDevice -> tlDevice.getLN0().isSetInputs()) .forEach(tlDevice -> { String flowSource = voltageCodification.get(tVoltageLevelName); TInputs tInputs = tlDevice.getLN0().getInputs(); From ca976d0ecb2353203b1ce417372738bca84a4f95 Mon Sep 17 00:00:00 2001 From: massifben <105049157+massifben@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:10:06 +0100 Subject: [PATCH 6/9] feat(#351): RSR-894 Update Compasflow and extref prebindings based on LNode Signed-off-by: massifben <105049157+massifben@users.noreply.github.com> --- .../sct/commons/ExtRefEditorService.java | 77 +++++-- .../compas/sct/commons/api/ExtRefEditor.java | 5 + .../compas/sct/commons/scl/ExtRefService.java | 77 +++++-- .../commons/scl/ied/AccessPointAdapter.java | 5 +- .../sct/commons/scl/ied/InputsAdapter.java | 9 +- .../compas/sct/commons/util/Utils.java | 24 --- .../sct/commons/ExtRefEditorServiceTest.java | 67 +++--- .../sct/commons/scl/ExtRefServiceTest.java | 201 ++++++++++++++---- .../sct/commons/testhelpers/SclHelper.java | 10 +- .../compas/sct/commons/util/UtilsTest.java | 54 ----- ..._extref_iedname_based_on_lnode_success.xml | 93 ++++++++ 11 files changed, 420 insertions(+), 202 deletions(-) create mode 100644 sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_based_on_lnode_success.xml diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefEditorService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefEditorService.java index 5773c8b2d..a727c218f 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefEditorService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ExtRefEditorService.java @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: 2022 RTE FRANCE +// SPDX-FileCopyrightText: 2023 RTE FRANCE // // SPDX-License-Identifier: Apache-2.0 package org.lfenergy.compas.sct.commons; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.api.ExtRefEditor; import org.lfenergy.compas.sct.commons.dto.*; @@ -24,13 +25,13 @@ import org.lfenergy.compas.sct.commons.util.PrivateUtils; import org.lfenergy.compas.sct.commons.util.Utils; +import java.math.BigInteger; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.*; import static org.lfenergy.compas.sct.commons.util.CommonConstants.*; -import static org.lfenergy.compas.sct.commons.util.Utils.isExtRefFeedBySameControlBlock; @RequiredArgsConstructor public class ExtRefEditorService implements ExtRefEditor { @@ -42,21 +43,9 @@ public class ExtRefEditorService implements ExtRefEditor { "6", "THT", "7", "THT" ); - private final ExtRefService extRefService; - /** - * Remove ExtRef which are fed by same Control Block - * - * @return list ExtRefs without duplication - */ - public static List filterDuplicatedExtRefs(List tExtRefs) { - List filteredList = new ArrayList<>(); - tExtRefs.forEach(tExtRef -> { - if (filteredList.stream().noneMatch(t -> isExtRefFeedBySameControlBlock(tExtRef, t))) - filteredList.add(tExtRef); - }); - return filteredList; - } + private final LdeviceService ldeviceService; + private final ExtRefService extRefService; /** * Provides valid IED sources according to EPF configuration.
@@ -425,8 +414,7 @@ public void debindCompasFlowsAndExtRefsBasedOnVoltageLevel(SCL scd) { .flatMap(ldeviceService::getLdevices) .forEach(tlDevice -> { String flowSource = voltageCodification.get(tVoltageLevelName); - TInputs tInputs = tlDevice.getLN0().getInputs(); - PrivateUtils.getPrivateStream(tInputs.getPrivate(), TCompasFlow.class) + extRefService.getCompasFlows(tlDevice) .filter(TCompasFlow::isSetFlowSourceVoltageLevel) .filter(TCompasFlow::isSetExtRefiedName) .forEach(tCompasFlow -> { @@ -435,19 +423,66 @@ public void debindCompasFlowsAndExtRefsBasedOnVoltageLevel(SCL scd) { extRefService.clearCompasFlowBinding(tCompasFlow); } else if (!tCompasFlow.getFlowSourceVoltageLevel().equals(flowSource)) { //debind extRef - extRefService.getMatchingExtRef(tInputs, tCompasFlow) + extRefService.getMatchingExtRefs(tlDevice, tCompasFlow) .forEach(extRefService::clearExtRefBinding); //debind compas flow extRefService.clearCompasFlowBinding(tCompasFlow); - } }); }) ); } - private record DoNameAndDaName(String doName, String daName) { + + @Override + public void updateIedNameBasedOnLnode(SCL scl) { + Map bayByTopoKey = scl.getSubstation().stream() + .flatMap(tSubstation -> tSubstation.getVoltageLevel().stream()) + .flatMap(tVoltageLevel -> tVoltageLevel.getBay().stream()) + .map(tBay -> PrivateUtils.extractCompasPrivate(tBay, TCompasTopo.class) + .filter(tCompasTopo -> isNotBlank(tCompasTopo.getNode()) && Objects.nonNull(tCompasTopo.getNodeOrder())) + .map(tCompasTopo -> new BayTopoKey(tBay, new TopoKey(tCompasTopo.getNode(), tCompasTopo.getNodeOrder()))) + ) + .flatMap(Optional::stream) + .collect(Collectors.toMap(BayTopoKey::topoKey, BayTopoKey::bay)); + + scl.getIED().stream() + .flatMap(ldeviceService::getLdevices) + .forEach(tlDevice -> + extRefService.getCompasFlows(tlDevice) + .filter(tCompasFlow -> Objects.nonNull(tCompasFlow.getFlowSourceBayNode()) && Objects.nonNull(tCompasFlow.getFlowSourceBayNodeOrder())) + .forEach(tCompasFlow -> + Optional.ofNullable(bayByTopoKey.get(new TopoKey(tCompasFlow.getFlowSourceBayNode().toString(), tCompasFlow.getFlowSourceBayNodeOrder()))) + .flatMap(tBay -> tBay.getFunction().stream() + .flatMap(tFunction -> tFunction.getLNode().stream()) + .filter(tlNode -> Objects.equals(tlNode.getLdInst(), tCompasFlow.getExtRefldinst()) + && Objects.equals(tlNode.getLnInst(), tCompasFlow.getExtReflnInst()) + && Utils.lnClassEquals(tlNode.getLnClass(), tCompasFlow.getExtReflnClass()) + && Objects.equals(tlNode.getPrefix(), tCompasFlow.getExtRefprefix())) + .map(TLNode::getIedName) + .filter(StringUtils::isNotBlank) + .findFirst() + ) + .ifPresentOrElse(iedName -> { + extRefService.getMatchingExtRefs(tlDevice, tCompasFlow).forEach(tExtRef -> tExtRef.setIedName(iedName)); + tCompasFlow.setExtRefiedName(iedName); + }, + () -> { + extRefService.getMatchingExtRefs(tlDevice, tCompasFlow).forEach(extRefService::clearExtRefBinding); + extRefService.clearCompasFlowBinding(tCompasFlow); + } + ) + ) + ); + } + + record TopoKey(String FlowNode, BigInteger FlowNodeOrder) { } + record BayTopoKey(TBay bay, TopoKey topoKey) { + } + + private record DoNameAndDaName(String doName, String daName) { + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ExtRefEditor.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ExtRefEditor.java index 4b656468f..6c2da6d7c 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ExtRefEditor.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ExtRefEditor.java @@ -70,4 +70,9 @@ public interface ExtRefEditor { */ void debindCompasFlowsAndExtRefsBasedOnVoltageLevel(SCL scd); + /** + * Update compas:Flow.ExtRefiedName and ExtRef.iedName, based on Substation LNode iedName + */ + void updateIedNameBasedOnLnode(SCL scd); + } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java index 09da0767f..8040d4917 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java @@ -6,26 +6,38 @@ package org.lfenergy.compas.sct.commons.scl; -import org.lfenergy.compas.scl2007b4.model.TCompasFlow; -import org.lfenergy.compas.scl2007b4.model.TExtRef; -import org.lfenergy.compas.scl2007b4.model.TInputs; +import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.util.PrivateUtils; import org.lfenergy.compas.sct.commons.util.Utils; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.stream.Stream; public class ExtRefService { /** - * List all ExtRefs in this Inputs + * List all ExtRefs in this LDevice * * @return list of ExtRefs. List is modifiable. */ - public Stream getExtRefs(TInputs inputs) { - if (inputs == null || !inputs.isSetExtRef()) { - return Stream.empty(); - } - return inputs.getExtRef().stream(); + public Stream getExtRefs(TLDevice tlDevice) { + return getInputs(tlDevice) + .filter(TInputs::isSetExtRef) + .stream() + .flatMap(tInputs -> tInputs.getExtRef().stream()); + } + + /** + * List all CompasFlows in this LDevice + * + * @return list of ExtRefs. List is modifiable. + */ + public Stream getCompasFlows(TLDevice tlDevice) { + return getInputs(tlDevice).stream() + .flatMap(tInputs -> PrivateUtils.extractCompasPrivates(tlDevice.getLN0().getInputs(), TCompasFlow.class)); } /** @@ -43,12 +55,12 @@ public Stream getMatchingCompasFlows(TInputs inputs, TExtRef tExtRe /** * Retrieves ExtRefs corresponding to given CompasFlow * - * @param inputs node containing CompasFlows and ExtRefs + * @param tlDevice LDevice containing CompasFlows and ExtRefs * @param tCompasFlow corresponding to Extrefs we are searching * @return stream of matching ExtRefs */ - public Stream getMatchingExtRef(TInputs inputs, TCompasFlow tCompasFlow) { - return getExtRefs(inputs) + public Stream getMatchingExtRefs(TLDevice tlDevice, TCompasFlow tCompasFlow) { + return getExtRefs(tlDevice) .filter(tExtRef -> isMatchingExtRef(tCompasFlow, tExtRef)); } @@ -103,4 +115,45 @@ private boolean isMatchingExtRef(TCompasFlow compasFlow, TExtRef extRef) { && Utils.equalsOrBothBlank(compasFlow.getExtReflnClass(), extRefLnClass) && Utils.equalsOrBothBlank(compasFlow.getExtReflnInst(), extRef.getLnInst()); } + + /** + * Checks if two ExtRefs fed by same Control Block + * + * @param t1 extref to compare + * @param t2 extref to compare + * @return true if the two ExtRef are fed by same Control Block, otherwise false + */ + public boolean isExtRefFeedBySameControlBlock(TExtRef t1, TExtRef t2) { + String srcLNClass1 = (t1.isSetSrcLNClass()) ? t1.getSrcLNClass().get(0) : TLLN0Enum.LLN_0.value(); + String srcLNClass2 = (t2.isSetSrcLNClass()) ? t2.getSrcLNClass().get(0) : TLLN0Enum.LLN_0.value(); + return Utils.equalsOrBothBlank(t1.getIedName(), t2.getIedName()) + && Utils.equalsOrBothBlank(t1.getSrcLDInst(), t2.getSrcLDInst()) + && srcLNClass1.equals(srcLNClass2) + && Utils.equalsOrBothBlank(t1.getSrcLNInst(), t2.getSrcLNInst()) + && Utils.equalsOrBothBlank(t1.getSrcPrefix(), t2.getSrcPrefix()) + && Utils.equalsOrBothBlank(t1.getSrcCBName(), t2.getSrcCBName()) + && Objects.equals(t1.getServiceType(), t2.getServiceType()); + } + + /** + * Remove ExtRef which are fed by same Control Block + * + * @return list ExtRefs without duplication + */ + public List filterDuplicatedExtRefs(List tExtRefs) { + List filteredList = new ArrayList<>(); + tExtRefs.forEach(tExtRef -> { + if (filteredList.stream().noneMatch(t -> isExtRefFeedBySameControlBlock(tExtRef, t))) + filteredList.add(tExtRef); + }); + return filteredList; + } + + private Optional getInputs(TLDevice tlDevice){ + if (!tlDevice.isSetLN0() || !tlDevice.getLN0().isSetInputs()) { + return Optional.empty(); + } + return Optional.of(tlDevice.getLN0().getInputs()); + } + } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java index afa9958c8..b2f38cbd5 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java @@ -9,6 +9,7 @@ import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.dto.SclReportItem; import org.lfenergy.compas.sct.commons.exception.ScdException; +import org.lfenergy.compas.sct.commons.scl.ExtRefService; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.scl.ldevice.LDeviceAdapter; import org.lfenergy.compas.sct.commons.scl.ln.AbstractLNAdapter; @@ -19,8 +20,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.lfenergy.compas.sct.commons.ExtRefEditorService.filterDuplicatedExtRefs; - /** * A representation of the model object * {@link org.lfenergy.compas.scl2007b4.model.TAccessPoint AccessPoint}. @@ -245,7 +244,7 @@ public ExtRefAnalyzeRecord getAllCoherentExtRefForAnalyze() { return extRefs; }).flatMap(Collection::stream) .toList(); - return new ExtRefAnalyzeRecord(sclReportItems, filterDuplicatedExtRefs(tExtRefList)); + return new ExtRefAnalyzeRecord(sclReportItems, new ExtRefService().filterDuplicatedExtRefs(tExtRefList)); } /** diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapter.java index b25094698..10a49ae39 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapter.java @@ -8,7 +8,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.lfenergy.compas.scl2007b4.model.*; -import org.lfenergy.compas.sct.commons.ExtRefEditorService; import org.lfenergy.compas.sct.commons.dto.DataAttributeRef; import org.lfenergy.compas.sct.commons.dto.FcdaForDataSetsCreation; import org.lfenergy.compas.sct.commons.dto.SclReportItem; @@ -104,13 +103,13 @@ public List updateAllExtRefIedNames(Map icdSy try { ActiveStatus lDeviceStatus = ActiveStatus.fromValue(optionalLDeviceStatus.get()); return switch (lDeviceStatus) { - case ON -> extRefService.getExtRefs(currentElem) + case ON -> currentElem.getExtRef().stream() .filter(tExtRef -> StringUtils.isNotBlank(tExtRef.getIedName()) && StringUtils.isNotBlank(tExtRef.getDesc())) .map(extRef -> updateExtRefIedName(extRef, icdSystemVersionToIed.get(extRef.getIedName()))) .flatMap(Optional::stream) .toList(); case OFF -> { - extRefService.getExtRefs(currentElem).forEach(extRefService::clearExtRefBinding); + currentElem.getExtRef().forEach(extRefService::clearExtRefBinding); yield Collections.emptyList(); } }; @@ -209,7 +208,7 @@ public List updateAllSourceDataSetsAndControlBlocks(Set filterDuplicatedExtRefs() { - return ExtRefEditorService.filterDuplicatedExtRefs(extRefService.getExtRefs(currentElem).toList()); + return new ExtRefService().filterDuplicatedExtRefs(currentElem.getExtRef()); } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java index b5d155e69..ddbfdfb98 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java @@ -10,12 +10,7 @@ import jakarta.xml.bind.Unmarshaller; import jakarta.xml.bind.util.JAXBSource; import org.apache.commons.lang3.StringUtils; -import org.lfenergy.compas.scl2007b4.model.TExtRef; -import org.lfenergy.compas.scl2007b4.model.TLLN0Enum; import org.lfenergy.compas.sct.commons.exception.ScdException; -import org.lfenergy.compas.sct.commons.scl.ln.AbstractLNAdapter; -import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; -import org.lfenergy.compas.sct.commons.scl.ldevice.LDeviceAdapter; import javax.xml.namespace.QName; import java.util.*; @@ -371,23 +366,4 @@ public static T copySclElement(T object, Class clazz) { } } - /** - * Checks if two ExtRefs fed by same Control Block - * - * @param t1 extref to compare - * @param t2 extref to compare - * @return true if the two ExtRef are fed by same Control Block, otherwise false - */ - public static boolean isExtRefFeedBySameControlBlock(TExtRef t1, TExtRef t2) { - String srcLNClass1 = (t1.isSetSrcLNClass()) ? t1.getSrcLNClass().get(0) : TLLN0Enum.LLN_0.value(); - String srcLNClass2 = (t2.isSetSrcLNClass()) ? t2.getSrcLNClass().get(0) : TLLN0Enum.LLN_0.value(); - return Utils.equalsOrBothBlank(t1.getIedName(), t2.getIedName()) - && Utils.equalsOrBothBlank(t1.getSrcLDInst(), t2.getSrcLDInst()) - && srcLNClass1.equals(srcLNClass2) - && Utils.equalsOrBothBlank(t1.getSrcLNInst(), t2.getSrcLNInst()) - && Utils.equalsOrBothBlank(t1.getSrcPrefix(), t2.getSrcPrefix()) - && Utils.equalsOrBothBlank(t1.getSrcCBName(), t2.getSrcCBName()) - && Objects.equals(t1.getServiceType(), t2.getServiceType()); - } - } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ExtRefEditorServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ExtRefEditorServiceTest.java index df0e36403..73b5b1747 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ExtRefEditorServiceTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ExtRefEditorServiceTest.java @@ -30,7 +30,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.lfenergy.compas.sct.commons.ExtRefEditorService.filterDuplicatedExtRefs; import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.*; import static org.lfenergy.compas.sct.commons.util.CommonConstants.*; @@ -40,7 +39,7 @@ class ExtRefEditorServiceTest { @BeforeEach void init() { - extRefEditorService = new ExtRefEditorService(new ExtRefService()); + extRefEditorService = new ExtRefEditorService(new LdeviceService(), new ExtRefService()); } @Test @@ -211,41 +210,6 @@ private void assertExtRefIsNotBound(TExtRef extRef) { assertThat(extRef.isSetSrcCBName()).isFalse(); } - @Test - void filterDuplicatedExtRefs_should_remove_duplicated_extrefs() { - // Given - TExtRef tExtRefLnClass = createExtRefExample("CB_Name1", TServiceType.GOOSE); - tExtRefLnClass.getSrcLNClass().add(TLLN0Enum.LLN_0.value()); - TExtRef tExtRef = createExtRefExample("CB_Name1", TServiceType.GOOSE); - List tExtRefList = List.of(tExtRef, tExtRefLnClass, createExtRefExample("CB", TServiceType.GOOSE), - createExtRefExample("CB", TServiceType.GOOSE)); - // When - List result = filterDuplicatedExtRefs(tExtRefList); - // Then - assertThat(result).hasSizeLessThan(tExtRefList.size()) - .hasSize(2); - } - - @Test - void filterDuplicatedExtRefs_should_not_remove_not_duplicated_extrefs() { - // Given - TExtRef tExtRefIedName = createExtRefExample("CB_1", TServiceType.GOOSE); - tExtRefIedName.setIedName("IED_XXX"); - TExtRef tExtRefLdInst = createExtRefExample("CB_1", TServiceType.GOOSE); - tExtRefLdInst.setSrcLDInst("LD_XXX"); - TExtRef tExtRefLnInst = createExtRefExample("CB_1", TServiceType.GOOSE); - tExtRefLnInst.setSrcLNInst("X"); - TExtRef tExtRefPrefix = createExtRefExample("CB_1", TServiceType.GOOSE); - tExtRefPrefix.setSrcPrefix("X"); - List tExtRefList = List.of(tExtRefIedName, tExtRefLdInst, tExtRefLnInst, tExtRefPrefix, - createExtRefExample("CB_1", TServiceType.GOOSE), createExtRefExample("CB_1", TServiceType.SMV)); - // When - List result = filterDuplicatedExtRefs(tExtRefList); - // Then - assertThat(result).hasSameSizeAs(tExtRefList) - .hasSize(6); - } - @Test void manageBindingForLDEPF_whenFlowKindIsInternalAndAllExtRefInSameBay_should_return_noReportAndExtRefUpdateSuccessfully() { //Given @@ -532,7 +496,6 @@ void manageBindingForLDEPF_when_extRefMatchFlowKindInternalOrExternal_should_upd assertThat(extRefBindExternally.getIedName()).isEqualTo("IED_NAME2"); assertExtRefIsBoundAccordingTOLDEPF(extRefBindExternally, analogueChannel10WithBayExternalBayScope); } - private void assertExtRefIsBoundAccordingTOLDEPF(TExtRef extRef, TChannel setting) { assertThat(extRef.getLdInst()).isEqualTo(setting.getLDInst()); assertThat(extRef.getLnClass()).contains(setting.getLNClass()); @@ -829,4 +792,32 @@ private TInputs findInputs(SCL scd) { } + @Test + void updateIedNameBasedOnLnode_should_update_CompasFlow_and_ExtRef_iedName(){ + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-iedname/scd_set_extref_iedname_based_on_lnode_success.xml"); + // When + extRefEditorService.updateIedNameBasedOnLnode(scd); + // Then + assertThat(findCompasFlow(scd, "IED_NAME1", "LD_INST11", "STAT_LDSUIED_LPDO 1 Sortie_13_BOOLEAN_18_stVal_1").getExtRefiedName()) + .isEqualTo("IED_NAME2"); + assertThat(findExtRef(scd, "IED_NAME1", "LD_INST11", "STAT_LDSUIED_LPDO 1 Sortie_13_BOOLEAN_18_stVal_1").getIedName()) + .isEqualTo("IED_NAME2"); + } + + @Test + void updateIedNameBasedOnLnode_when_no_matching_lnode_should_clear_binding(){ + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-iedname/scd_set_extref_iedname_based_on_lnode_success.xml"); + PrivateUtils.extractCompasPrivate(scd.getSubstation().get(0).getVoltageLevel().get(0).getBay().get(0), TCompasTopo.class).orElseThrow().setNode("99"); + // When + extRefEditorService.updateIedNameBasedOnLnode(scd); + // Then + TCompasFlow compasFlow = findCompasFlow(scd, "IED_NAME1", "LD_INST11", "STAT_LDSUIED_LPDO 1 Sortie_13_BOOLEAN_18_stVal_1"); + assertThat(compasFlow) + .extracting(TCompasFlow::getExtRefiedName, TCompasFlow::getExtRefldinst, TCompasFlow::getExtRefprefix, TCompasFlow::getExtReflnClass, TCompasFlow::getExtReflnInst) + .containsOnlyNulls(); + assertExtRefIsNotBound(findExtRef(scd, "IED_NAME1", "LD_INST11", "STAT_LDSUIED_LPDO 1 Sortie_13_BOOLEAN_18_stVal_1")); + + } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java index d0812915b..04c6057c6 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java @@ -7,6 +7,7 @@ package org.lfenergy.compas.sct.commons.scl; import org.assertj.core.groups.Tuple; +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; @@ -18,38 +19,74 @@ 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.createExtRefExample; class ExtRefServiceTest { - private static final ObjectFactory objectFactory = new ObjectFactory(); - ExtRefService extRefService = new ExtRefService(); + ExtRefService extRefService; - @ParameterizedTest(name = "{0}") - @MethodSource("provideTAnyLns") - void getExtRefs(String testCase, TInputs tInputs, int size) { - //Given - //When - //Then - assertThat(extRefService.getExtRefs(tInputs)).hasSize(size); + @BeforeEach + void setUp() { + extRefService = new ExtRefService(); } - private static Stream provideTAnyLns() { - TInputs tInputsEmpty = new TInputs(); + @Test + void getExtRefs_should_return_extRefs() { + //Given + TLDevice tlDevice = new TLDevice(); + tlDevice.setLN0(new LN0()); TInputs tInputs = new TInputs(); + tlDevice.getLN0().setInputs(tInputs); TExtRef tExtRef1 = new TExtRef(); TExtRef tExtRef2 = new TExtRef(); tInputs.getExtRef().add(tExtRef1); tInputs.getExtRef().add(tExtRef2); + //When + Stream result = extRefService.getExtRefs(tlDevice); + //Then + assertThat(result).hasSize(2); + } + + @ParameterizedTest + @MethodSource("provideLDevices") + void getExtRefs_should_return_empty_stream(TLDevice tlDevice) { + //Given : parameters + //When + Stream result = extRefService.getExtRefs(tlDevice); + //Then + assertThat(result).isEmpty(); + } + private static Stream provideLDevices() { + TLDevice tlDeviceWithoutLn0 = new TLDevice(); + tlDeviceWithoutLn0.setLN0(new LN0()); return Stream.of( - Arguments.of("Ln without Inputs node should return empty stream", null, 0), - Arguments.of("Ln with empty Inputs should return empty stream", tInputsEmpty, 0), - Arguments.of("Ln0 with Inputs node should return stream 2 extrefs", tInputs, 2)); + Arguments.of(named("LDevice without LN0 should return empty stream", tlDeviceWithoutLn0)), + Arguments.of(named("LDevice with empty Inputs should return empty stream", new TLDevice())) + ); + } + + @Test + void getCompasFlows_should_return_compasFlow() { + // Given + TLDevice tlDevice = new TLDevice(); + tlDevice.setLN0(new LN0()); + TInputs tInputs = new TInputs(); + tlDevice.getLN0().setInputs(tInputs); + TPrivate tPrivate = new TPrivate(); + tPrivate.setType(PrivateEnum.COMPAS_FLOW.getPrivateType()); + tPrivate.getContent().add(new ObjectFactory().createFlow(new TCompasFlow())); + tInputs.getPrivate().add(tPrivate); + // When + Stream result = extRefService.getCompasFlows(tlDevice); + // Then + assertThat(result).hasSize(1); } @Test - void getMatchingCompasFlows() { + void getMatchingCompasFlows_should_succeed() { //Given TInputs tInputs = new TInputs(); TExtRef tExtRef1 = createExtRef("Desc_1", "IED_Name_1", "LD_INST_1"); @@ -66,18 +103,19 @@ void getMatchingCompasFlows() { } @Test - void getMatchingTextRef() { + void getMatchingExtRefs_should_succeed() { //Given - TLN tln = new TLN(); + TLDevice tlDevice = new TLDevice(); + tlDevice.setLN0(new LN0()); TInputs tInputs = new TInputs(); TExtRef tExtRef1 = createExtRef("Desc_1", "IED_Name_1", "LD_INST_1"); TExtRef tExtRef2 = createExtRef("Desc_2", "IED_Name_2", "LD_INST_2"); tInputs.getExtRef().add(tExtRef1); tInputs.getExtRef().add(tExtRef2); TCompasFlow tCompasFlow = createCompasFlow("Desc_1", "IED_Name_1", "LD_INST_1"); - tln.setInputs(tInputs); + tlDevice.getLN0().setInputs(tInputs); //When - Stream tExtRefStream = extRefService.getMatchingExtRef(tInputs, tCompasFlow); + Stream tExtRefStream = extRefService.getMatchingExtRefs(tlDevice, tCompasFlow); //Then assertThat(tExtRefStream).hasSize(1) .map(TExtRef::getIedName, TExtRef::getLdInst, TExtRef::getDesc) @@ -85,28 +123,7 @@ void getMatchingTextRef() { } @Test - void getMatchingTextRef_success_when_lnclass_null() { - //Given - TLN tln = new TLN(); - TInputs tInputs = new TInputs(); - TExtRef tExtRef1 = createExtRef("Desc_1", "IED_Name_1", "LD_INST_1"); - tExtRef1.getLnClass().clear(); - TExtRef tExtRef2 = createExtRef("Desc_2", "IED_Name_2", "LD_INST_2"); - tInputs.getExtRef().add(tExtRef1); - tInputs.getExtRef().add(tExtRef2); - TCompasFlow tCompasFlow = createCompasFlow("Desc_1", "IED_Name_1", "LD_INST_1"); - tCompasFlow.setExtReflnClass(null); - tln.setInputs(tInputs); - //When - Stream tExtRefStream = extRefService.getMatchingExtRef(tInputs, tCompasFlow); - //Then - assertThat(tExtRefStream).hasSize(1) - .map(TExtRef::getIedName, TExtRef::getLdInst, TExtRef::getDesc) - .containsExactly(Tuple.tuple("IED_Name_1", "LD_INST_1", "Desc_1")); - } - - @Test - void clearBinding() { + void clearExtRefBinding_should_remove_binding() { //Given TExtRef tExtRef = createExtRef("Desc_1", "IED_Name_1", "LD_INST_1"); //When @@ -119,7 +136,7 @@ void clearBinding() { } @Test - void clearCompasFlowBinding() { + void clearCompasFlowBinding_should_remove_binding() { //Given TCompasFlow compasFlow = createCompasFlow("Desc_1", "IED_Name_1", "LD_INST_1"); //When @@ -131,10 +148,106 @@ void clearCompasFlowBinding() { assertThat(compasFlow.getDataStreamKey()).isEqualTo("Desc_1"); } + @ParameterizedTest + @MethodSource("provideExtRefsFedBySameControlBlock") + void isExtRefFeedBySameControlBlock_should_return_true(TExtRef tExtRef1, TExtRef tExtRef2) { + // Given : parameter + // When + boolean result = extRefService.isExtRefFeedBySameControlBlock(tExtRef1, tExtRef2); + // Then + assertThat(result).isTrue(); + } + + private static Stream provideExtRefsFedBySameControlBlock() { + TExtRef tExtRefLnClass = createExtRefExample("CB_1", TServiceType.GOOSE); + tExtRefLnClass.getSrcLNClass().add(TLLN0Enum.LLN_0.value()); + + return Stream.of( + Arguments.of(createExtRefExample("CB_1", TServiceType.GOOSE), createExtRefExample("CB_1", TServiceType.GOOSE)), + Arguments.of(tExtRefLnClass, createExtRefExample("CB_1", TServiceType.GOOSE)), + Arguments.of(createExtRefExample("CB_1", TServiceType.GOOSE), tExtRefLnClass) + ); + } + + @ParameterizedTest + @MethodSource("provideExtRefsToCompare") + void isExtRefFeedBySameControlBlock_should_return_false(TExtRef tExtRef1, TExtRef tExtRef2) { + // Given : parameters + // When + boolean result = extRefService.isExtRefFeedBySameControlBlock(tExtRef1, tExtRef2); + // Then + assertThat(result).isFalse(); + } + + private static Stream provideExtRefsToCompare() { + TExtRef tExtRefLnClass = createExtRefExample("CB_1", TServiceType.GOOSE); + tExtRefLnClass.getSrcLNClass().add("XXX"); + TExtRef tExtRefIedName = createExtRefExample("CB_1", TServiceType.GOOSE); + tExtRefIedName.setIedName("IED_XXX"); + TExtRef tExtRefLdInst = createExtRefExample("CB_1", TServiceType.GOOSE); + tExtRefLdInst.setSrcLDInst("LD_XXX"); + TExtRef tExtRefLnInst = createExtRefExample("CB_1", TServiceType.GOOSE); + tExtRefLnInst.setSrcLNInst("X"); + TExtRef tExtRefPrefix = createExtRefExample("CB_1", TServiceType.GOOSE); + tExtRefPrefix.setSrcPrefix("X"); + + return Stream.of( + Arguments.of(named("ExtRef is not fed by same CB when different ServiceType", createExtRefExample("CB_1", TServiceType.GOOSE)), + createExtRefExample("CB_1", TServiceType.SMV)), + Arguments.of(named("ExtRef is not fed by same CB when different SrcCBName", createExtRefExample("CB_1", TServiceType.GOOSE)), + createExtRefExample("CB_2", TServiceType.GOOSE)), + Arguments.of(named("ExtRef is not fed by same CB when different SrcLnClass", createExtRefExample("CB_1", TServiceType.GOOSE)), + tExtRefLnClass), + Arguments.of(named("ExtRef is not fed by same CB when different IedName", createExtRefExample("CB_1", TServiceType.GOOSE)), + tExtRefIedName), + Arguments.of(named("ExtRef is not fed by same CB when different SrcLdInst", createExtRefExample("CB_1", TServiceType.GOOSE)), + tExtRefLdInst), + Arguments.of(named("ExtRef is not fed by same CB when different SrcLnInst", createExtRefExample("CB_1", TServiceType.GOOSE)), + tExtRefLnInst), + Arguments.of(named("ExtRef is not fed by same CB when different SrcPrefix", createExtRefExample("CB_1", TServiceType.GOOSE)), + tExtRefPrefix) + ); + } + + @Test + void filterDuplicatedExtRefs_should_remove_duplicated_extrefs() { + // Given + TExtRef tExtRefLnClass = createExtRefExample("CB_Name1", TServiceType.GOOSE); + tExtRefLnClass.getSrcLNClass().add(TLLN0Enum.LLN_0.value()); + TExtRef tExtRef = createExtRefExample("CB_Name1", TServiceType.GOOSE); + List tExtRefList = List.of(tExtRef, tExtRefLnClass, createExtRefExample("CB", TServiceType.GOOSE), + createExtRefExample("CB", TServiceType.GOOSE)); + // When + List result = extRefService.filterDuplicatedExtRefs(tExtRefList); + // Then + assertThat(result).hasSizeLessThan(tExtRefList.size()) + .hasSize(2); + } + + @Test + void filterDuplicatedExtRefs_should_not_remove_not_duplicated_extrefs() { + // Given + TExtRef tExtRefIedName = createExtRefExample("CB_1", TServiceType.GOOSE); + tExtRefIedName.setIedName("IED_XXX"); + TExtRef tExtRefLdInst = createExtRefExample("CB_1", TServiceType.GOOSE); + tExtRefLdInst.setSrcLDInst("LD_XXX"); + TExtRef tExtRefLnInst = createExtRefExample("CB_1", TServiceType.GOOSE); + tExtRefLnInst.setSrcLNInst("X"); + TExtRef tExtRefPrefix = createExtRefExample("CB_1", TServiceType.GOOSE); + tExtRefPrefix.setSrcPrefix("X"); + List tExtRefList = List.of(tExtRefIedName, tExtRefLdInst, tExtRefLnInst, tExtRefPrefix, + createExtRefExample("CB_1", TServiceType.GOOSE), createExtRefExample("CB_1", TServiceType.SMV)); + // When + List result = extRefService.filterDuplicatedExtRefs(tExtRefList); + // Then + assertThat(result).hasSameSizeAs(tExtRefList) + .hasSize(6); + } + private static TPrivate createPrivateCompasFlow(List compasFlows) { TPrivate tPrivate = new TPrivate(); tPrivate.setType(PrivateEnum.COMPAS_FLOW.getPrivateType()); - tPrivate.getContent().addAll(compasFlows.stream().map(objectFactory::createFlow).toList()); + tPrivate.getContent().addAll(compasFlows.stream().map(value -> new ObjectFactory().createFlow(value)).toList()); return tPrivate; } @@ -158,4 +271,4 @@ private TCompasFlow createCompasFlow(String dataStreamKey, String extRefIedName, return tCompasFlow; } -} \ No newline at end of file +} 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 fdea655ab..89987741d 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 @@ -13,6 +13,7 @@ import org.lfenergy.compas.sct.commons.scl.ln.LN0Adapter; import org.lfenergy.compas.sct.commons.scl.ln.LNAdapter; import org.lfenergy.compas.sct.commons.util.ControlBlockEnum; +import org.lfenergy.compas.sct.commons.util.PrivateUtils; import org.lfenergy.compas.sct.commons.util.Utils; import org.opentest4j.AssertionFailedError; @@ -69,7 +70,14 @@ public static TExtRef findExtRef(SCL scl, String iedName, String ldInst, String .stream() .filter(extRef -> extRefDesc.equals(extRef.getDesc())) .findFirst() - .orElseThrow(() -> new AssertionFailedError(String.format("ExtRef.des=%s not found in IED.name=%s,LDevice.inst=%s", extRefDesc, iedName, ldInst))); + .orElseThrow(() -> new AssertionFailedError(String.format("ExtRef.desc=%s not found in IED.name=%s,LDevice.inst=%s", extRefDesc, iedName, ldInst))); + } + + public static TCompasFlow findCompasFlow(SCL scl, String iedName, String ldInst, String compasFlowDataStreamKey) { + return PrivateUtils.extractCompasPrivates(findInputs(scl, iedName, ldInst).getCurrentElem(), TCompasFlow.class) + .filter(compasFlow -> compasFlowDataStreamKey.equals(compasFlow.getDataStreamKey())) + .findFirst() + .orElseThrow(() -> new AssertionFailedError(String.format("CompasFlow.dataStreamKey=%s not found in IED.name=%s,LDevice.inst=%s", compasFlowDataStreamKey, iedName, ldInst))); } public static LN0Adapter findLn0(SCL scl, String iedName, String ldInst) { diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/UtilsTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/UtilsTest.java index 788b466d3..a1d66361f 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/UtilsTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/UtilsTest.java @@ -11,10 +11,8 @@ import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.support.ReflectionSupport; -import org.lfenergy.compas.scl2007b4.model.TExtRef; import org.lfenergy.compas.scl2007b4.model.TLLN0Enum; import org.lfenergy.compas.scl2007b4.model.TLN; -import org.lfenergy.compas.scl2007b4.model.TServiceType; import org.lfenergy.compas.sct.commons.dto.FCDAInfo; import org.lfenergy.compas.sct.commons.exception.ScdException; @@ -22,7 +20,6 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.*; -import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.createExtRefExample; import static org.lfenergy.compas.sct.commons.util.Utils.copySclElement; @@ -514,57 +511,6 @@ void toHex_should_return_hexadecimal(long number, int length, String expected) { assertThat(result).isEqualTo(expected); } - @Test - void isExtRefFeedBySameControlBlock_should_return_true() { - // Given - TExtRef tExtRefLnClass = createExtRefExample("CB_1", TServiceType.GOOSE); - tExtRefLnClass.getSrcLNClass().add(TLLN0Enum.LLN_0.value()); - TExtRef tExtRef = createExtRefExample("CB_1", TServiceType.GOOSE); - // When - // Then - assertThat(Utils.isExtRefFeedBySameControlBlock(tExtRef, tExtRefLnClass)).isTrue(); - assertThat(Utils.isExtRefFeedBySameControlBlock(createExtRefExample("CB_1", TServiceType.GOOSE), tExtRef)).isTrue(); - } - - @ParameterizedTest(name = "{0}") - @MethodSource("provideExtRefsToCompare") - void isExtRefFeedBySameControlBlock_should_return_false(String testCase, TExtRef tExtRef1, TExtRef tExtRef2) { - // Given - // When - // Then - assertThat(Utils.isExtRefFeedBySameControlBlock(tExtRef1, tExtRef2)).isFalse(); - } - - private static Stream provideExtRefsToCompare() { - TExtRef tExtRefLnClass = createExtRefExample("CB_1", TServiceType.GOOSE); - tExtRefLnClass.getSrcLNClass().add("XXX"); - TExtRef tExtRefIedName = createExtRefExample("CB_1", TServiceType.GOOSE); - tExtRefIedName.setIedName("IED_XXX"); - TExtRef tExtRefLdInst = createExtRefExample("CB_1", TServiceType.GOOSE); - tExtRefLdInst.setSrcLDInst("LD_XXX"); - TExtRef tExtRefLnInst = createExtRefExample("CB_1", TServiceType.GOOSE); - tExtRefLnInst.setSrcLNInst("X"); - TExtRef tExtRefPrefix = createExtRefExample("CB_1", TServiceType.GOOSE); - tExtRefPrefix.setSrcPrefix("X"); - - return Stream.of( - Arguments.of("ExtRef is not fed by same CB when different ServiceType", createExtRefExample("CB_1", TServiceType.GOOSE), - createExtRefExample("CB_1", TServiceType.SMV)), - Arguments.of("ExtRef is not fed by same CB when different SrcCBName", createExtRefExample("CB_1", TServiceType.GOOSE), - createExtRefExample("CB_2", TServiceType.GOOSE)), - Arguments.of("ExtRef is not fed by same CB when different SrcLnClass", createExtRefExample("CB_1", TServiceType.GOOSE), - tExtRefLnClass), - Arguments.of("ExtRef is not fed by same CB when different IedName", createExtRefExample("CB_1", TServiceType.GOOSE), - tExtRefIedName), - Arguments.of("ExtRef is not fed by same CB when different SrcLdInst", createExtRefExample("CB_1", TServiceType.GOOSE), - tExtRefLdInst), - Arguments.of("ExtRef is not fed by same CB when different SrcLnInst", createExtRefExample("CB_1", TServiceType.GOOSE), - tExtRefLnInst), - Arguments.of("ExtRef is not fed by same CB when different SrcPrefix", createExtRefExample("CB_1", TServiceType.GOOSE), - tExtRefPrefix) - ); - } - @Test void copySclElement_should_copy_by_value() { // Given diff --git a/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_based_on_lnode_success.xml b/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_based_on_lnode_success.xml new file mode 100644 index 000000000..f42453fb9 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_based_on_lnode_success.xml @@ -0,0 +1,93 @@ + + + + + +
+ + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + + + + + + + + + + + + + + + + + + + + + + + + + on + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + From 95ee0d5f2b5ea882aed5684416cb63ef11e3910d Mon Sep 17 00:00:00 2001 From: massifben <105049157+massifben@users.noreply.github.com> Date: Wed, 6 Dec 2023 11:58:21 +0100 Subject: [PATCH 7/9] feat(#362): RSR-812 - Use generated JAXB class for configuration of Communication of Control Blocks Signed-off-by: massifben <105049157+massifben@users.noreply.github.com> --- .../SclAutomationServiceIntegrationTest.java | 5 +- sct-commons/pom.xml | 19 +- .../commons/ControlBlockEditorService.java | 430 ++++++++++++++++++ .../sct/commons/ControlBlockService.java | 159 ------- .../sct/commons/api/ControlBlockEditor.java | 16 +- .../dto/ControlBlockNetworkSettings.java | 71 --- .../sct/commons/scl/ControlService.java | 26 ++ .../commons/scl/ied/ControlBlockAdapter.java | 80 ---- .../sct/commons/util/ControlBlockEnum.java | 24 +- .../ControlBlockNetworkSettingsCsvHelper.java | 204 --------- .../src/main/resources/xsd/GSE_SMV_CB_COM.xsd | 152 +++++++ .../{LDEPF_Config_file_v2.xsd => LDEPF.xsd} | 0 ...ava => ControlBlockEditorServiceTest.java} | 416 ++++++++++++----- .../dto/ControlBlockNetworkSettingsTest.java | 235 ---------- .../scl/ied/ControlBlockAdapterTest.java | 87 +--- .../commons/util/ControlBlockEnumTest.java | 41 +- .../ControlBlockCommunicationTemplates.csv | 9 - 17 files changed, 995 insertions(+), 979 deletions(-) create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockEditorService.java delete mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockService.java delete mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettings.java create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ControlService.java delete mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ControlBlockNetworkSettingsCsvHelper.java create mode 100644 sct-commons/src/main/resources/xsd/GSE_SMV_CB_COM.xsd rename sct-commons/src/main/resources/xsd/{LDEPF_Config_file_v2.xsd => LDEPF.xsd} (100%) rename sct-commons/src/test/java/org/lfenergy/compas/sct/commons/{ControlBlockServiceTest.java => ControlBlockEditorServiceTest.java} (52%) delete mode 100644 sct-commons/src/test/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettingsTest.java delete mode 100644 sct-commons/src/test/resources/ControlBlockCommunicationTemplates.csv diff --git a/sct-app/src/test/java/org.lfenergy.compas.sct.app/SclAutomationServiceIntegrationTest.java b/sct-app/src/test/java/org.lfenergy.compas.sct.app/SclAutomationServiceIntegrationTest.java index d4aaea1ca..d2e8bd735 100644 --- a/sct-app/src/test/java/org.lfenergy.compas.sct.app/SclAutomationServiceIntegrationTest.java +++ b/sct-app/src/test/java/org.lfenergy.compas.sct.app/SclAutomationServiceIntegrationTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test; import org.lfenergy.compas.scl2007b4.model.LN0; import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.sct.commons.ControlBlockService; +import org.lfenergy.compas.sct.commons.ControlBlockEditorService; import org.lfenergy.compas.sct.commons.SclService; import org.lfenergy.compas.sct.commons.SubstationService; import org.lfenergy.compas.sct.commons.api.ControlBlockEditor; @@ -16,6 +16,7 @@ import org.lfenergy.compas.sct.commons.api.SubstationEditor; import org.lfenergy.compas.sct.commons.dto.HeaderDTO; import org.lfenergy.compas.sct.commons.exception.ScdException; +import org.lfenergy.compas.sct.commons.scl.ControlService; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.scl.ldevice.LDeviceAdapter; @@ -33,7 +34,7 @@ class SclAutomationServiceIntegrationTest { private SclAutomationService sclAutomationService ; private static final SclEditor sclEditor = new SclService() ; private static final SubstationEditor substationEditor = new SubstationService() ; - private static final ControlBlockEditor controlBlockEditor = new ControlBlockService() ; + private static final ControlBlockEditor controlBlockEditor = new ControlBlockEditorService(new ControlService()) ; private HeaderDTO headerDTO; diff --git a/sct-commons/pom.xml b/sct-commons/pom.xml index c807d1237..285b507b1 100644 --- a/sct-commons/pom.xml +++ b/sct-commons/pom.xml @@ -185,7 +185,7 @@ - ${project.basedir}/src/main/resources/xsd/LDEPF_Config_file_v2.xsd + ${project.basedir}/src/main/resources/xsd/LDEPF.xsd ${project.basedir}/src/main/resources/binding_configuration.xjb @@ -214,6 +214,23 @@ false + + cbcom + + xjc + + + + ${project.basedir}/src/main/resources/xsd/GSE_SMV_CB_COM.xsd + + + ${project.basedir}/src/main/resources/binding_configuration.xjb + + org.lfenergy.compas.sct.commons.model.cbcom + true + false + + diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockEditorService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockEditorService.java new file mode 100644 index 000000000..9f01aa8ec --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockEditorService.java @@ -0,0 +1,430 @@ +// SPDX-FileCopyrightText: 2022 2023 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.lfenergy.compas.scl2007b4.model.*; +import org.lfenergy.compas.sct.commons.api.ControlBlockEditor; +import org.lfenergy.compas.sct.commons.dto.FcdaForDataSetsCreation; +import org.lfenergy.compas.sct.commons.dto.SclReportItem; +import org.lfenergy.compas.sct.commons.exception.ScdException; +import org.lfenergy.compas.sct.commons.model.cbcom.*; +import org.lfenergy.compas.sct.commons.scl.ControlService; +import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; +import org.lfenergy.compas.sct.commons.scl.ldevice.LDeviceAdapter; +import org.lfenergy.compas.sct.commons.scl.ln.LNAdapter; +import org.lfenergy.compas.sct.commons.util.ControlBlockEnum; +import org.lfenergy.compas.sct.commons.util.PrivateUtils; +import org.lfenergy.compas.sct.commons.util.SclConstructorHelper; +import org.lfenergy.compas.sct.commons.util.Utils; + +import java.math.BigInteger; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newAddress; +import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newP; + +@RequiredArgsConstructor +public class ControlBlockEditorService implements ControlBlockEditor { + + private static final int MAX_VLAN_ID = 0x0FFF; + private static final int MAX_VLAN_PRIORITY = 7; + private static final String NONE = "none"; + private static final String APPID_P_TYPE = "APPID"; + private static final String MAC_ADDRESS_P_TYPE = "MAC-Address"; + private static final String VLAN_ID_P_TYPE = "VLAN-ID"; + private static final String VLAN_PRIORITY_P_TYPE = "VLAN-PRIORITY"; + private static final int APPID_LENGTH = 4; + private static final int VLAN_ID_LENGTH = 3; + private static final String MISSING_ATTRIBUTE_IN_GSE_SMV_CB_COM = "Error in Control Block communication setting file: vlan is missing attribute "; + private final ControlService controlService; + + @Override + public List analyzeDataGroups(SCL scd) { + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + return sclRootAdapter.streamIEDAdapters() + .map(iedAdapter -> { + List list = new ArrayList<>(); + list.addAll(iedAdapter.checkDataGroupCoherence()); + list.addAll(iedAdapter.checkBindingDataGroupCoherence()); + return list; + }).flatMap(Collection::stream).toList(); + } + + @Override + public List createDataSetAndControlBlocks(SCL scd, Set allowedFcdas) { + checkFcdaInitDataPresence(allowedFcdas); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + Stream lDeviceAdapters = sclRootAdapter.streamIEDAdapters().flatMap(IEDAdapter::streamLDeviceAdapters); + return createDataSetAndControlBlocks(lDeviceAdapters, allowedFcdas); + } + + @Override + public List createDataSetAndControlBlocks(SCL scd, String targetIedName, Set allowedFcdas) { + checkFcdaInitDataPresence(allowedFcdas); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName(targetIedName); + return createDataSetAndControlBlocks(iedAdapter.streamLDeviceAdapters(), allowedFcdas); + + } + + @Override + public List createDataSetAndControlBlocks(SCL scd, String targetIedName, String targetLDeviceInst, Set allowedFcdas) { + requireNotBlank(targetIedName, "IED.name parameter is missing"); + checkFcdaInitDataPresence(allowedFcdas); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName(targetIedName); + LDeviceAdapter lDeviceAdapter = iedAdapter.getLDeviceAdapterByLdInst(targetLDeviceInst); + return createDataSetAndControlBlocks(Stream.of(lDeviceAdapter), allowedFcdas); + } + + private void checkFcdaInitDataPresence(Set allowedFcdas) { + if (allowedFcdas == null || allowedFcdas.isEmpty()) { + throw new ScdException("Accepted FCDAs list is empty, you should initialize allowed FCDA lists with CsvHelper class before"); + } + } + + private List createDataSetAndControlBlocks(Stream lDeviceAdapters, Set allowedFcdas) { + return lDeviceAdapters + .map(lDeviceAdapter -> lDeviceAdapter.createDataSetAndControlBlocks(allowedFcdas)) + .flatMap(List::stream) + .toList(); + } + + @Override + public void removeAllControlBlocksAndDatasetsAndExtRefSrcBindings(final SCL scl) { + SclRootAdapter sclRootAdapter = new SclRootAdapter(scl); + List lDeviceAdapters = sclRootAdapter.streamIEDAdapters() + .flatMap(IEDAdapter::streamLDeviceAdapters).toList(); + // LN0 + lDeviceAdapters.stream() + .map(LDeviceAdapter::getLN0Adapter) + .forEach(ln0 -> { + ln0.removeAllControlBlocksAndDatasets(); + ln0.removeAllExtRefSourceBindings(); + }); + // Other LN + lDeviceAdapters.stream() + .map(LDeviceAdapter::getLNAdapters).flatMap(List::stream) + .forEach(LNAdapter::removeAllControlBlocksAndDatasets); + } + + @Override + public List configureNetworkForAllControlBlocks(SCL scd, CBCom cbCom) { + return Stream.concat( + configureNetworkForControlBlocks(scd, cbCom, TCBType.GOOSE), + configureNetworkForControlBlocks(scd, cbCom, TCBType.SV)) + .toList(); + } + + private Stream configureNetworkForControlBlocks(SCL scl, CBCom cbCom, TCBType tcbType) { + CbComSettings cbComSettings; + try { + cbComSettings = parseCbCom(cbCom, tcbType); + } catch (ScdException ex) { + return Stream.of(SclReportItem.error("Control Block Communication setting files", ex.getMessage())); + } + return scl.getIED().stream() + .flatMap(tied -> + tied.getAccessPoint() + .stream() + .filter(tAccessPoint -> tAccessPoint.isSetServer() && tAccessPoint.getServer().isSetLDevice()) + .flatMap(tAccessPoint -> tAccessPoint.getServer().getLDevice().stream() + .map(tlDevice -> new IedApLd(tied, tAccessPoint.getName(), tlDevice)) + ) + ) + .flatMap(iedApLd -> Optional.ofNullable(iedApLd.lDevice().getLN0()).stream() + .flatMap(ln0 -> controlService.getControls(ln0, ControlBlockEnum.from(tcbType).getControlBlockClass())) + .map(tControl -> { + CriteriaOrError criteriaOrError = getCriteria(iedApLd.ied(), tcbType, tControl.getName()); + if (criteriaOrError.errorMessage != null) { + return Optional.of(SclReportItem.error(iedApLd.getXPath(), criteriaOrError.errorMessage)); + } + Settings settings = cbComSettings.settingsByCriteria.get(criteriaOrError.criteria); + if (settings == null) { + return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because: No controlBlock communication settings found with these " + criteriaOrError.criteria); + } + return configureControlBlockNetwork(scl.getCommunication(), settings, cbComSettings.appIdIterator, cbComSettings.macAddressIterator, tControl, iedApLd); + }) + .flatMap(Optional::stream) + ); + } + + private CbComSettings parseCbCom(CBCom cbCom, TCBType tcbType) { + TRange appIdRange = Optional.ofNullable(cbCom.getAppIdRanges()).map(AppIdRanges::getAppIdRange).stream() + .flatMap(Collection::stream) + .filter(tRange -> tcbType.equals(tRange.getCBType())) + .findFirst() + .orElseThrow(() -> new ScdException("Control Block Communication setting files does not contain AppIdRange for cbType " + tcbType.value())); + PrimitiveIterator.OfLong appIdIterator = Utils.sequence(Long.parseLong(appIdRange.getStart(), 16), Long.parseLong(appIdRange.getEnd(), 16)); + + TRange macRange = Optional.ofNullable(cbCom.getMacRanges()).map(MacRanges::getMacRange).stream() + .flatMap(Collection::stream) + .filter(tRange -> tcbType.equals(tRange.getCBType())) + .findFirst() + .orElseThrow(() -> new ScdException("Control Block Communication setting files does not contain MacRange for cbType " + tcbType.value())); + Iterator macAddressIterator = Utils.macAddressSequence(macRange.getStart(), macRange.getEnd()); + + Map settingsByCriteria = Optional.ofNullable(cbCom.getVlans()).map(Vlans::getVlan).stream() + .flatMap(Collection::stream) + .filter(vlan -> tcbType.equals(vlan.getCBType())) + .collect(Collectors.toMap(this::vlanToCriteria, this::vlanToSetting)); + + return new CbComSettings(appIdIterator, macAddressIterator, settingsByCriteria); + } + + private Optional configureControlBlockNetwork(TCommunication tCommunication, Settings settings, PrimitiveIterator.OfLong appIdIterator, Iterator macAddressIterator, TControl tControl, IedApLd iedApLd) { + if (settings.vlanId() == null) { + return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because no Vlan Id was provided in the settings"); + } + if (!appIdIterator.hasNext()) { + return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of appId is exhausted"); + } + if (!macAddressIterator.hasNext()) { + return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of MAC Address is exhausted"); + } + + Optional optConApAdapter = findConnectedAp(tCommunication, iedApLd.ied.getName(), iedApLd.apName); + if (optConApAdapter.isEmpty()) { + return newError(iedApLd, tControl, "Cannot configure communication for ControlBlock because no ConnectedAP found for AccessPoint"); + } + TConnectedAP tConnectedAP = optConApAdapter.get(); + List listOfPs = new ArrayList<>(); + listOfPs.add(newP(APPID_P_TYPE, Utils.toHex(appIdIterator.nextLong(), APPID_LENGTH))); + listOfPs.add(newP(MAC_ADDRESS_P_TYPE, macAddressIterator.next())); + listOfPs.add(newP(VLAN_ID_P_TYPE, Utils.toHex(settings.vlanId(), VLAN_ID_LENGTH))); + if (settings.vlanPriority() != null) { + listOfPs.add(newP(VLAN_PRIORITY_P_TYPE, String.valueOf(settings.vlanPriority()))); + } + + switch (tControl) { + case TGSEControl ignored -> updateGseOrCreateIfNotExists(tConnectedAP, iedApLd.lDevice().getInst(), tControl.getName(), listOfPs, SclConstructorHelper.newDurationInMilliSec(settings.minTime), SclConstructorHelper.newDurationInMilliSec(settings.maxTime)); + case TSampledValueControl ignored -> updateSmvOrCreateIfNotExists(tConnectedAP, iedApLd.lDevice().getInst(), tControl.getName(), listOfPs); + default -> throw new ScdException("Unsupported Control Block type for communication configuration : " + tControl.getClass().getName()); + } + return Optional.empty(); + } + + private Optional findConnectedAp(TCommunication tCommunication, String iedName, String apName) { + if (tCommunication == null || !tCommunication.isSetSubNetwork()) { + return Optional.empty(); + } + return tCommunication.getSubNetwork().stream() + .filter(TSubNetwork::isSetConnectedAP) + .flatMap(tSubNetwork -> tSubNetwork.getConnectedAP().stream()) + .filter(tConnectedAP -> iedName.equals(tConnectedAP.getIedName()) && apName.equals(tConnectedAP.getApName())) + .findFirst(); + } + + private void updateGseOrCreateIfNotExists(TConnectedAP tConnectedAP, String ldInst, String cbName, List listOfP, TDurationInMilliSec minTime, TDurationInMilliSec maxTime) { + Optional optGse = tConnectedAP.isSetGSE() ? + tConnectedAP.getGSE().stream().filter(gse1 -> Objects.equals(ldInst, gse1.getLdInst()) && Objects.equals(cbName, gse1.getCbName())).findFirst() + : Optional.empty(); + TGSE gse = optGse + .orElseGet(() -> { + TGSE newGse = new TGSE(); + newGse.setLdInst(ldInst); + newGse.setCbName(cbName); + tConnectedAP.getGSE().add(newGse); + return newGse; + } + ); + gse.setAddress(newAddress(listOfP)); + gse.setMinTime(minTime); + gse.setMaxTime(maxTime); + } + + /** + * Create A SMV Section or update an existing SMV Section (the network configuration of a SampledValueControl block).. + */ + private void updateSmvOrCreateIfNotExists(TConnectedAP tConnectedAP, String ldInst, String cbName, List listOfP) { + Optional optSmv = tConnectedAP.isSetSMV() ? + tConnectedAP.getSMV().stream().filter(smv1 -> Objects.equals(ldInst, smv1.getLdInst()) && Objects.equals(cbName, smv1.getCbName())).findFirst() + : Optional.empty(); + TSMV smv = optSmv + .orElseGet(() -> { + TSMV newSmv = new TSMV(); + newSmv.setLdInst(ldInst); + newSmv.setCbName(cbName); + tConnectedAP.getSMV().add(newSmv); + return newSmv; + } + ); + smv.setAddress(newAddress(listOfP)); + } + + private static Optional newError(IedApLd iedApLd, TControl tControl, String message) { + return Optional.of(SclReportItem.error(iedApLd.getXPath() + "/LN0/" + controlBlockXPath(tControl), + message)); + } + + private static String controlBlockXPath(TControl tControl) { + return ControlBlockEnum.from(tControl.getClass()).getElementName() + "[@name=\"" + tControl.getName() + "\"]"; + } + + public CriteriaOrError getCriteria(TIED tied, TCBType cbType, String cbName) { + Optional compasSystemVersion = PrivateUtils.extractCompasPrivate(tied, TCompasSystemVersion.class); + if (compasSystemVersion.isEmpty()) { + return new CriteriaOrError(null, "No private COMPAS-SystemVersion found in this IED"); + } + if (StringUtils.isBlank(compasSystemVersion.get().getMainSystemVersion()) + || (StringUtils.isBlank(compasSystemVersion.get().getMinorSystemVersion()))) { + return new CriteriaOrError(null, "Missing MainSystemVersion or MinorSystemVersion attribute in COMPAS-SystemVersion private of IED"); + } + String systemVersionWithoutV = removeVFromSystemVersion(compasSystemVersion.get()); + Optional compasICDHeader = PrivateUtils.extractCompasPrivate(tied, TCompasICDHeader.class); + if (compasICDHeader.isEmpty()) { + return new CriteriaOrError(null, "No private COMPAS-ICDHeader found in this IED"); + } + if (compasICDHeader.get().getIEDSystemVersioninstance() == null) { + return new CriteriaOrError(null, "No IEDSystemVersioninstance in the COMPAS-ICDHeader of this IED"); + } + TBayIntOrExt bayIntOrExt = cbName.endsWith("I") ? TBayIntOrExt.BAY_INTERNAL : TBayIntOrExt.BAY_EXTERNAL; + + return new CriteriaOrError( + new Criteria(cbType, + systemVersionWithoutV, + TIEDType.fromValue(compasICDHeader.get().getIEDType().value()), + TIEDRedundancy.fromValue(compasICDHeader.get().getIEDredundancy().value()), + compasICDHeader.get().getIEDSystemVersioninstance(), + bayIntOrExt), null); + } + + private String removeVFromSystemVersion(TCompasSystemVersion compasSystemVersion) { + String[] minorVersionParts = compasSystemVersion.getMinorSystemVersion().split("\\."); + return (minorVersionParts.length == 3) ? + compasSystemVersion.getMainSystemVersion() + "." + minorVersionParts[0] + "." + minorVersionParts[1] + : null; + } + + private Criteria vlanToCriteria(TVlan vlan) { + requireNotNull(vlan.getCBType(), MISSING_ATTRIBUTE_IN_GSE_SMV_CB_COM + "CBType"); + requireNotBlank(vlan.getXY(), MISSING_ATTRIBUTE_IN_GSE_SMV_CB_COM + "XY"); + requireNotBlank(vlan.getZW(), MISSING_ATTRIBUTE_IN_GSE_SMV_CB_COM + "ZW"); + requireNotNull(vlan.getIEDType(), MISSING_ATTRIBUTE_IN_GSE_SMV_CB_COM + "IEDType"); + requireNotNull(vlan.getIEDRedundancy(), MISSING_ATTRIBUTE_IN_GSE_SMV_CB_COM + "IEDRedundancy"); + requireNotBlank(vlan.getIEDSystemVersionInstance(), MISSING_ATTRIBUTE_IN_GSE_SMV_CB_COM + "IEDSystemVersionInstance"); + requireNotNull(vlan.getBayIntOrExt(), MISSING_ATTRIBUTE_IN_GSE_SMV_CB_COM + "BayIntOrExt"); + + return new Criteria( + vlan.getCBType(), + vlan.getXY() + "." + vlan.getZW(), + vlan.getIEDType(), + vlan.getIEDRedundancy(), + toIedSystemVersionInstance(vlan.getIEDSystemVersionInstance()), + vlan.getBayIntOrExt() + ); + } + + private Settings vlanToSetting(TVlan vlan) { + return new Settings(toVLanId(vlan.getVlanId()), toVlanPriority(vlan.getVlanPriority()), toDurationInMilliSec(vlan.getMinTime()), toDurationInMilliSec(vlan.getMaxTime())); + } + + private void requireNotBlank(String str, String message) { + if (StringUtils.isBlank(str)) { + throw new ScdException(message); + } + } + + private void requireNotNull(Object o, String message) { + if (Objects.isNull(o)) { + throw new ScdException(message); + } + } + + private BigInteger toIedSystemVersionInstance(String strIedSystemVersionInstance) { + if (StringUtils.isBlank(strIedSystemVersionInstance)) { + return null; + } + BigInteger iedSystemVersionInstance; + try { + iedSystemVersionInstance = new BigInteger(strIedSystemVersionInstance); + } catch (NumberFormatException e) { + throw new ScdException("Error in Control Block communication setting file: IED System Version Instance must be an integer, but got : %s".formatted(strIedSystemVersionInstance)); + } + return iedSystemVersionInstance; + } + + private Integer toVLanId(String strVlanId) { + if (StringUtils.isBlank(strVlanId) || NONE.equalsIgnoreCase(strVlanId)) { + return null; + } + int vlanId; + try { + vlanId = Integer.parseInt(strVlanId); + } catch (NumberFormatException e) { + throw new ScdException("Error in Control Block communication setting file: VLAN ID must be an integer or '%s', but got : %s".formatted(NONE, strVlanId)); + } + if (vlanId < 0 || vlanId > MAX_VLAN_ID) { + throw new ScdException("Error in Control Block communication setting file: VLAN ID must be between 0 and %d, but got : %s".formatted(MAX_VLAN_ID, strVlanId)); + } + return vlanId; + } + + private static Byte toVlanPriority(String strVlanPriority) { + if (StringUtils.isBlank(strVlanPriority) || NONE.equalsIgnoreCase(strVlanPriority)) { + return null; + } + byte vlanPriority; + try { + vlanPriority = Byte.parseByte(strVlanPriority); + } catch (NumberFormatException e) { + throw new ScdException("Error in Control Block communication setting file: VLAN Priority must be an integer or '%s', but got : %s".formatted(NONE, strVlanPriority)); + } + if (vlanPriority < 0 || vlanPriority > MAX_VLAN_PRIORITY) { + throw new ScdException("Error in Control Block communication setting file: VLAN PRIORITY must be between 0 and %d, but got : %s".formatted(MAX_VLAN_PRIORITY, strVlanPriority)); + } + return vlanPriority; + } + + private TDurationInMilliSec toDurationInMilliSec(String strDuration) { + if (StringUtils.isBlank(strDuration) || NONE.equalsIgnoreCase(strDuration)) { + return null; + } + long duration; + try { + duration = Long.parseLong(strDuration); + } catch (NumberFormatException e) { + throw new ScdException("Error in Control Block communication setting file: VLAN MinTime and MaxTime must be an integer or '%s', but got : %s".formatted(NONE, strDuration)); + } + return SclConstructorHelper.newDurationInMilliSec(duration); + } + + /** + * Key to search for a control block communication setting + */ + public record Criteria(TCBType cbType, String systemVersionWithoutV, TIEDType iedType, TIEDRedundancy iedRedundancy, BigInteger iedSystemVersionInstance, TBayIntOrExt bayIntOrExt) { + } + + /** + * Communication settings for ControlBlock or Error message + */ + public record CriteriaOrError(Criteria criteria, String errorMessage) { + } + + /** + * Communication settings for ControlBlock + */ + public record Settings(Integer vlanId, Byte vlanPriority, TDurationInMilliSec minTime, TDurationInMilliSec maxTime) { + } + + /** + * All settings of CbCom in a useful format + */ + record CbComSettings(PrimitiveIterator.OfLong appIdIterator, Iterator macAddressIterator, Map settingsByCriteria) { + } + + record IedApLd(TIED ied, String apName, TLDevice lDevice) { + String getXPath() { + return """ + /SCL/IED[@name="%s"]/AccessPoint[@name="%s"]/Server/LDevice[@inst="%s"]""".formatted(ied.getName(), apName, lDevice.getInst()); + } + + } +} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockService.java deleted file mode 100644 index 5f28d9e61..000000000 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockService.java +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-FileCopyrightText: 2022 2023 RTE FRANCE -// -// SPDX-License-Identifier: Apache-2.0 - -package org.lfenergy.compas.sct.commons; - -import org.apache.commons.lang3.StringUtils; -import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.sct.commons.api.ControlBlockEditor; -import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings; -import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.NetworkRanges; -import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.RangesPerCbType; -import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.Settings; -import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.SettingsOrError; -import org.lfenergy.compas.sct.commons.dto.FcdaForDataSetsCreation; -import org.lfenergy.compas.sct.commons.dto.SclReportItem; -import org.lfenergy.compas.sct.commons.exception.ScdException; -import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; -import org.lfenergy.compas.sct.commons.scl.ied.ControlBlockAdapter; -import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; -import org.lfenergy.compas.sct.commons.scl.ldevice.LDeviceAdapter; -import org.lfenergy.compas.sct.commons.scl.ln.LNAdapter; -import org.lfenergy.compas.sct.commons.util.ControlBlockEnum; -import org.lfenergy.compas.sct.commons.util.Utils; - -import java.util.*; -import java.util.stream.Stream; - -public class ControlBlockService implements ControlBlockEditor { - - - @Override - public List analyzeDataGroups(SCL scd) { - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - return sclRootAdapter.streamIEDAdapters() - .map(iedAdapter -> { - List list = new ArrayList<>(); - list.addAll(iedAdapter.checkDataGroupCoherence()); - list.addAll(iedAdapter.checkBindingDataGroupCoherence()); - return list; - }).flatMap(Collection::stream).toList(); - } - - @Override - public List createDataSetAndControlBlocks(SCL scd, Set allowedFcdas) { - checkFcdaInitDataPresence(allowedFcdas); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - Stream lDeviceAdapters = sclRootAdapter.streamIEDAdapters().flatMap(IEDAdapter::streamLDeviceAdapters); - return createDataSetAndControlBlocks(lDeviceAdapters, allowedFcdas); - } - - @Override - public List createDataSetAndControlBlocks(SCL scd, String targetIedName, Set allowedFcdas) { - checkFcdaInitDataPresence(allowedFcdas); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName(targetIedName); - return createDataSetAndControlBlocks(iedAdapter.streamLDeviceAdapters(), allowedFcdas); - - } - - @Override - public List createDataSetAndControlBlocks(SCL scd, String targetIedName, String targetLDeviceInst, Set allowedFcdas) { - if (StringUtils.isBlank(targetIedName)) { - throw new ScdException("IED.name parameter is missing"); - } - checkFcdaInitDataPresence(allowedFcdas); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName(targetIedName); - LDeviceAdapter lDeviceAdapter = iedAdapter.getLDeviceAdapterByLdInst(targetLDeviceInst); - return createDataSetAndControlBlocks(Stream.of(lDeviceAdapter), allowedFcdas); - } - - private void checkFcdaInitDataPresence(Set allowedFcdas) { - if (allowedFcdas == null || allowedFcdas.isEmpty()) { - throw new ScdException("Accepted FCDAs list is empty, you should initialize allowed FCDA lists with CsvHelper class before"); - } - } - - private List createDataSetAndControlBlocks(Stream lDeviceAdapters, Set allowedFcdas) { - return lDeviceAdapters - .map(lDeviceAdapter -> lDeviceAdapter.createDataSetAndControlBlocks(allowedFcdas)) - .flatMap(List::stream) - .toList(); - } - - @Override - public List configureNetworkForAllControlBlocks(SCL scd, ControlBlockNetworkSettings controlBlockNetworkSettings, - RangesPerCbType rangesPerCbType) { - List sclReportItems = new ArrayList<>(); - sclReportItems.addAll(configureNetworkForControlBlocks(scd, controlBlockNetworkSettings, rangesPerCbType.gse(), ControlBlockEnum.GSE)); - sclReportItems.addAll(configureNetworkForControlBlocks(scd, controlBlockNetworkSettings, rangesPerCbType.sampledValue(), ControlBlockEnum.SAMPLED_VALUE)); - return sclReportItems; - } - - @Override - public void removeAllControlBlocksAndDatasetsAndExtRefSrcBindings(final SCL scl) { - SclRootAdapter sclRootAdapter = new SclRootAdapter(scl); - List lDeviceAdapters = sclRootAdapter.streamIEDAdapters() - .flatMap(IEDAdapter::streamLDeviceAdapters).toList(); - // LN0 - lDeviceAdapters.stream() - .map(LDeviceAdapter::getLN0Adapter) - .forEach(ln0 -> { - ln0.removeAllControlBlocksAndDatasets(); - ln0.removeAllExtRefSourceBindings(); - }); - // Other LN - lDeviceAdapters.stream() - .map(LDeviceAdapter::getLNAdapters).flatMap(List::stream) - .forEach(LNAdapter::removeAllControlBlocksAndDatasets); - } - - - private List configureNetworkForControlBlocks(SCL scd, ControlBlockNetworkSettings controlBlockNetworkSettings, - NetworkRanges networkRanges, ControlBlockEnum controlBlockEnum) { - PrimitiveIterator.OfLong appIdIterator = Utils.sequence(networkRanges.appIdStart(), networkRanges.appIdEnd()); - Iterator macAddressIterator = Utils.macAddressSequence(networkRanges.macAddressStart(), networkRanges.macAddressEnd()); - - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - return sclRootAdapter.streamIEDAdapters() - .flatMap(iedAdapter -> - iedAdapter.streamLDeviceAdapters() - .filter(LDeviceAdapter::hasLN0) - .map(LDeviceAdapter::getLN0Adapter) - .flatMap(ln0Adapter -> ln0Adapter.streamControlBlocks(controlBlockEnum)) - .map(controlBlockAdapter -> configureControlBlockNetwork(controlBlockNetworkSettings, appIdIterator, macAddressIterator, controlBlockAdapter))) - .flatMap(Optional::stream) - .toList(); - } - - private Optional configureControlBlockNetwork(ControlBlockNetworkSettings controlBlockNetworkSettings, PrimitiveIterator.OfLong appIdIterator, Iterator macAddressIterator, ControlBlockAdapter controlBlockAdapter) { - SettingsOrError settingsOrError = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); - if (settingsOrError.errorMessage() != null) { - return Optional.of(controlBlockAdapter.buildFatalReportItem( - "Cannot configure network for this ControlBlock because: " + settingsOrError.errorMessage())); - } - Settings settings = settingsOrError.settings(); - if (settings == null) { - return Optional.of(controlBlockAdapter.buildFatalReportItem( - "Cannot configure network for this ControlBlock because no settings was provided")); - } - if (settings.vlanId() == null) { - return Optional.of(controlBlockAdapter.buildFatalReportItem( - "Cannot configure network for this ControlBlock because no Vlan Id was provided in the settings")); - } - if (!appIdIterator.hasNext()) { - return Optional.of(controlBlockAdapter.buildFatalReportItem( - "Cannot configure network for this ControlBlock because range of appId is exhausted")); - } - if (!macAddressIterator.hasNext()) { - return Optional.of(controlBlockAdapter.buildFatalReportItem( - "Cannot configure network for this ControlBlock because range of MAC Address is exhausted")); - } - - return controlBlockAdapter.configureNetwork(appIdIterator.nextLong(), macAddressIterator.next(), settings.vlanId(), settings.vlanPriority(), - settings.minTime(), settings.maxTime()); - } - -} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ControlBlockEditor.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ControlBlockEditor.java index 7406c23c3..8235dd5ea 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ControlBlockEditor.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ControlBlockEditor.java @@ -6,9 +6,9 @@ import org.lfenergy.compas.scl2007b4.model.SCL; import org.lfenergy.compas.scl2007b4.model.TExtRef; -import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings; import org.lfenergy.compas.sct.commons.dto.FcdaForDataSetsCreation; import org.lfenergy.compas.sct.commons.dto.SclReportItem; +import org.lfenergy.compas.sct.commons.model.cbcom.CBCom; import org.lfenergy.compas.sct.commons.util.Utils; import java.util.List; @@ -22,7 +22,7 @@ *
    *
  1. {@link ControlBlockEditor#createDataSetAndControlBlocks(SCL, Set) Create DataSet and ControlBlock based on the TExtRef}
  2. *
  3. {@link ControlBlockEditor#createDataSetAndControlBlocks(SCL, String, Set) Create DataSet and ControlBlock based on the TExtRef in given IED}
  4. - *
  5. {@link ControlBlockEditor#createDataSetAndControlBlocks(SCL, String,String, Set) Create DataSet and ControlBlock based on the TExtRef in given IED and LDevice}
  6. + *
  7. {@link ControlBlockEditor#createDataSetAndControlBlocks(SCL, String, String, Set) Create DataSet and ControlBlock based on the TExtRef in given IED and LDevice}
  8. *
  9. {@link ControlBlockEditor#configureNetworkForAllControlBlocks Configure the network for the ControlBlocks}
  10. *
  11. {@link ControlBlockEditor#removeAllControlBlocksAndDatasetsAndExtRefSrcBindings Removes all ControlBlocks and DataSets for all LNs in SCL}
  12. *
  13. {@link ControlBlockEditor#analyzeDataGroups(SCL)} Checks Control Blocks, DataSets and FCDA number limitation into Access Points }
  14. @@ -85,17 +85,11 @@ public interface ControlBlockEditor { * - the Communication/SubNetwork/ConnectedAP/GSE element, for the GSEControl blocks * - the Communication/SubNetwork/ConnectedAP/SMV element, for the SampledValueControl blocks * - * @param scd input SCD object. The object will be modified with the new DataGSESet and SMV elements - * @param controlBlockNetworkSettings a method tha gives the network configuration information for a given ControlBlock - * @param rangesPerCbType provide NetworkRanges for GSEControl and SampledValueControl. NetworkRanges contains : - * start-end app APPID range (long value), start-end MAC-Addresses (Mac-Addresses values: Ex: "01-0C-CD-01-01-FF") + * @param scd input SCD object. The object will be modified with the new GSE and SMV elements + * @param cbCom communication settings to configure Control Block Communication * @return list of encountered errors * @see Utils#macAddressToLong(String) for the expected MAC address format - * @see ControlBlockNetworkSettings - * @see ControlBlockNetworkSettings.RangesPerCbType - * @see ControlBlockNetworkSettings.NetworkRanges */ - List configureNetworkForAllControlBlocks(SCL scd, ControlBlockNetworkSettings controlBlockNetworkSettings, - ControlBlockNetworkSettings.RangesPerCbType rangesPerCbType); + List configureNetworkForAllControlBlocks(SCL scd, CBCom cbCom); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettings.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettings.java deleted file mode 100644 index 65fcb11f3..000000000 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettings.java +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-FileCopyrightText: 2023 RTE FRANCE -// -// SPDX-License-Identifier: Apache-2.0 - -package org.lfenergy.compas.sct.commons.dto; - -import org.lfenergy.compas.scl2007b4.model.TDurationInMilliSec; -import org.lfenergy.compas.sct.commons.scl.ied.ControlBlockAdapter; - -/** - * This interface has a single method which provides network settings for a ControlBlock. - * These are used to create: - * - the Communication/SubNetwork/ConnectedAP/GSE element, which is the network configuration of a GSEControl block - * - the Communication/SubNetwork/ConnectedAP/SMV element, which is the network configuration of a SampledValueControl block - * It is a FunctionalInterface, so it can be implemented with a lambda expression. - * - * @see org.lfenergy.compas.sct.commons.util.ControlBlockNetworkSettingsCsvHelper - */ -@FunctionalInterface -public interface ControlBlockNetworkSettings { - - /** - * This method provides a vlanId, vlanPriority, minTime, maxTime for this ControlBlock. - * vlanPriority will be ignored when vlanId is null. - * - * @param controlBlockAdapter ControlBlock for which we want to configure the communication section - * @return network settings to use for configuring Communication section for this ControlBlock. - * An error message can be provided (i.e. errorMessage not null) or a null settings, in order to avoid configuring the ControlBlock. - */ - SettingsOrError getNetworkSettings(ControlBlockAdapter controlBlockAdapter); - - /** - * Network settings for ControlBlock communication - * - * @param vlanId id of the vlan - * @param vlanPriority priority for the vlan - * @param minTime minTime for GSE communication element - * @param maxTime maxTime for GSE communication element - */ - record Settings(Integer vlanId, Byte vlanPriority, TDurationInMilliSec minTime, TDurationInMilliSec maxTime) { - } - - /** - * Network settings for ControlBlock communication or Error message - * - * @param settings Network settings for ControlBlock communication. Can be null when errorMessage is provided - * @param errorMessage should be null if settings is provided - */ - record SettingsOrError(Settings settings, String errorMessage) { - } - - /** - * NetworkRanges for GSEControl and SampledValueControl - * - * @param gse NetworkRanges for GSEControl - * @param sampledValue NetworkRanges for SampledValueControl - */ - record RangesPerCbType(NetworkRanges gse, NetworkRanges sampledValue) { - } - - /** - * Range of APPID and range of MAC-Address - * - * @param appIdStart range start for APPID (inclusive) - * @param appIdEnd range end for APPID (inclusive) - * @param macAddressStart range start for MAC-Addresses (inclusive). Ex: "01-0C-CD-01-00-00" - * @param macAddressEnd range end for MAC-Addresses (inclusive). Ex: "01-0C-CD-01-01-FF" - */ - record NetworkRanges(long appIdStart, long appIdEnd, String macAddressStart, String macAddressEnd) { - } -} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ControlService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ControlService.java new file mode 100644 index 000000000..c1904d913 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ControlService.java @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.scl; + +import org.lfenergy.compas.scl2007b4.model.*; + +import java.util.stream.Stream; + +public class ControlService { + + public Stream getControls(TAnyLN tAnyLN, Class tControlClass){ + if (tControlClass == TGSEControl.class && tAnyLN instanceof TLN0 tln0 && tln0.isSetGSEControl()){ + return tln0.getGSEControl().stream().map(tControlClass::cast); + } else if (tControlClass == TSampledValueControl.class && tAnyLN instanceof TLN0 tln0 && tln0.isSetSampledValueControl()){ + return tln0.getSampledValueControl().stream().map(tControlClass::cast); + } else if (tControlClass == TReportControl.class && tAnyLN.isSetReportControl()){ + return tAnyLN.getReportControl().stream().map(tControlClass::cast); + } else if (tControlClass == TLogControl.class){ + return tAnyLN.getLogControl().stream().map(tControlClass::cast); + } + return Stream.empty(); + } + +} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapter.java index 6902f2f5e..f6c539def 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapter.java @@ -7,21 +7,10 @@ import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.dto.ControlBlockTarget; -import org.lfenergy.compas.sct.commons.dto.SclReportItem; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; -import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; -import org.lfenergy.compas.sct.commons.scl.com.ConnectedAPAdapter; -import org.lfenergy.compas.sct.commons.scl.ldevice.LDeviceAdapter; import org.lfenergy.compas.sct.commons.scl.ln.AbstractLNAdapter; import org.lfenergy.compas.sct.commons.util.ControlBlockEnum; -import org.lfenergy.compas.sct.commons.util.Utils; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newDurationInMilliSec; -import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newP; import static org.lfenergy.compas.sct.commons.util.Utils.xpathAttributeFilter; /** @@ -52,12 +41,6 @@ public class ControlBlockAdapter extends SclElementAdapter, TControl> { private static final long RPT_ENABLED_MAX_DEFAULT = 1L; - private static final String APPID_P_TYPE = "APPID"; - private static final String MAC_ADDRESS_P_TYPE = "MAC-Address"; - private static final String VLAN_ID_P_TYPE = "VLAN-ID"; - private static final String VLAN_PRIORITY_P_TYPE = "VLAN-PRIORITY"; - private static final int APPID_LENGTH = 4; - private static final int VLAN_ID_LENGTH = 3; public ControlBlockAdapter(AbstractLNAdapter parentAdapter, TControl tControl) { super(parentAdapter, tControl); @@ -128,67 +111,4 @@ public void addTargetIfNotExists(AbstractLNAdapter targetLn) { } } - /** - * Configure the Communication section for this ControlBlock - * - Communication/SubNetwork/ConnectedAP/GSE for GSEControl block - * - Communication/SubNetwork/ConnectedAP/SMV for SampledValueControl block - * @param appId value for P type APPID - * @param macAddress value for P type MAC-Address - * @param vlanId value for P type VLAN-ID - * @param vlanPriority value for P type VLAN-PRIORITY - * @param minTime MinTime Element - * @param maxTime MaxTime Element - * @return An empty Optional if network have been configured, else a SclReportItem. - */ - public Optional configureNetwork(long appId, String macAddress, Integer vlanId, Byte vlanPriority, TDurationInMilliSec minTime, - TDurationInMilliSec maxTime) { - String accessPointName = getParentLDeviceAdapter().getAccessPoint().getName(); - - Optional optConApAdapter = getSclRootAdapter().findConnectedApAdapter(getParentIedAdapter().getName(), accessPointName); - if (optConApAdapter.isEmpty()) { - return Optional.of(buildFatalReportItem("Cannot configure network for ControlBlock because no ConnectAP found for parent AccessPoint")); - } - ConnectedAPAdapter connectedAPAdapter = optConApAdapter.get(); - List listOfPs = new ArrayList<>(); - listOfPs.add(newP(APPID_P_TYPE, Utils.toHex(appId, APPID_LENGTH))); - listOfPs.add(newP(MAC_ADDRESS_P_TYPE, macAddress)); - if (vlanId != null) { - listOfPs.add(newP(VLAN_ID_P_TYPE, Utils.toHex(vlanId, VLAN_ID_LENGTH))); - if (vlanPriority != null) { - listOfPs.add(newP(VLAN_PRIORITY_P_TYPE, String.valueOf(vlanPriority))); - } - } - switch (getControlBlockEnum()) { - case GSE -> connectedAPAdapter.updateGseOrCreateIfNotExists(getParentLDeviceAdapter().getInst(), currentElem.getName(), listOfPs, newDurationInMilliSec(minTime), newDurationInMilliSec(maxTime)); - case SAMPLED_VALUE -> connectedAPAdapter.updateSmvOrCreateIfNotExists(getParentLDeviceAdapter().getInst(), currentElem.getName(), listOfPs); - default -> { - return Optional.of(buildFatalReportItem("configureNetwork not yet implemented for %s ControlBlocks".formatted(getControlBlockEnum()))); - } - } - return Optional.empty(); - } - - /** - * Get parent LDevice - * @return ControlBlock's parent lDeviceAdapter - */ - private LDeviceAdapter getParentLDeviceAdapter() { - return getParentAdapter().getParentAdapter(); - } - - /** - * Get parent IED - * @return ControlBlock's parent IEDAdapter - */ - public IEDAdapter getParentIedAdapter() { - return getParentAdapter().getParentIed(); - } - - /** - * Get SCL Root - * @return sclRootAdapter - */ - private SclRootAdapter getSclRootAdapter() { - return getParentIedAdapter().getParentAdapter(); - } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ControlBlockEnum.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ControlBlockEnum.java index 23cb0bbcb..025b14992 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ControlBlockEnum.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ControlBlockEnum.java @@ -5,23 +5,23 @@ package org.lfenergy.compas.sct.commons.util; import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.lfenergy.compas.scl2007b4.model.*; +import org.lfenergy.compas.sct.commons.model.cbcom.TCBType; import java.util.Arrays; import java.util.Objects; @Getter +@RequiredArgsConstructor public enum ControlBlockEnum { - GSE(TGSEControl.class), - SAMPLED_VALUE(TSampledValueControl.class), - REPORT(TReportControl.class), - LOG(TLogControl.class); + GSE(TGSEControl.class, "GSEControl"), + SAMPLED_VALUE(TSampledValueControl.class, "SampledValueControl"), + REPORT(TReportControl.class, "ReportControl"), + LOG(TLogControl.class, "LogControl"); private final Class controlBlockClass; - - ControlBlockEnum(Class controlBlockClass) { - this.controlBlockClass = controlBlockClass; - } + private final String elementName; public static ControlBlockEnum from(TServiceType tServiceType) { Objects.requireNonNull(tServiceType); @@ -40,4 +40,12 @@ public static ControlBlockEnum from(Class tControlClass) { .orElseThrow(() -> new IllegalArgumentException("Unsupported TControl class : " + tControlClass.getSimpleName())); } + public static ControlBlockEnum from(TCBType tcbType) { + return switch (tcbType){ + case GOOSE -> GSE; + case SV -> SAMPLED_VALUE; + default -> throw new IllegalArgumentException("Unsupported TCBType: " + tcbType); + }; + } + } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ControlBlockNetworkSettingsCsvHelper.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ControlBlockNetworkSettingsCsvHelper.java deleted file mode 100644 index dc4b2ff6a..000000000 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ControlBlockNetworkSettingsCsvHelper.java +++ /dev/null @@ -1,204 +0,0 @@ -// SPDX-FileCopyrightText: 2023 RTE FRANCE -// -// SPDX-License-Identifier: Apache-2.0 - -package org.lfenergy.compas.sct.commons.util; - -import com.opencsv.bean.CsvBindByPosition; -import lombok.ToString; -import org.apache.commons.lang3.StringUtils; -import org.lfenergy.compas.scl2007b4.model.*; -import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings; -import org.lfenergy.compas.sct.commons.scl.ied.ControlBlockAdapter; -import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; - -import java.io.Reader; -import java.math.BigInteger; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - * This class is an implementation example for interface ControlBlockNetworkSettings. - * It relies on a CSV file. - * The first columns of the CSV file are the criteria to match the ControlBlock (controlBlockEnum, systemVersionWithoutV, iedType, iedRedundancy, - * isBayInternal), - * The last columns are the network settings for the matched ControlBlock (as described in {@link ControlBlockNetworkSettings.Settings}). - * - * @see CsvUtils - */ -public class ControlBlockNetworkSettingsCsvHelper implements ControlBlockNetworkSettings { - - private static final int MAX_VLAN_ID = 0x0FFF; - private static final int MAX_VLAN_PRIORITY = 7; - private static final String NONE = "none"; - - private final Map allSettings; - - /** - * Constructor - * Provide the CSV file as a Reader. For example, you can create a reader like this : - * new InputStreamReader(getClass().getClassLoader().getResourceAsStream(fileName), StandardCharsets.UTF_8); - * - * @param csvSource a reader that provides the data as CSV. For example : - */ - public ControlBlockNetworkSettingsCsvHelper(Reader csvSource) { - allSettings = readCsvFile(csvSource); - } - - private Map readCsvFile(Reader csvSource) { - return CsvUtils.parseRows(csvSource, Row.class).stream() - .distinct() - .collect(Collectors.toMap( - ControlBlockNetworkSettingsCsvHelper::rowToCriteria, - ControlBlockNetworkSettingsCsvHelper::rowToSetting - )); - } - - @Override - public SettingsOrError getNetworkSettings(ControlBlockAdapter controlBlockAdapter) { - ControlBlockEnum controlBlockEnum = controlBlockAdapter.getControlBlockEnum(); - IEDAdapter iedAdapter = controlBlockAdapter.getParentIedAdapter(); - Optional compasSystemVersion = iedAdapter.getCompasSystemVersion(); - if (compasSystemVersion.isEmpty()) { - return new SettingsOrError(null, "No private COMPAS-SystemVersion found in this IED"); - } - String systemVersionWithoutV = removeVFromSystemVersion(compasSystemVersion.get()); - Optional compasICDHeader = iedAdapter.getCompasICDHeader(); - if (compasICDHeader.isEmpty()) { - return new SettingsOrError(null, "No private COMPAS-ICDHeader found in this IED"); - } - TCompasIEDType iedType = compasICDHeader.get().getIEDType(); - TCompasIEDRedundancy iedRedundancy = compasICDHeader.get().getIEDredundancy(); - BigInteger iedSystemVersionInstance = compasICDHeader.get().getIEDSystemVersioninstance(); - boolean isBayInternal = controlBlockAdapter.getName().endsWith("I"); - - Criteria criteria = new Criteria(controlBlockEnum, systemVersionWithoutV, iedType, iedRedundancy, iedSystemVersionInstance, isBayInternal); - Settings settings = findSettings(criteria); - return settings != null ? - new SettingsOrError(settings, null) : - new SettingsOrError(null, "No row found with these criteria " + criteria); - } - - private Settings findSettings(Criteria criteria) { - Objects.requireNonNull(criteria); - if (criteria.systemVersionWithoutV() == null - || criteria.iedType() == null - || criteria.iedRedundancy() == null - || criteria.iedSystemVersionInstance == null) { - return null; - } - return allSettings.get(criteria); - } - - private static String removeVFromSystemVersion(TCompasSystemVersion compasSystemVersion) { - if (StringUtils.isBlank(compasSystemVersion.getMainSystemVersion()) - || (StringUtils.isBlank(compasSystemVersion.getMinorSystemVersion()))) { - return null; - } - String[] minorVersionParts = compasSystemVersion.getMinorSystemVersion().split("\\."); - return (minorVersionParts.length == 3) ? - compasSystemVersion.getMainSystemVersion() + "." + minorVersionParts[0] + "." + minorVersionParts[1] - : null; - } - - private static Criteria rowToCriteria(Row row) { - if (StringUtils.isBlank(row.cbType) - || StringUtils.isBlank(row.xy) - || StringUtils.isBlank(row.zw) - || StringUtils.isBlank(row.iedType) - || StringUtils.isBlank(row.iedRedundancy) - || StringUtils.isBlank(row.iedSystemVersionInstance) - || StringUtils.isBlank(row.bindingType) - ) { - throw new IllegalArgumentException("At least one criteria is null in row " + row); - } - ControlBlockEnum controlBlockEnum = switch (row.cbType) { - case "GOOSE" -> ControlBlockEnum.GSE; - case "SV" -> ControlBlockEnum.SAMPLED_VALUE; - default -> throw new IllegalArgumentException("Unsupported Control Block Type : " + row.cbType); - }; - return new Criteria( - controlBlockEnum, - row.xy + "." + row.zw, - TCompasIEDType.fromValue(row.iedType), - TCompasIEDRedundancy.fromValue(row.iedRedundancy), - new BigInteger(row.iedSystemVersionInstance), - row.bindingType.equals("BAY_INTERNAL") - ); - } - - private static Settings rowToSetting(Row row) { - Integer vlanId = toVLanId(row.vlanId); - Byte vlanPriority = toVlanPriority(row.vlanPriority); - TDurationInMilliSec minTime = toDurationInMilliSec(row.minTime); - TDurationInMilliSec maxTime = toDurationInMilliSec(row.maxTime); - return new Settings(vlanId, vlanPriority, minTime, maxTime); - } - - private static Byte toVlanPriority(String strVlanPriority) { - if (StringUtils.isBlank(strVlanPriority) || NONE.equalsIgnoreCase(strVlanPriority)) { - return null; - } - byte vlanPriority = Byte.parseByte(strVlanPriority); - if (vlanPriority < 0 || vlanPriority > MAX_VLAN_PRIORITY) { - throw new IllegalArgumentException("VLAN PRIORITY must be between 0 and %d, but got : %d".formatted(MAX_VLAN_PRIORITY, vlanPriority)); - } - return vlanPriority; - } - - private static Integer toVLanId(String strVlanId) { - if (StringUtils.isBlank(strVlanId) || NONE.equalsIgnoreCase(strVlanId)) { - return null; - } - int vlanId = Integer.parseInt(strVlanId); - if (vlanId < 0 || vlanId > MAX_VLAN_ID) { - throw new IllegalArgumentException("VLAN ID must be between 0 and %d, but got : %d".formatted(MAX_VLAN_ID, vlanId)); - } - return vlanId; - } - - private static TDurationInMilliSec toDurationInMilliSec(String duration) { - if (StringUtils.isBlank(duration) || NONE.equalsIgnoreCase(duration)) { - return null; - } - return SclConstructorHelper.newDurationInMilliSec(Long.parseLong(duration)); - } - - private record Criteria( - ControlBlockEnum controlBlockEnum, - String systemVersionWithoutV, - TCompasIEDType iedType, - TCompasIEDRedundancy iedRedundancy, - BigInteger iedSystemVersionInstance, - boolean isBayInternal) { - } - - @ToString - public static class Row { - @CsvBindByPosition(position = 0) - private String cbType; - @CsvBindByPosition(position = 1) - private String xy; - @CsvBindByPosition(position = 2) - private String zw; - @CsvBindByPosition(position = 3) - private String iedType; - @CsvBindByPosition(position = 4) - private String iedRedundancy; - @CsvBindByPosition(position = 5) - private String iedSystemVersionInstance; - @CsvBindByPosition(position = 6) - private String bindingType; - @CsvBindByPosition(position = 7) - private String vlanId; - @CsvBindByPosition(position = 8) - private String vlanPriority; - @CsvBindByPosition(position = 9) - private String minTime; - @CsvBindByPosition(position = 10) - private String maxTime; - } - -} diff --git a/sct-commons/src/main/resources/xsd/GSE_SMV_CB_COM.xsd b/sct-commons/src/main/resources/xsd/GSE_SMV_CB_COM.xsd new file mode 100644 index 000000000..dbc9bbe3c --- /dev/null +++ b/sct-commons/src/main/resources/xsd/GSE_SMV_CB_COM.xsd @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IED type to be used to identity the set of LDevice.inst handled by the IED + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sct-commons/src/main/resources/xsd/LDEPF_Config_file_v2.xsd b/sct-commons/src/main/resources/xsd/LDEPF.xsd similarity index 100% rename from sct-commons/src/main/resources/xsd/LDEPF_Config_file_v2.xsd rename to sct-commons/src/main/resources/xsd/LDEPF.xsd diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ControlBlockServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ControlBlockEditorServiceTest.java similarity index 52% rename from sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ControlBlockServiceTest.java rename to sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ControlBlockEditorServiceTest.java index 9045762fa..3a720f6de 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ControlBlockServiceTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/ControlBlockEditorServiceTest.java @@ -7,16 +7,16 @@ import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; 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.*; -import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings; import org.lfenergy.compas.sct.commons.dto.ControlBlockTarget; import org.lfenergy.compas.sct.commons.dto.FcdaForDataSetsCreation; import org.lfenergy.compas.sct.commons.dto.SclReportItem; import org.lfenergy.compas.sct.commons.exception.ScdException; +import org.lfenergy.compas.sct.commons.model.cbcom.*; +import org.lfenergy.compas.sct.commons.scl.ControlService; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.scl.ied.DataSetAdapter; @@ -29,45 +29,33 @@ import org.lfenergy.compas.sct.commons.testhelpers.MarshallerWrapper; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; import org.lfenergy.compas.sct.commons.util.CsvUtils; -import org.mockito.InjectMocks; -import org.mockito.junit.jupiter.MockitoExtension; +import org.lfenergy.compas.sct.commons.util.PrivateEnum; +import org.lfenergy.compas.sct.commons.util.PrivateUtils; -import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.*; import static org.lfenergy.compas.scl2007b4.model.TFCEnum.ST; -import static org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.*; import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.*; import static org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller.assertIsMarshallable; import static org.lfenergy.compas.sct.commons.util.ControlBlockEnum.*; -import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newDurationInMilliSec; -@ExtendWith(MockitoExtension.class) -class ControlBlockServiceTest { +class ControlBlockEditorServiceTest { - @InjectMocks - ControlBlockService controlBlockService; + ControlBlockEditorService controlBlockEditorService; private Set allowedFcdas; - private static final long GSE_APP_ID_MIN = 0x9; - private static final long SMV_APP_ID_MIN = 0x400A; - private static final String GSE_MAC_ADDRESS_PREFIX = "01-02-03-04-"; - private static final String SMV_MAC_ADDRESS_PREFIX = "0A-0B-0C-0D-"; - private static final NetworkRanges GSE_NETWORK_RANGES = new NetworkRanges(GSE_APP_ID_MIN, GSE_APP_ID_MIN + 10, GSE_MAC_ADDRESS_PREFIX + "00-FF", GSE_MAC_ADDRESS_PREFIX + "01-AA"); - private static final NetworkRanges SMV_NETWORK_RANGES = new NetworkRanges(SMV_APP_ID_MIN, SMV_APP_ID_MIN + 10, SMV_MAC_ADDRESS_PREFIX + "00-FF", SMV_MAC_ADDRESS_PREFIX + "01-AA"); - private static final RangesPerCbType RANGES_PER_CB_TYPE = new RangesPerCbType(GSE_NETWORK_RANGES, SMV_NETWORK_RANGES); - - @BeforeEach void init() { + controlBlockEditorService = new ControlBlockEditorService(new ControlService()); allowedFcdas = new HashSet<>(CsvUtils.parseRows("FcdaCandidates.csv", StandardCharsets.UTF_8, FcdaForDataSetsCreation.class)); } @@ -82,7 +70,7 @@ void analyzeDataGroups_should_success() { iedAdapter1.getCurrentElem().getAccessPoint().get(0).getServices().getClientServices().setMaxSMV(2L); iedAdapter1.getCurrentElem().getAccessPoint().get(0).getServices().getClientServices().setMaxReports(1L); // When - List sclReportItems = controlBlockService.analyzeDataGroups(scd); + List sclReportItems = controlBlockEditorService.analyzeDataGroups(scd); //Then assertThat(sclReportItems).isEmpty(); @@ -100,7 +88,7 @@ void analyzeDataGroups_should_return_errors_messages() { iedAdapter.getCurrentElem().getAccessPoint().get(0).getServices().getGOOSE().setMax(2L); iedAdapter.getCurrentElem().getAccessPoint().get(0).getServices().getConfReportControl().setMax(0L); // When - List sclReportItems = controlBlockService.analyzeDataGroups(scd); + List sclReportItems = controlBlockEditorService.analyzeDataGroups(scd); //Then assertThat(sclReportItems).hasSize(11) .extracting(SclReportItem::message) @@ -118,13 +106,12 @@ void analyzeDataGroups_should_return_errors_messages() { "There are too much SMV Control Blocks for the IED IED_NAME2: 3 > 1 max"); } - @Test void removeControlBlocksAndDatasetAndExtRefSrc_should_remove_srcXXX_attributes_on_ExtRef() { // Given SCL scl = SclTestMarshaller.getSCLFromFile("/scl-remove-controlBlocks-dataSet-extRefSrc/scl-with-control-blocks.xml"); // When - controlBlockService.removeAllControlBlocksAndDatasetsAndExtRefSrcBindings(scl); + controlBlockEditorService.removeAllControlBlocksAndDatasetsAndExtRefSrcBindings(scl); // Then SclRootAdapter scdRootAdapter = new SclRootAdapter(scl); List extRefs = scdRootAdapter @@ -157,7 +144,7 @@ void createDataSetAndControlBlocks_should_Throw_Exception_when_list_allowed_fcda // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When Then - assertThatCode(() -> controlBlockService.createDataSetAndControlBlocks(scd, fcdaForDataSets)) + assertThatCode(() -> controlBlockEditorService.createDataSetAndControlBlocks(scd, fcdaForDataSets)) .isInstanceOf(ScdException.class) .hasMessage("Accepted FCDAs list is empty, you should initialize allowed FCDA lists with CsvHelper class before"); } @@ -167,7 +154,7 @@ void createDataSetAndControlBlocks_should_create_DataSet() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When - List sclReportItems = controlBlockService.createDataSetAndControlBlocks(scd, allowedFcdas); + List sclReportItems = controlBlockEditorService.createDataSetAndControlBlocks(scd, allowedFcdas); // Then assertThat(sclReportItems).isEmpty(); assertThat(streamAllDataSets(scd)).hasSize(6); @@ -198,7 +185,7 @@ void createDataSetAndControlBlocks_should_create_ControlBlocks() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When - List sclReportItems = controlBlockService.createDataSetAndControlBlocks(scd, allowedFcdas); + List sclReportItems = controlBlockEditorService.createDataSetAndControlBlocks(scd, allowedFcdas); // Then assertThat(sclReportItems).isEmpty(); @@ -231,7 +218,7 @@ void createDataSetAndControlBlocks_should_set_ExtRef_srcXXX_attributes() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When - List sclReportItems = controlBlockService.createDataSetAndControlBlocks(scd, allowedFcdas); + List sclReportItems = controlBlockEditorService.createDataSetAndControlBlocks(scd, allowedFcdas); // Then assertThat(sclReportItems).isEmpty(); @@ -261,7 +248,7 @@ void createDataSetAndControlBlocks_with_targetIedName_should_Throw_Exception_whe // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When Then - assertThatCode(() -> controlBlockService.createDataSetAndControlBlocks(scd, "IED_NAME1", fcdaForDataSets)) + assertThatCode(() -> controlBlockEditorService.createDataSetAndControlBlocks(scd, "IED_NAME1", fcdaForDataSets)) .isInstanceOf(ScdException.class) .hasMessage("Accepted FCDAs list is empty, you should initialize allowed FCDA lists with CsvHelper class before"); } @@ -271,7 +258,7 @@ void createDataSetAndControlBlocks_when_targetIedName_is_provided_should_succeed // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When - List sclReportItems = controlBlockService.createDataSetAndControlBlocks(scd, "IED_NAME1", allowedFcdas); + List sclReportItems = controlBlockEditorService.createDataSetAndControlBlocks(scd, "IED_NAME1", allowedFcdas); // Then assertThat(sclReportItems).isEmpty(); assertThat(streamAllDataSets(scd)).hasSize(6); @@ -287,7 +274,7 @@ void createDataSetAndControlBlocks_when_targetIedName_is_provided_and_no_ext_ref // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When - List sclReportItems = controlBlockService.createDataSetAndControlBlocks(scd, "IED_NAME2", allowedFcdas); + List sclReportItems = controlBlockEditorService.createDataSetAndControlBlocks(scd, "IED_NAME2", allowedFcdas); // Then assertThat(sclReportItems).isEmpty(); assertThat(streamAllDataSets(scd)).isEmpty(); @@ -298,7 +285,7 @@ void createDataSetAndControlBlocks_when_targetIedName_is_not_found_should_throw_ // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When & Then - assertThatThrownBy(() -> controlBlockService.createDataSetAndControlBlocks(scd, "non_existing_IED_name", allowedFcdas)) + assertThatThrownBy(() -> controlBlockEditorService.createDataSetAndControlBlocks(scd, "non_existing_IED_name", allowedFcdas)) .isInstanceOf(ScdException.class) .hasMessage("IED.name 'non_existing_IED_name' not found in SCD"); } @@ -309,7 +296,7 @@ void createDataSetAndControlBlocks_with_targetIedName_and_targetLDeviceInst_shou // Given SCL scd = new SCL(); // When Then - assertThatCode(() -> controlBlockService.createDataSetAndControlBlocks(scd, "IED_NAME1", "LD_INST11", fcdaForDataSets)) + assertThatCode(() -> controlBlockEditorService.createDataSetAndControlBlocks(scd, "IED_NAME1", "LD_INST11", fcdaForDataSets)) .isInstanceOf(ScdException.class) .hasMessage("Accepted FCDAs list is empty, you should initialize allowed FCDA lists with CsvHelper class before"); } @@ -319,7 +306,7 @@ void createDataSetAndControlBlocks_when_targetIedName_and_targetLDeviceInst_is_p // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When - List sclReportItems = controlBlockService.createDataSetAndControlBlocks(scd, "IED_NAME1", "LD_INST11", allowedFcdas); + List sclReportItems = controlBlockEditorService.createDataSetAndControlBlocks(scd, "IED_NAME1", "LD_INST11", allowedFcdas); // Then assertThat(sclReportItems).isEmpty(); } @@ -329,7 +316,7 @@ void createDataSetAndControlBlocks_when_targetIedName_is_not_found_and_targetLDe // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When & Then - assertThatThrownBy(() -> controlBlockService.createDataSetAndControlBlocks(scd, "non_existing_IED_name", "LD_INST11", allowedFcdas)) + assertThatThrownBy(() -> controlBlockEditorService.createDataSetAndControlBlocks(scd, "non_existing_IED_name", "LD_INST11", allowedFcdas)) .isInstanceOf(ScdException.class) .hasMessage("IED.name 'non_existing_IED_name' not found in SCD"); } @@ -339,7 +326,7 @@ void createDataSetAndControlBlocks_when_targetIedName_and_targetLDeviceInst_is_n // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When & Then - assertThatThrownBy(() -> controlBlockService.createDataSetAndControlBlocks(scd, "IED_NAME1", "non_existing_LDevice_inst", allowedFcdas)) + assertThatThrownBy(() -> controlBlockEditorService.createDataSetAndControlBlocks(scd, "IED_NAME1", "non_existing_LDevice_inst", allowedFcdas)) .isInstanceOf(ScdException.class) .hasMessage("LDevice.inst 'non_existing_LDevice_inst' not found in IED 'IED_NAME1'"); } @@ -349,19 +336,18 @@ void createDataSetAndControlBlocks_when_targetLDeviceInst_is_provided_without_ta // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When & Then - assertThatThrownBy(() -> controlBlockService.createDataSetAndControlBlocks(scd, null, "LD_INST11", allowedFcdas)) + assertThatThrownBy(() -> controlBlockEditorService.createDataSetAndControlBlocks(scd, null, "LD_INST11", allowedFcdas)) .isInstanceOf(ScdException.class) .hasMessage("IED.name parameter is missing"); } - @Test void updateAllSourceDataSetsAndControlBlocks_should_sort_FCDA_inside_DataSet_and_avoid_duplicates() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_test_fcda_sort.xml"); // When - List sclReportItems = controlBlockService.createDataSetAndControlBlocks(scd, allowedFcdas); + List sclReportItems = controlBlockEditorService.createDataSetAndControlBlocks(scd, allowedFcdas); // Then assertThat(sclReportItems).isEmpty(); DataSetAdapter dataSetAdapter = findDataSet(scd, "IED_NAME2", "LD_INST21", "DS_LD_INST21_GSI"); @@ -376,45 +362,73 @@ void updateAllSourceDataSetsAndControlBlocks_should_sort_FCDA_inside_DataSet_and } @Test - void configureNetworkForAllControlBlocks_should_create_GSE_and_SMV_elements() { + void configureNetworkForAllControlBlocks_should_create_GSE_elements() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + CBCom cbCom = createCbCom(); + // When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + // Then + assertThat(sclReportItems.stream().noneMatch(SclReportItem::isError)).isTrue(); + TGSE gse1 = getCommunicationGSE(scd, "IED_NAME2", "CB_LD_INST21_GSI"); + assertThat(gse1.getLdInst()).isEqualTo("LD_INST21"); + assertThat(SclDuration.from(gse1.getMinTime())).isEqualTo(new SclDuration("10", "s", "m")); + assertThat(SclDuration.from(gse1.getMaxTime())).isEqualTo(new SclDuration("2000", "s", "m")); + assertThat(gse1.getAddress().getP()).extracting(TP::getType, TP::getValue).containsExactlyInAnyOrder( + Tuple.tuple("VLAN-PRIORITY", "1"), + Tuple.tuple("APPID", "0000"), + Tuple.tuple("MAC-Address", "01-0C-CD-01-00-00"), + Tuple.tuple("VLAN-ID", "12D") + ); + TGSE gse2 = getCommunicationGSE(scd, "IED_NAME2", "CB_LD_INST21_GMI"); + assertThat(gse2.getLdInst()).isEqualTo("LD_INST21"); + assertThat(SclDuration.from(gse2.getMinTime())).isEqualTo(new SclDuration("10", "s", "m")); + assertThat(SclDuration.from(gse2.getMaxTime())).isEqualTo(new SclDuration("2000", "s", "m")); + assertThat(gse2.getAddress().getP()).extracting(TP::getType, TP::getValue).containsExactlyInAnyOrder( + Tuple.tuple("VLAN-PRIORITY", "1"), + Tuple.tuple("APPID", "0001"), + Tuple.tuple("MAC-Address", "01-0C-CD-01-00-01"), + Tuple.tuple("VLAN-ID", "12D") + ); + TGSE gse3 = getCommunicationGSE(scd, "IED_NAME3", "CB_LD_INST31_GSE"); + assertThat(gse3.getLdInst()).isEqualTo("LD_INST31"); + assertThat(SclDuration.from(gse3.getMinTime())).isEqualTo(new SclDuration("10", "s", "m")); + assertThat(SclDuration.from(gse3.getMaxTime())).isEqualTo(new SclDuration("2000", "s", "m")); + assertThat(gse3.getAddress().getP()).extracting(TP::getType, TP::getValue).containsExactlyInAnyOrder( + Tuple.tuple("VLAN-PRIORITY", "2"), + Tuple.tuple("APPID", "0002"), + Tuple.tuple("MAC-Address", "01-0C-CD-01-00-02"), + Tuple.tuple("VLAN-ID", "12E") + ); + MarshallerWrapper.assertValidateXmlSchema(scd); + } - TDurationInMilliSec minTime = newDurationInMilliSec(10); - TDurationInMilliSec maxTime = newDurationInMilliSec(2000); - ControlBlockNetworkSettings controlBlockNetworkSettings = controlBlockAdapter -> new SettingsOrError(new Settings(0x1D6, (byte) 4, minTime, maxTime), null); - + @Test + void configureNetworkForAllControlBlocks_should_create_SMV_elements() { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + CBCom cbCom = createCbCom(); // When - List sclReportItems = controlBlockService.configureNetworkForAllControlBlocks(scd, controlBlockNetworkSettings, RANGES_PER_CB_TYPE); + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); // Then assertThat(sclReportItems.stream().noneMatch(SclReportItem::isError)).isTrue(); - TConnectedAP connectedAP = new SclRootAdapter(scd).findConnectedApAdapter("IED_NAME2", "AP_NAME").get().getCurrentElem(); - TGSE gse = connectedAP.getGSE().stream() - .filter(tgse -> "CB_LD_INST21_GSI".equals(tgse.getCbName())) - .findFirst().get(); - assertThat(gse.getLdInst()).isEqualTo("LD_INST21"); - assertThat(gse.getMinTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) - .containsExactly("s", "m", new BigDecimal("10")); - assertThat(gse.getMaxTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) - .containsExactly("s", "m", new BigDecimal("2000")); - assertThat(gse.getAddress().getP()).extracting(TP::getType, TP::getValue) - .containsExactlyInAnyOrder( - Tuple.tuple("VLAN-PRIORITY", "4"), - Tuple.tuple("APPID", "0009"), - Tuple.tuple("MAC-Address", "01-02-03-04-00-FF"), - Tuple.tuple("VLAN-ID", "1D6") - ); - TSMV smv = connectedAP.getSMV().stream() - .filter(tsmv -> "CB_LD_INST21_SVI".equals(tsmv.getCbName())) - .findFirst().get(); - assertThat(smv.getLdInst()).isEqualTo("LD_INST21"); - assertThat(smv.getAddress().getP()).extracting(TP::getType, TP::getValue) - .containsExactlyInAnyOrder( - Tuple.tuple("VLAN-PRIORITY", "4"), - Tuple.tuple("APPID", "400A"), - Tuple.tuple("MAC-Address", "0A-0B-0C-0D-00-FF"), - Tuple.tuple("VLAN-ID", "1D6") - ); + TSMV smv1 = getCommunicationSMV(scd, "IED_NAME2", "CB_LD_INST21_SVI"); + assertThat(smv1.getLdInst()).isEqualTo("LD_INST21"); + assertThat(smv1.getAddress().getP()).extracting(TP::getType, TP::getValue).containsExactlyInAnyOrder( + Tuple.tuple("VLAN-PRIORITY", "3"), + Tuple.tuple("APPID", "4000"), + Tuple.tuple("MAC-Address", "01-0C-CD-04-00-00"), + Tuple.tuple("VLAN-ID", "12F") + ); + assertThat(sclReportItems.stream().noneMatch(SclReportItem::isError)).isTrue(); + TSMV smv2 = getCommunicationSMV(scd, "IED_NAME3", "CB_LD_INST31_SVE"); + assertThat(smv2.getLdInst()).isEqualTo("LD_INST31"); + assertThat(smv2.getAddress().getP()).extracting(TP::getType, TP::getValue).containsExactlyInAnyOrder( + Tuple.tuple("VLAN-PRIORITY", "4"), + Tuple.tuple("APPID", "4001"), + Tuple.tuple("MAC-Address", "01-0C-CD-04-00-01"), + Tuple.tuple("VLAN-ID", "130") + ); MarshallerWrapper.assertValidateXmlSchema(scd); } @@ -422,12 +436,13 @@ void configureNetworkForAllControlBlocks_should_create_GSE_and_SMV_elements() { void configureNetworkForAllControlBlocks_should_create_GSE_with_incremental_appid_and_mac_addresses() { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); - - TDurationInMilliSec minTime = newDurationInMilliSec(10); - TDurationInMilliSec maxTime = newDurationInMilliSec(2000); - ControlBlockNetworkSettings controlBlockNetworkSettings = controlBlockAdapter -> new SettingsOrError(new Settings(0x1D6, (byte) 4, minTime, maxTime), null); + CBCom cbCom = createCbCom(); + cbCom.getAppIdRanges().getAppIdRange().get(0).setStart("0009"); + cbCom.getAppIdRanges().getAppIdRange().get(0).setEnd("000B"); + cbCom.getMacRanges().getMacRange().get(0).setStart("01-02-03-04-00-FF"); + cbCom.getMacRanges().getMacRange().get(0).setEnd("01-02-03-04-01-01"); // When - List sclReportItems = controlBlockService.configureNetworkForAllControlBlocks(scd, controlBlockNetworkSettings, RANGES_PER_CB_TYPE); + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); // Then assertThat(sclReportItems.stream().noneMatch(SclReportItem::isError)).isTrue(); assertThat(streamAllConnectedApGseP(scd, "APPID")) @@ -438,19 +453,160 @@ void configureNetworkForAllControlBlocks_should_create_GSE_with_incremental_appi @ParameterizedTest @MethodSource("provideConfigureNetworkForAllControlBlocksErrors") - void configureNetworkForAllControlBlocks_should_fail_when_no_settings_for_this_controlBlock(ControlBlockNetworkSettings controlBlockNetworkSettings, - RangesPerCbType rangesPerCbType, - String expectedMessage) { + void configureNetworkForAllControlBlocks_should_fail_when_no_settings_for_this_controlBlock(CBCom cbCom, String expectedMessage, String expectedXPath) { // Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); // When - List sclReportItems = controlBlockService.configureNetworkForAllControlBlocks(scd, controlBlockNetworkSettings, rangesPerCbType); + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); // Then assertThat(sclReportItems.stream().noneMatch(SclReportItem::isError)).isFalse(); assertThat(sclReportItems) - .extracting(SclReportItem::message, SclReportItem::xpath) - .contains(Tuple.tuple(expectedMessage, - "/SCL/IED[@name=\"IED_NAME2\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST21\"]/LN0/GSEControl[@name=\"CB_LD_INST21_GMI\"]")); + .contains(SclReportItem.error(expectedXPath, expectedMessage)); + } + + public static Stream provideConfigureNetworkForAllControlBlocksErrors() { + CBCom cbComWithNoVlan = createCbCom(); + cbComWithNoVlan.getVlans().getVlan().clear(); + CBCom cbComWithMissingVlanId = createCbCom(); + cbComWithMissingVlanId.getVlans().getVlan().get(0).setVlanId(null); + CBCom cbComWithNotEnoughAppId = createCbCom(); + cbComWithNotEnoughAppId.getAppIdRanges().getAppIdRange().get(0).setStart("0000"); + cbComWithNotEnoughAppId.getAppIdRanges().getAppIdRange().get(0).setEnd("00001"); + CBCom cbComWithNotEnoughMacAddress = createCbCom(); + cbComWithNotEnoughMacAddress.getMacRanges().getMacRange().get(0).setStart("01-0C-CD-01-00-00"); + cbComWithNotEnoughMacAddress.getMacRanges().getMacRange().get(0).setEnd("01-0C-CD-01-00-01"); + + return Stream.of( + Arguments.of(cbComWithNoVlan, "Cannot configure communication for this ControlBlock because: No controlBlock communication settings found with these Criteria[cbType=GOOSE, systemVersionWithoutV=01.00.009.001, iedType=BCU, iedRedundancy=A, iedSystemVersionInstance=1, bayIntOrExt=BAY_INTERNAL]", + "/SCL/IED[@name=\"IED_NAME2\"]/AccessPoint[@name=\"AP_NAME\"]/Server/LDevice[@inst=\"LD_INST21\"]/LN0/GSEControl[@name=\"CB_LD_INST21_GMI\"]"), + Arguments.of(cbComWithMissingVlanId, "Cannot configure communication for this ControlBlock because no Vlan Id was provided in the settings", + "/SCL/IED[@name=\"IED_NAME2\"]/AccessPoint[@name=\"AP_NAME\"]/Server/LDevice[@inst=\"LD_INST21\"]/LN0/GSEControl[@name=\"CB_LD_INST21_GMI\"]"), + Arguments.of(cbComWithNotEnoughAppId, "Cannot configure communication for this ControlBlock because range of appId is exhausted", + "/SCL/IED[@name=\"IED_NAME3\"]/AccessPoint[@name=\"AP_NAME\"]/Server/LDevice[@inst=\"LD_INST31\"]/LN0/GSEControl[@name=\"CB_LD_INST31_GSE\"]"), + Arguments.of(cbComWithNotEnoughMacAddress, "Cannot configure communication for this ControlBlock because range of MAC Address is exhausted", + "/SCL/IED[@name=\"IED_NAME3\"]/AccessPoint[@name=\"AP_NAME\"]/Server/LDevice[@inst=\"LD_INST31\"]/LN0/GSEControl[@name=\"CB_LD_INST31_GSE\"]") + ); + } + + @ParameterizedTest + @MethodSource("provideBlankCriteria") + void configureNetworkForAllControlBlocks_when_setting_files_has_blank_criteria_should_return_error(Consumer setCriteriaBlank) { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + CBCom cbCom = createCbCom(); + setCriteriaBlank.accept(cbCom.getVlans().getVlan().get(0)); + //When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + //Then + assertThat(sclReportItems).hasSize(1); + assertThat(sclReportItems.get(0)).extracting(SclReportItem::isError, SclReportItem::xpath) + .containsExactly(true, "Control Block Communication setting files"); + assertThat(sclReportItems.get(0).message()).matches("Error in Control Block communication setting file: vlan is missing attribute .*"); + } + + private static Stream provideBlankCriteria() { + return Stream.of(Arguments.of( + (Consumer) tVlan -> tVlan.setXY(null), + (Consumer) tVlan -> tVlan.setZW(null), + (Consumer) tVlan -> tVlan.setIEDType(null), + (Consumer) tVlan -> tVlan.setIEDRedundancy(null), + (Consumer) tVlan -> tVlan.setIEDSystemVersionInstance(null), + (Consumer) tVlan -> tVlan.setBayIntOrExt(null) + )); + } + + @ParameterizedTest + @MethodSource("provideMalformedNumbers") + void configureNetworkForAllControlBlocks_when_malformed_numbers_should_return_error(Consumer setMalformedNumber) { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + CBCom cbCom = createCbCom(); + setMalformedNumber.accept(cbCom.getVlans().getVlan().get(0)); + //When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + //Then + assertThat(sclReportItems).hasSize(1); + assertThat(sclReportItems.get(0)).extracting(SclReportItem::isError, SclReportItem::xpath) + .containsExactly(true, "Control Block Communication setting files"); + assertThat(sclReportItems.get(0).message()).matches("Error in Control Block communication setting file: .+ must be an integer( or 'none')?, but got : XXX"); + } + + private static Stream provideMalformedNumbers() { + return Stream.of( + Arguments.of((Consumer) tVlan -> tVlan.setIEDSystemVersionInstance("XXX")), + Arguments.of((Consumer) tVlan -> tVlan.setVlanId("XXX")), + Arguments.of((Consumer) tVlan -> tVlan.setVlanPriority("XXX")), + Arguments.of((Consumer) tVlan -> tVlan.setMinTime("XXX")), + Arguments.of((Consumer) tVlan -> tVlan.setMaxTime("XXX")) + ); + } + + @ParameterizedTest + @MethodSource("provideOutOfBoundNumbers") + void configureNetworkForAllControlBlocks_when_out_of_bound_numbers_should_return_error(Consumer setMalformedNumber) { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + CBCom cbCom = createCbCom(); + setMalformedNumber.accept(cbCom.getVlans().getVlan().get(0)); + //When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + //Then + assertThat(sclReportItems).hasSize(1); + assertThat(sclReportItems.get(0)).extracting(SclReportItem::isError, SclReportItem::xpath) + .containsExactly(true, "Control Block Communication setting files"); + assertThat(sclReportItems.get(0).message()).matches("Error in Control Block communication setting file: VLAN (ID|PRIORITY) must be between 0 and [0-9]+, but got : .*"); + } + + private static Stream provideOutOfBoundNumbers() { + return Stream.of( + Arguments.of((Consumer) tVlan -> tVlan.setVlanId("4096")), // VlanId > MAX_VLAN_ID + Arguments.of((Consumer) tVlan -> tVlan.setVlanId("-1")), // VlanId < 0 + Arguments.of((Consumer) tVlan -> tVlan.setVlanPriority("8")), // VlanPriority > MAX_VLAN_PRIORITY + Arguments.of((Consumer) tVlan -> tVlan.setVlanPriority("-1")) // VlanPriority < 0 + ); + } + + @Test + void configureNetworkForAllControlBlocks_when_missing_connectedAp_should_return_error() { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + scd.getCommunication().getSubNetwork().get(0).getConnectedAP().remove(0); + CBCom cbCom = createCbCom(); + //When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + //Then + assertThat(sclReportItems).hasSize(3); + assertThat(sclReportItems).extracting(SclReportItem::isError).containsOnly(true); + assertThat(sclReportItems).extracting(SclReportItem::message).containsOnly("Cannot configure communication for ControlBlock because no ConnectedAP found for AccessPoint"); + assertThat(sclReportItems).extracting(SclReportItem::xpath).allMatch(xpath -> xpath.startsWith(""" + /SCL/IED[@name="IED_NAME2"]/AccessPoint[@name="AP_NAME"]/Server/LDevice[@inst="LD_INST21"]/LN0/""")); + } + + @ParameterizedTest + @MethodSource("provideRemovePrivateInfo") + void configureNetworkForAllControlBlocks_when_missing_IED_privates_should_return_error(Consumer removePrivateInfo) { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + CBCom cbCom = createCbCom(); + removePrivateInfo.accept(scd.getIED().get(1)); + //When + List sclReportItems = controlBlockEditorService.configureNetworkForAllControlBlocks(scd, cbCom); + //Then + assertThat(sclReportItems).hasSize(3); + assertThat(sclReportItems).extracting(SclReportItem::isError, SclReportItem::xpath) + .containsOnly(Tuple.tuple(true, """ + /SCL/IED[@name="IED_NAME2"]/AccessPoint[@name="AP_NAME"]/Server/LDevice[@inst="LD_INST21"]""")); + assertThat(sclReportItems).extracting(SclReportItem::message).allSatisfy(message -> assertThat(message).containsAnyOf("COMPAS-ICDHeader", "COMPAS-SystemVersion")); + } + + private static Stream provideRemovePrivateInfo() { + return Stream.of( + Arguments.of((Consumer) tied -> PrivateUtils.removePrivates(tied, PrivateEnum.COMPAS_SYSTEM_VERSION)), + Arguments.of((Consumer) tied -> PrivateUtils.extractCompasPrivate(tied, TCompasSystemVersion.class).ifPresent(tCompasSystemVersion -> tCompasSystemVersion.setMainSystemVersion(null))), + Arguments.of((Consumer) tied -> PrivateUtils.extractCompasPrivate(tied, TCompasSystemVersion.class).ifPresent(tCompasSystemVersion -> tCompasSystemVersion.setMinorSystemVersion(null))), + Arguments.of((Consumer) tied -> PrivateUtils.removePrivates(tied, PrivateEnum.COMPAS_ICDHEADER)), + Arguments.of((Consumer) tied -> PrivateUtils.extractCompasPrivate(tied, TCompasICDHeader.class).ifPresent(tCompasICDHeader -> tCompasICDHeader.setIEDSystemVersioninstance(null))) + ); } @Test @@ -458,7 +614,7 @@ void removeControlBlocksAndDatasetAndExtRefSrc_should_remove_controlBlocks_and_D // Given SCL scl = SclTestMarshaller.getSCLFromFile("/scl-remove-controlBlocks-dataSet-extRefSrc/scl-with-control-blocks.xml"); // When - controlBlockService.removeAllControlBlocksAndDatasetsAndExtRefSrcBindings(scl); + controlBlockEditorService.removeAllControlBlocksAndDatasetsAndExtRefSrcBindings(scl); // Then SclRootAdapter scdRootAdapter = new SclRootAdapter(scl); List lDevices = scdRootAdapter.streamIEDAdapters().flatMap(IEDAdapter::streamLDeviceAdapters).toList(); @@ -478,7 +634,7 @@ void removeControlBlocksAndDatasetAndExtRefSrc_should_remove_controlBlocks_and_D // Given SCL scl = SclTestMarshaller.getSCLFromFile("/scl-remove-controlBlocks-dataSet-extRefSrc/scl-with-control-blocks.xml"); // When - controlBlockService.removeAllControlBlocksAndDatasetsAndExtRefSrcBindings(scl); + controlBlockEditorService.removeAllControlBlocksAndDatasetsAndExtRefSrcBindings(scl); // Then SclRootAdapter scdRootAdapter = new SclRootAdapter(scl); List lns = scdRootAdapter.streamIEDAdapters() @@ -493,30 +649,70 @@ void removeControlBlocksAndDatasetAndExtRefSrc_should_remove_controlBlocks_and_D assertIsMarshallable(scl); } - public static Stream provideConfigureNetworkForAllControlBlocksErrors() { - Settings settingsWithNullVlanId = new Settings(null, (byte) 1, newDurationInMilliSec(1), newDurationInMilliSec(2)); - Settings settings = new Settings(1, (byte) 1, newDurationInMilliSec(1), newDurationInMilliSec(2)); - return Stream.of( - Arguments.of((ControlBlockNetworkSettings) controlBlockAdapter -> new SettingsOrError(null, null), - RANGES_PER_CB_TYPE, - "Cannot configure network for this ControlBlock because no settings was provided"), - Arguments.of((ControlBlockNetworkSettings) controlBlockAdapter -> new SettingsOrError(null, "Custom error message"), - RANGES_PER_CB_TYPE, - "Cannot configure network for this ControlBlock because: Custom error message"), - Arguments.of((ControlBlockNetworkSettings) controlBlockAdapter -> new SettingsOrError(settingsWithNullVlanId, null), - RANGES_PER_CB_TYPE, - "Cannot configure network for this ControlBlock because no Vlan Id was provided in the settings"), - Arguments.of((ControlBlockNetworkSettings) controlBlockAdapter -> new SettingsOrError(settings, null), - new RangesPerCbType( - new NetworkRanges(GSE_APP_ID_MIN, GSE_APP_ID_MIN, GSE_MAC_ADDRESS_PREFIX + "00-FF", GSE_MAC_ADDRESS_PREFIX + "01-AA"), - SMV_NETWORK_RANGES), - "Cannot configure network for this ControlBlock because range of appId is exhausted"), - Arguments.of((ControlBlockNetworkSettings) controlBlockAdapter -> new SettingsOrError(settings, null), - new RangesPerCbType( - new NetworkRanges(GSE_APP_ID_MIN, GSE_APP_ID_MIN + 10, GSE_MAC_ADDRESS_PREFIX + "00-FF", GSE_MAC_ADDRESS_PREFIX + "00-FF"), - SMV_NETWORK_RANGES), - "Cannot configure network for this ControlBlock because range of MAC Address is exhausted") - ); + private static TGSE getCommunicationGSE(SCL scd, String iedName, String cbName) { + return new SclRootAdapter(scd).findConnectedApAdapter(iedName, "AP_NAME").orElseThrow() + .getCurrentElem() + .getGSE().stream() + .filter(tgse -> cbName.equals(tgse.getCbName())) + .findFirst().orElseThrow(); + } + + private static TSMV getCommunicationSMV(SCL scd, String iedName, String cbName) { + return new SclRootAdapter(scd).findConnectedApAdapter(iedName, "AP_NAME").orElseThrow() + .getCurrentElem() + .getSMV().stream() + .filter(tsmv -> cbName.equals(tsmv.getCbName())) + .findFirst().orElseThrow(); + } + + private static CBCom createCbCom() { + CBCom cbCom = new CBCom(); + cbCom.setMacRanges(new MacRanges()); + cbCom.setAppIdRanges(new AppIdRanges()); + cbCom.setVlans(new Vlans()); + cbCom.getMacRanges().getMacRange().add(newRange(TCBType.GOOSE, "01-0C-CD-01-00-00", "01-0C-CD-01-01-FF")); + cbCom.getMacRanges().getMacRange().add(newRange(TCBType.SV, "01-0C-CD-04-00-00", "01-0C-CD-04-FF-FF")); + cbCom.getAppIdRanges().getAppIdRange().add(newRange(TCBType.GOOSE, "0000", "4000")); + cbCom.getAppIdRanges().getAppIdRange().add(newRange(TCBType.SV, "4000", "7FFF")); + cbCom.getVlans().getVlan().addAll(List.of( + newVlan(TCBType.GOOSE, TBayIntOrExt.BAY_INTERNAL, "301", "1"), + newVlan(TCBType.GOOSE, TBayIntOrExt.BAY_EXTERNAL, "302", "2"), + newVlan(TCBType.SV, TBayIntOrExt.BAY_INTERNAL, "303", "3"), + newVlan(TCBType.SV, TBayIntOrExt.BAY_EXTERNAL, "304", "4") + )); + return cbCom; } + private static TRange newRange(TCBType tcbType, String start, String end) { + TRange macRangeGSE = new TRange(); + macRangeGSE.setCBType(tcbType); + macRangeGSE.setStart(start); + macRangeGSE.setEnd(end); + return macRangeGSE; + } + + private static TVlan newVlan(TCBType tcbType, TBayIntOrExt tBayIntOrExt, String vlanId, String vlanPriority) { + TVlan gseVlan = new TVlan(); + gseVlan.setCBType(tcbType); + gseVlan.setXY("01.00"); + gseVlan.setZW("009.001"); + gseVlan.setIEDType(TIEDType.BCU); + gseVlan.setIEDRedundancy(TIEDRedundancy.A); + gseVlan.setIEDSystemVersionInstance("1"); + gseVlan.setBayIntOrExt(tBayIntOrExt); + gseVlan.setVlanId(vlanId); + gseVlan.setVlanPriority(vlanPriority); + gseVlan.setMinTime("10"); + gseVlan.setMaxTime("2000"); + return gseVlan; + } + + /** + * Help comparing TDurationInMilliSec + */ + record SclDuration(String value, String unit, String multiplier) { + static SclDuration from(TDurationInMilliSec tDurationInMilliSec) { + return new SclDuration(tDurationInMilliSec.getValue().toString(), tDurationInMilliSec.getUnit(), tDurationInMilliSec.getMultiplier()); + } + } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettingsTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettingsTest.java deleted file mode 100644 index 89450f8d6..000000000 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettingsTest.java +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-FileCopyrightText: 2023 RTE FRANCE -// -// SPDX-License-Identifier: Apache-2.0 - -package org.lfenergy.compas.sct.commons.dto; - -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.EnumSource; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.scl2007b4.model.TDurationInMilliSec; -import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.SettingsOrError; -import org.lfenergy.compas.sct.commons.util.PrivateUtils; -import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; -import org.lfenergy.compas.sct.commons.scl.ied.ControlBlockAdapter; -import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; -import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; -import org.lfenergy.compas.sct.commons.util.ControlBlockEnum; -import org.lfenergy.compas.sct.commons.util.ControlBlockNetworkSettingsCsvHelper; -import org.lfenergy.compas.sct.commons.util.CsvUtils; -import org.lfenergy.compas.sct.commons.util.PrivateEnum; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.StringReader; -import java.math.BigDecimal; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.Settings; -import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findControlBlock; -import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findIed; - -class ControlBlockNetworkSettingsTest { - - private ControlBlockNetworkSettings controlBlockNetworkSettings; - - @BeforeEach - public void setUp() { - String fileName = "ControlBlockCommunicationTemplates.csv"; - InputStream inputStream = Objects.requireNonNull(CsvUtils.class.getClassLoader().getResourceAsStream(fileName), "Resource not found: " + fileName); - InputStreamReader inputStreamReader = new InputStreamReader(inputStream); - controlBlockNetworkSettings = new ControlBlockNetworkSettingsCsvHelper(inputStreamReader); - } - - @ParameterizedTest - @ValueSource(strings = { - ";01.00;009.001;BCU;A;1;BAY_INTERNAL;300;4;10;2000", - "GOOSE;;009.001;BCU;A;1;BAY_INTERNAL;300;4;10;2000", - "GOOSE;01.00;;BCU;A;1;BAY_INTERNAL;300;4;10;2000", - "GOOSE;01.00;009.001;;A;1;BAY_INTERNAL;300;4;10;2000", - "GOOSE;01.00;009.001;BCU;;1;BAY_INTERNAL;300;4;10;2000", - "GOOSE;01.00;009.001;BCU;A;;BAY_INTERNAL;300;4;10;2000", - "GOOSE;01.00;009.001;BCU;A;1;;300;4;10;2000" - }) - void constructor_when_csv_has_blank_criteria_cells_should_throw_exception(String row) { - //Given - StringReader stringReader = new StringReader(row); - //When & Then - assertThatThrownBy(() -> new ControlBlockNetworkSettingsCsvHelper(stringReader)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageStartingWith("At least one criteria is null in row ControlBlockNetworkSettingsCsvHelper.Row") - .hasMessageContaining("=null,"); - } - - @ParameterizedTest - @ValueSource(strings = { - "GOOSE;01.00;009.001;BCU;A;XXX;BAY_INTERNAL;300;4;10;2000", - "GOOSE;01.00;009.001;BCU;A;1;BAY_INTERNAL;XXX;4;10;2000", - "GOOSE;01.00;009.001;BCU;A;1;BAY_INTERNAL;300;XXX;10;2000", - "GOOSE;01.00;009.001;BCU;A;1;BAY_INTERNAL;300;4;XXX;2000", - "GOOSE;01.00;009.001;BCU;A;1;BAY_INTERNAL;300;4;10;XXX" - }) - void constructor_when_csv_has_malformed_numbers_should_throw_exception(String row) { - //Given - StringReader stringReader = new StringReader(row); - //When & Then - assertThatThrownBy(() -> new ControlBlockNetworkSettingsCsvHelper(stringReader)) - .isInstanceOf(NumberFormatException.class) - .hasMessage("For input string: \"XXX\""); - } - - @ParameterizedTest - @ValueSource(strings = { - "GOOSE;01.00;009.001;BCU;A;1;BAY_INTERNAL;4096;4;10;2000", // VlanId > MAX_VLAN_ID - "GOOSE;01.00;009.001;BCU;A;1;BAY_INTERNAL;-1;4;10;2000", // VlanId < 0 - "GOOSE;01.00;009.001;BCU;A;1;BAY_INTERNAL;300;8;10;2000", // VlanPriority > MAX_VLAN_PRIORITY - "GOOSE;01.00;009.001;BCU;A;1;BAY_INTERNAL;300;-1;10;2000" // VlanPriority < 0 - }) - void constructor_when_csv_has_numbers_our_out_of_bound_should_throw_exception(String row) { - //Given - StringReader stringReader = new StringReader(row); - //When & Then - assertThatThrownBy(() -> new ControlBlockNetworkSettingsCsvHelper(stringReader)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("must be between 0 and "); - } - - @Test - void constructor_when_unsupported_cbType_should_throw_exception() { - //Given - StringReader stringReader = new StringReader("CUSTOM_CB_TYPE;01.00;009.001;BCU;A;1;BAY_INTERNAL;1;4;10;2000"); - //When & Then - assertThatThrownBy(() -> new ControlBlockNetworkSettingsCsvHelper(stringReader)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Unsupported Control Block Type : CUSTOM_CB_TYPE"); - } - - @Test - void getNetworkSettings_should_return_settings_for_bay_internal_controlBlock() { - //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); - ControlBlockAdapter controlBlockAdapter = findControlBlock(scd, "IED_NAME2", "LD_INST21", "CB_LD_INST21_GSI", ControlBlockEnum.GSE); - - //When - SettingsOrError settingsOrError = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); - - //Then - assertThat(settingsOrError.errorMessage()).isNull(); - Settings networkSettings = settingsOrError.settings(); - assertThat(networkSettings) - .extracting(Settings::vlanId, Settings::vlanPriority) - .containsExactly(300, (byte) 4); - assertThat(networkSettings.minTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) - .containsExactly("s", "m", new BigDecimal("10")); - assertThat(networkSettings.maxTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) - .containsExactly("s", "m", new BigDecimal("2000")); - } - - @Test - void getNetworkSettings_should_return_settings_for_bay_external_controlBlock() { - //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); - ControlBlockAdapter controlBlockAdapter = findControlBlock(scd, "IED_NAME3", "LD_INST31", "CB_LD_INST31_GSE", ControlBlockEnum.GSE); - - //When - SettingsOrError settingsOrError = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); - - //Then - assertThat(settingsOrError.errorMessage()).isNull(); - Settings networkSettings = settingsOrError.settings(); - assertThat(networkSettings) - .extracting(Settings::vlanId, Settings::vlanPriority) - .containsExactly(301, (byte) 5); - assertThat(networkSettings.minTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) - .containsExactly("s", "m", new BigDecimal("15")); - assertThat(networkSettings.maxTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) - .containsExactly("s", "m", new BigDecimal("5000")); - } - - @Test - void getNetworkSettings_should_return_vlanId_null_when_column_contains_none() { - //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); - ControlBlockAdapter controlBlockAdapter = findControlBlock(scd, "IED_NAME2", "LD_INST21", "CB_LD_INST21_SVI", ControlBlockEnum.SAMPLED_VALUE); - - //When - SettingsOrError settingsOrError = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); - - //Then - assertThat(settingsOrError.errorMessage()).isNull(); - Settings networkSettings = settingsOrError.settings(); - assertThat(networkSettings.vlanId()).isNull(); - assertThat(networkSettings.vlanPriority()).isNull(); - } - - @Test - void getNetworkSettings_should_return_null_when_row_not_found_in_csv_file() { - //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - findIed(sclRootAdapter.getCurrentElem(), "IED_NAME2").getCompasSystemVersion().get().setMainSystemVersion("99.99"); - ControlBlockAdapter controlBlockAdapter = findControlBlock(sclRootAdapter.getCurrentElem(), "IED_NAME2", "LD_INST21", "CB_LD_INST21_GSI", ControlBlockEnum.GSE); - - //When - SettingsOrError settingsOrError = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); - - //Then - assertThat(settingsOrError.errorMessage()).isEqualTo("No row found with these criteria Criteria[controlBlockEnum=GSE, systemVersionWithoutV=99.99.009.001, iedType=BCU, iedRedundancy=A, iedSystemVersionInstance=1, isBayInternal=true]"); - assertThat(settingsOrError.settings()).isNull(); - } - - @ParameterizedTest - @EnumSource(value = PrivateEnum.class, mode = EnumSource.Mode.INCLUDE, names = {"COMPAS_ICDHEADER", "COMPAS_SYSTEM_VERSION"}) - void getNetworkSettings_should_return_null_when_missing_ied_private(PrivateEnum missingPrivate) { - //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - PrivateUtils.removePrivates(findIed(sclRootAdapter.getCurrentElem(), "IED_NAME2").getCurrentElem(), missingPrivate); - ControlBlockAdapter controlBlockAdapter = findControlBlock(sclRootAdapter.getCurrentElem(), "IED_NAME2", "LD_INST21", "CB_LD_INST21_GSI", ControlBlockEnum.GSE); - - //When - SettingsOrError settingsOrError = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); - - //Then - assertThat(settingsOrError.errorMessage()).isEqualTo("No private %s found in this IED".formatted(missingPrivate.getPrivateType())); - assertThat(settingsOrError.settings()).isNull(); - } - - @ParameterizedTest - @MethodSource("provideInvalidCompasAttributes") - void getNetworkSettings_should_return_null_when_missing_ied_private_attributes(Consumer transformIedPrivate) { - //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); - ControlBlockAdapter controlBlockAdapter = findControlBlock(scd, "IED_NAME2", "LD_INST21", "CB_LD_INST21_GSI", ControlBlockEnum.GSE); - IEDAdapter iedAdapter = findIed(scd, "IED_NAME2"); - transformIedPrivate.accept(iedAdapter); - - //When - SettingsOrError settingsOrError = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); - - //Then - assertThat(settingsOrError.errorMessage()).startsWith("No row found with these criteria "); - assertThat(settingsOrError.settings()).isNull(); - } - - private static Stream provideInvalidCompasAttributes() { - return Stream.of( - Arguments.of((Consumer) iedAdapter -> iedAdapter.getCompasICDHeader().get().setIEDType(null)), - Arguments.of((Consumer) iedAdapter -> iedAdapter.getCompasICDHeader().get().setIEDredundancy(null)), - Arguments.of((Consumer) iedAdapter -> iedAdapter.getCompasICDHeader().get().setIEDSystemVersioninstance(null)), - Arguments.of((Consumer) iedAdapter -> iedAdapter.getCompasSystemVersion().get().setMainSystemVersion(null)), - Arguments.of((Consumer) iedAdapter -> iedAdapter.getCompasSystemVersion().get().setMinorSystemVersion(null)), - Arguments.of((Consumer) iedAdapter -> iedAdapter.getCompasSystemVersion().get().setMinorSystemVersion("1")) // Invalid format for MinorSystemVersion - ); - } - -} diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapterTest.java index 2e419e3d4..2e1d29687 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapterTest.java @@ -4,21 +4,18 @@ package org.lfenergy.compas.sct.commons.scl.ied; -import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.lfenergy.compas.scl2007b4.model.*; -import org.lfenergy.compas.sct.commons.dto.SclReportItem; +import org.lfenergy.compas.scl2007b4.model.SCL; +import org.lfenergy.compas.scl2007b4.model.TControl; +import org.lfenergy.compas.scl2007b4.model.TControlWithIEDName; +import org.lfenergy.compas.scl2007b4.model.TGSEControl; import org.lfenergy.compas.sct.commons.scl.ln.LN0Adapter; import org.lfenergy.compas.sct.commons.scl.ln.LNAdapter; -import org.lfenergy.compas.sct.commons.testhelpers.SclHelper; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; import org.lfenergy.compas.sct.commons.util.ControlBlockEnum; -import org.lfenergy.compas.sct.commons.util.SclConstructorHelper; -import java.math.BigDecimal; import java.util.List; -import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findLn; @@ -64,80 +61,4 @@ void addTargetIfNotExists_should_add_target(){ .containsExactly("AP_NAME", "IED_NAME2", "LD_INST21", "1", List.of("ANCR"), "prefix"); } - @Test - @Tag("issue-321") - void configureNetwork_should_add_GSE_element() { - // Given - SCL scd = SclTestMarshaller.getSCLFromFile("/scl-ln-adapter/scd_with_ln.xml"); - TConnectedAP connectedAP = SclHelper.addConnectedAp(scd, "SUB_NETWORK_NAME", "AP_NAME", "IED_NAME1"); - LN0Adapter ln0 = findLn0(scd, "IED_NAME1", "LD_INST11"); - // When - ln0.createDataSetIfNotExists("datSet", ControlBlockEnum.GSE); - // When - ControlBlockAdapter controlBlockAdapter = ln0.createControlBlockIfNotExists("cbName", "cbId", "datSet", ControlBlockEnum.GSE); - // When - Optional sclReportItem = controlBlockAdapter.configureNetwork(10L, "00-01-02-04-05", 11, (byte) 12, SclConstructorHelper.newDurationInMilliSec(3), - SclConstructorHelper.newDurationInMilliSec(20)); - // Then - assertThat(sclReportItem).isEmpty(); - assertThat(connectedAP.getGSE()).hasSize(1); - TGSE gse = connectedAP.getGSE().get(0); - assertThat(gse.getLdInst()).isEqualTo("LD_INST11"); - assertThat(gse.getMinTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) - .containsExactly("s", "m", new BigDecimal("3")); - assertThat(gse.getMaxTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) - .containsExactly("s", "m", new BigDecimal("20")); - assertThat(gse.getAddress().getP()).extracting(TP::getType, TP::getValue) - .containsExactlyInAnyOrder( - Tuple.tuple("APPID", "000A"), - Tuple.tuple("MAC-Address", "00-01-02-04-05"), - Tuple.tuple("VLAN-ID", "00B"), - Tuple.tuple("VLAN-PRIORITY", "12") - ); - } - - @Test - @Tag("issue-321") - void configureNetwork_should_add_SMV_element() { - // Given - SCL scd = SclTestMarshaller.getSCLFromFile("/scl-ln-adapter/scd_with_ln.xml"); - TConnectedAP connectedAP = SclHelper.addConnectedAp(scd, "SUB_NETWORK_NAME", "AP_NAME", "IED_NAME1"); - LN0Adapter ln0 = findLn0(scd, "IED_NAME1", "LD_INST11"); - // When - ln0.createDataSetIfNotExists("datSet", ControlBlockEnum.SAMPLED_VALUE); - // When - ControlBlockAdapter controlBlockAdapter = ln0.createControlBlockIfNotExists("cbName", "cbId", "datSet", ControlBlockEnum.SAMPLED_VALUE); - // When - Optional sclReportItem = controlBlockAdapter.configureNetwork(10L, "00-01-02-04-05", 11, (byte) 12, null, null); - // Then - assertThat(sclReportItem).isEmpty(); - assertThat(connectedAP.getSMV()).hasSize(1); - TSMV smv = connectedAP.getSMV().get(0); - assertThat(smv.getLdInst()).isEqualTo("LD_INST11"); - assertThat(smv.getAddress().getP()).extracting(TP::getType, TP::getValue) - .containsExactlyInAnyOrder( - Tuple.tuple("APPID", "000A"), - Tuple.tuple("MAC-Address", "00-01-02-04-05"), - Tuple.tuple("VLAN-ID", "00B"), - Tuple.tuple("VLAN-PRIORITY", "12") - ); - } - - @Test - @Tag("issue-321") - void configureNetwork_when_connectApNotFound_should_return_sclReportItem() { - // Given - SCL scd = SclTestMarshaller.getSCLFromFile("/scl-ln-adapter/scd_with_ln.xml"); - SclHelper.addConnectedAp(scd, "SUB_NETWORK_NAME", "AP_NAME", "IED_NAME2"); // ConnectedAp for IED_NAME2 instead of IED_NAME1 - LN0Adapter ln0 = findLn0(scd, "IED_NAME1", "LD_INST11"); - // When - ln0.createDataSetIfNotExists("datSet", ControlBlockEnum.SAMPLED_VALUE); - // When - ControlBlockAdapter controlBlockAdapter = ln0.createControlBlockIfNotExists("cbName", "cbId", "datSet", ControlBlockEnum.SAMPLED_VALUE); - // When - Optional sclReportItem = controlBlockAdapter.configureNetwork(10L, "00-01-02-04-05", 11, (byte) 12, null, null); - // Then - assertThat(sclReportItem).isPresent(); - } - } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/ControlBlockEnumTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/ControlBlockEnumTest.java index 7aa300377..b3444fecb 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/ControlBlockEnumTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/ControlBlockEnumTest.java @@ -7,41 +7,44 @@ 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.EnumSource; import org.junit.jupiter.params.provider.MethodSource; import org.lfenergy.compas.scl2007b4.model.*; +import org.lfenergy.compas.sct.commons.model.cbcom.TCBType; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThat; +import static org.lfenergy.compas.sct.commons.util.ControlBlockEnum.from; class ControlBlockEnumTest { @ParameterizedTest @MethodSource("provideFromTServiceType") - void from_should_map_ServiceType_to_ControlBlockEnumTest(TServiceType tServiceType, ControlBlockEnum expected) { + void from_TServiceType_should_map_ServiceType_to_ControlBlockEnumTest(TServiceType tServiceType, ControlBlockEnum expected) { //Given : parameter //When - ControlBlockEnum result = ControlBlockEnum.from(tServiceType); + ControlBlockEnum result = from(tServiceType); //Then assertThat(result).isEqualTo(expected); } @Test - void from_POOL_should_throw_exception() { + void from_TServiceType_POOL_should_throw_exception() { //Given TServiceType pollServiceType = TServiceType.POLL; //When & Then - assertThatThrownBy(() -> ControlBlockEnum.from(pollServiceType)) + assertThatThrownBy(() -> from(pollServiceType)) .isInstanceOf(IllegalArgumentException.class); } @ParameterizedTest @MethodSource("provideFromTControlClass") - void from_should_map_Class_to_ControlBlockEnumTest(Class tControlClass, ControlBlockEnum expected) { + void from_TControl_should_map_Class_to_ControlBlockEnumTest(Class tControlClass, ControlBlockEnum expected) { //Given : parameter //When - ControlBlockEnum result = ControlBlockEnum.from(tControlClass); + ControlBlockEnum result = from(tControlClass); //Then assertThat(result).isEqualTo(expected); } @@ -63,4 +66,30 @@ public static Stream provideFromTServiceType() { ); } + @ParameterizedTest + @MethodSource("provideFromTCBType") + public void from_TCBType_when_GOOSE_or_SV_should_succeed(TCBType tcbType, ControlBlockEnum expectedControlBlockEnum){ + // Given : parameter + // When + ControlBlockEnum result = from(tcbType); + // Then + assertThat(result).isEqualTo(expectedControlBlockEnum); + } + + @ParameterizedTest + @EnumSource(value = TCBType.class, mode = EnumSource.Mode.EXCLUDE, names = {"GOOSE", "SV"}) + public void from_TCBType_when_unsupported_TCBType_should_throw_exception(TCBType tcbType){ + // Given : parameter + // When & Then + assertThatThrownBy(() -> from(tcbType)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unsupported TCBType: " + tcbType.name()); + } + + public static Stream provideFromTCBType() { + return Stream.of( + Arguments.of(TCBType.GOOSE, ControlBlockEnum.GSE), + Arguments.of(TCBType.SV, ControlBlockEnum.SAMPLED_VALUE) + ); + } } diff --git a/sct-commons/src/test/resources/ControlBlockCommunicationTemplates.csv b/sct-commons/src/test/resources/ControlBlockCommunicationTemplates.csv deleted file mode 100644 index 5d149af69..000000000 --- a/sct-commons/src/test/resources/ControlBlockCommunicationTemplates.csv +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-FileCopyrightText: 2022 RTE FRANCE -# -# SPDX-License-Identifier: Apache-2.0 - -#CB Type;X.Y;Z.W;IedType;IedRedundancy;IedSystemVersionInstance;Bay Internal OR External;VLAN-ID;VLAN-PRIORITY;MINTIME;MAXTIME -GOOSE;01.00;009.001;BCU;A;1;BAY_INTERNAL;300;4;10;2000 -SV;01.00;009.001;BCU;A;1;BAY_INTERNAL;None;None;; -GOOSE;01.00;009.001;BCU;A;1;BAY_EXTERNAL;301;5;15;5000 -SV;01.00;009.001;BCU;A;1;BAY_EXTERNAL;None;None;; From 7be88e5ec24e1c5f90606608a999211e854bd4cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 07:21:52 +0000 Subject: [PATCH 8/9] build(deps): bump dawidd6/action-download-artifact from 2 to 3 Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 2 to 3. - [Release notes](https://github.com/dawidd6/action-download-artifact/releases) - [Commits](https://github.com/dawidd6/action-download-artifact/compare/v2...v3) --- updated-dependencies: - dependency-name: dawidd6/action-download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/sonarcloud-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud-analysis.yml b/.github/workflows/sonarcloud-analysis.yml index a2a67067d..4641ae4a5 100644 --- a/.github/workflows/sonarcloud-analysis.yml +++ b/.github/workflows/sonarcloud-analysis.yml @@ -20,7 +20,7 @@ jobs: run: cat $GITHUB_EVENT_PATH - name: Download PR number artifact if: github.event.workflow_run.event == 'pull_request' - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@v3 with: workflow: SonarCloud Build run_id: ${{ github.event.workflow_run.id }} From cccd6cce81ed7a2a68c025800b533a58f3f4f2fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 07:21:54 +0000 Subject: [PATCH 9/9] build(deps): bump actions/upload-artifact from 3 to 4 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/sonarcloud-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud-build.yml b/.github/workflows/sonarcloud-build.yml index efabf9e5e..c71626f92 100644 --- a/.github/workflows/sonarcloud-build.yml +++ b/.github/workflows/sonarcloud-build.yml @@ -55,7 +55,7 @@ jobs: run: echo ${{ github.event.number }} > PR_NUMBER.txt - name: Archive PR number if: github.event_name == 'pull_request' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PR_NUMBER path: PR_NUMBER.txt