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 d1a456e76..72576e5bb 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 @@ -4,7 +4,9 @@ package org.lfenergy.compas.sct.commons; +import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.api.ExtRefEditor; import org.lfenergy.compas.sct.commons.api.LnEditor; @@ -20,6 +22,7 @@ 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.AbstractLNAdapter; +import org.lfenergy.compas.sct.commons.scl.ln.LnId; import org.lfenergy.compas.sct.commons.util.ActiveStatus; import org.lfenergy.compas.sct.commons.util.PrivateUtils; import org.lfenergy.compas.sct.commons.util.Utils; @@ -28,10 +31,12 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import static org.apache.commons.lang3.StringUtils.*; import static org.lfenergy.compas.sct.commons.util.CommonConstants.*; +@Slf4j @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"; @@ -47,6 +52,9 @@ public class ExtRefEditorService implements ExtRefEditor { private final LnEditor lnEditor; private final DataTypeTemplatesService dataTypeTemplatesService; + @Getter + private final List errorHandler = new ArrayList<>(); + /** * Provides valid IED sources according to EPF configuration.
* EPF verification include:
@@ -56,24 +64,26 @@ public class ExtRefEditorService implements ExtRefEditor { * 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 scl SCL object * @param compasBay TCompasBay represent Bay Private * @param channel TChannel represent parameters * @return the IED sources matching the LDEPF parameters */ - private 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)) + private List getIedSources(SCL scl, TCompasBay compasBay, TChannel channel) { + return scl.getIED().stream() + .filter(tied -> { + Optional tCompasBay = PrivateUtils.extractCompasPrivate(tied, TCompasBay.class); + return (channel.getBayScope().equals(TCBScopeType.BAY_EXTERNAL) + && tCompasBay.stream().noneMatch(bay -> bay.getUUID().equals(compasBay.getUUID()))) + || (channel.getBayScope().equals(TCBScopeType.BAY_INTERNAL) + && tCompasBay.stream().anyMatch(bay -> bay.getUUID().equals(compasBay.getUUID()))); + }).filter(tied -> doesIcdHeaderMatchLDEPFChannel(tied, channel)) + .filter(tied -> ldeviceService.findLdevice(tied, channel.getLDInst()) + .filter(tlDevice -> ldeviceService.getLdeviceStatus(tlDevice).map(ActiveStatus.ON::equals).orElse(false)) + .map(tlDevice -> getActiveLNSourceByLDEPFChannel(tlDevice, channel) + .map(tAnyLN -> isValidDataTypeTemplate(scl.getDataTypeTemplates(), tAnyLN, channel)) .orElse(false)) .orElse(false)) - .map(IEDAdapter::getCurrentElem) .limit(2) .toList(); } @@ -83,17 +93,16 @@ private List getIedSources(SclRootAdapter sclRootAdapter, TCompasBay compa * - The location of ExtRef should be in LDevice (inst=LDEPF)
* - ExtRef that lacks Bay or ICDHeader Private is not returned
* - * @param sclReportItems List of SclReportItem * @return list of ExtRef and associated Bay */ - private List getExtRefWithBayReferenceInLDEPF(TDataTypeTemplates dataTypeTemplates, TIED tied, final TLDevice tlDevice, final List sclReportItems) { + private List getExtRefWithBayReferenceInLDEPF(TDataTypeTemplates dataTypeTemplates, TIED tied, final TLDevice tlDevice) { List extRefBayReferenceList = new ArrayList<>(); String lDevicePath = "SCL/IED[@name=\"" + tied.getName() + "\"]/AccessPoint/Server/LDevice[@inst=\"" + tlDevice.getInst() + "\"]"; Optional tCompasBay = PrivateUtils.extractCompasPrivate(tied, TCompasBay.class); if (tCompasBay.isEmpty()) { - sclReportItems.add(SclReportItem.error(lDevicePath, "The IED has no Private Bay")); + errorHandler.add(SclReportItem.error(lDevicePath, "The IED has no Private Bay")); if (PrivateUtils.extractCompasPrivate(tied, TCompasICDHeader.class).isEmpty()) { - sclReportItems.add(SclReportItem.error(lDevicePath, "The IED has no Private compas:ICDHeader")); + errorHandler.add(SclReportItem.error(lDevicePath, "The IED has no Private compas:ICDHeader")); } return Collections.emptyList(); } @@ -104,7 +113,7 @@ private List getExtRefWithBayReferenceInLDEPF .getExtRef().stream() .map(extRef -> new ExtRefInfo.ExtRefWithBayReference(tied.getName(), tCompasBay.get(), extRef)).toList()); } else { - sclReportItems.add(SclReportItem.error(lDevicePath, "DO@name=Mod/DA@name=stVal not found in DataTypeTemplate")); + errorHandler.add(SclReportItem.error(lDevicePath, "DO@name=Mod/DA@name=stVal not found in DataTypeTemplate")); } return extRefBayReferenceList; } @@ -131,83 +140,64 @@ private static Boolean doesExtRefMatchLDEPFChannel(TExtRef extRef, TChannel tCha /** * Verify whether the IED satisfies the EPF channel for the private element `TCompasICDHeader` * - * @param iedAdapter IEDAdapter + * @param tied TIED * @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()) + private static boolean doesIcdHeaderMatchLDEPFChannel(TIED tied, TChannel channel) { + Optional tCompasICDHeader = PrivateUtils.extractCompasPrivate(tied, TCompasICDHeader.class); + return tCompasICDHeader.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 Optional getActiveSourceLDeviceByLDEPFChannel(IEDAdapter iedAdapter, TChannel channel) { - return ldeviceService.findLdevice(iedAdapter.getCurrentElem(), 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 tlDevice TLDevice * @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)); + private Optional getActiveLNSourceByLDEPFChannel(TLDevice tlDevice, TChannel channel) { + return Stream.concat(Stream.of(tlDevice.getLN0()), tlDevice.getLN().stream()) + .filter(tAnyLN -> { + LnId lnId = LnId.from(tAnyLN); + return lnId.lnClass().equals(channel.getLNClass()) + && lnId.lnInst().equals(channel.getLNInst()) + && trimToEmpty(channel.getLNPrefix()).equals(trimToEmpty(lnId.prefix())); + }).findFirst() + .filter(tAnyLN -> lnEditor.getDaiModStValValue(tAnyLN).map(ActiveStatus.ON::equals).orElse(true)); } /** * Verify whether the LN satisfies the EPF channel parameters for Data Type Template elements. * - * @param lnAdapter AbstractLNAdapter + * @param dtt TDataTypeTemplates + * @param tAnyLN TAnyLN * @param channel TChannel * @return true if the LN matches the EPF channel */ - private static boolean isValidDataTypeTemplate(AbstractLNAdapter lnAdapter, TChannel channel) { + private boolean isValidDataTypeTemplate(TDataTypeTemplates dtt, TAnyLN tAnyLN, 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); + String daName = channel.getDAName(); + List sdoNames = new ArrayList<>(); + List bdaNames = new ArrayList<>(); if (isNotBlank(channel.getSDOName())) { - doTypeName.getStructNames().add(channel.getSDOName()); + sdoNames.add(channel.getSDOName()); } - DaTypeName daTypeName = new DaTypeName(channel.getDAName()); if (isNotBlank(channel.getBDAName())) { - daTypeName.setBType(TPredefinedBasicTypeEnum.STRUCT); - daTypeName.getStructNames().add(channel.getBDAName()); + bdaNames.add(channel.getBDAName()); } if (isNotBlank(channel.getSBDAName())) { - daTypeName.getStructNames().add(channel.getSBDAName()); + bdaNames.add(channel.getSBDAName()); } - return lnAdapter.getDataTypeTemplateAdapter().getLNodeTypeAdapterById(lnAdapter.getLnType()) - .filter(lNodeTypeAdapter -> { - try { - lNodeTypeAdapter.checkDoAndDaTypeName(doTypeName, daTypeName); - } catch (ScdException ex) { - return false; - } - return true; - }).isPresent(); + DoLinkedToDaFilter doLinkedToDaFilter = new DoLinkedToDaFilter(doName, sdoNames, daName, bdaNames); + return dataTypeTemplatesService.getFilteredDoLinkedToDa(dtt, tAnyLN.getLnType(), doLinkedToDaFilter) + .findFirst().isPresent(); } @Override @@ -270,29 +260,28 @@ public TExtRef updateExtRefSource(SCL scd, ExtRefInfo extRefInfo) throws ScdExce @Override public List manageBindingForLDEPF(SCL scd, EPF epf) { - List sclReportItems = new ArrayList<>(); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - if (!epf.isSetChannels()) return sclReportItems; + errorHandler.clear(); + if (!epf.isSetChannels()) return errorHandler; + log.info("Processing %d EPF setting channels".formatted(epf.getChannels().getChannel().size())); iedService.getFilteredIeds(scd, ied -> !ied.getName().contains("TEST")) .forEach(tied -> ldeviceService.findLdevice(tied, LDEVICE_LDEPF) - .ifPresent(tlDevice -> getExtRefWithBayReferenceInLDEPF(scd.getDataTypeTemplates(), tied, tlDevice, sclReportItems) + .ifPresent(tlDevice -> getExtRefWithBayReferenceInLDEPF(scd.getDataTypeTemplates(), tied, tlDevice) .forEach(extRefBayRef -> epf.getChannels().getChannel().stream().filter(tChannel -> doesExtRefMatchLDEPFChannel(extRefBayRef.extRef(), tChannel)) .findFirst().ifPresent(channel -> { - List iedSources = getIedSources(sclRootAdapter, extRefBayRef.compasBay(), channel); + List iedSources = getIedSources(scd, extRefBayRef.compasBay(), channel); if (iedSources.size() == 1) { updateLDEPFExtRefBinding(extRefBayRef.extRef(), iedSources.getFirst(), channel); - LDeviceAdapter lDeviceAdapter = new LDeviceAdapter(new IEDAdapter(sclRootAdapter, tied.getName()), tlDevice); - sclReportItems.addAll(updateLDEPFDos(lDeviceAdapter, extRefBayRef.extRef(), channel)); + updateLDEPFDos(scd.getDataTypeTemplates(), tied, tlDevice, extRefBayRef.extRef(), channel); } else { if (iedSources.size() > 1) { - sclReportItems.add(SclReportItem.warning(null, "There is more than one IED source to bind the signal " + + errorHandler.add(SclReportItem.warning(null, "There is more than one IED source to bind the signal " + "/IED@name=" + extRefBayRef.iedName() + "/LDevice@inst=LDEPF/LN0" + "/ExtRef@desc=" + extRefBayRef.extRef().getDesc())); } // If the source IED is not found, there will be no update or report message. } })))); - return sclReportItems; + return errorHandler; } @Override @@ -337,61 +326,86 @@ private void updateLDEPFExtRefBinding(TExtRef extRef, TIED iedSource, TChannel s } String doName = isBlank(setting.getDOInst()) || setting.getDOInst().equals("0") ? setting.getDOName() : setting.getDOName() + setting.getDOInst(); extRef.setDoName(doName); + log.info(("LDEPF - Update Binding => ExtRef(desc=%s) " + + "ExtRef(iedName=%s), ExtRef(LdInst=%s), ExtRef(LNClass=%s), ExtRef(LNInst=%s), ExtRef(prefix=%s), ExtRef(doName=%s)").formatted(extRef.getDesc(), extRef.getIedName(), extRef.getLdInst(), extRef.getLnClass().getFirst(), extRef.getLnInst(), extRef.isSetPrefix() ? extRef.getPrefix() : "", doName)); } - private List updateLDEPFDos(LDeviceAdapter lDeviceAdapter, TExtRef extRef, TChannel setting) { - List sclReportItems = new ArrayList<>(); + private String computeDaiValue(TAnyLN tAnyLN, TExtRef extRef, String daName) { + if (LN_PREFIX_B.equals(LnId.from(tAnyLN).prefix()) || LN_PREFIX_A.equals(LnId.from(tAnyLN).prefix())) { + return extRef.getIedName() + + extRef.getLdInst() + "/" + + trimToEmpty(extRef.getPrefix()) + + extRef.getLnClass().getFirst() + + trimToEmpty(extRef.getLnInst()) + "." + + extRef.getDoName() + "." + Q_DA_NAME; + } else { + return extRef.getIedName() + + extRef.getLdInst() + "/" + + trimToEmpty(extRef.getPrefix()) + + extRef.getLnClass().getFirst() + + trimToEmpty(extRef.getLnInst()) + "." + + extRef.getDoName() + "." + + daName; + } + } + + private void updateLDEPFDos(TDataTypeTemplates dtt, TIED tied, TLDevice tlDevice, TExtRef tExtRef, TChannel setting) { + // Digital if (setting.getChannelType().equals(TChannelType.DIGITAL)) { - //digital - lDeviceAdapter.findLnAdapter(LN_RBDR, setting.getChannelNum(), null) - .ifPresent(lnAdapter -> DO_DA_MAPPINGS.forEach(doNameAndDaName -> updateVal(lnAdapter, doNameAndDaName.doName, doNameAndDaName.daName, extRef, setting).ifPresent(sclReportItems::add))); - lDeviceAdapter.findLnAdapter(LN_RBDR, setting.getChannelNum(), LN_PREFIX_B) - .ifPresent(lnAdapter -> DO_DA_MAPPINGS.forEach(doNameAndDaName -> updateVal(lnAdapter, doNameAndDaName.doName, doNameAndDaName.daName, extRef, setting).ifPresent(sclReportItems::add))); + lnEditor.findLn(tlDevice, tAnyLN -> LnId.from(tAnyLN).lnClass().equals(LN_RBDR) && LnId.from(tAnyLN).lnInst().equals(setting.getChannelNum())) + .ifPresent(tln -> updateDaiValue(dtt, tied, tlDevice, tln, tExtRef, setting)); + lnEditor.findLn(tlDevice, tAnyLN -> LnId.from(tAnyLN).lnClass().equals(LN_RBDR) && LnId.from(tAnyLN).lnInst().equals(setting.getChannelNum()) + && LnId.from(tAnyLN).prefix().equals(LN_PREFIX_B)) + .ifPresent(tln -> updateDaiValue(dtt, tied, tlDevice, tln, tExtRef, setting)); } + // Analog if (setting.getChannelType().equals(TChannelType.ANALOG)) { - //analog - lDeviceAdapter.findLnAdapter(LN_RADR, setting.getChannelNum(), null) - .ifPresent(lnAdapter -> DO_DA_MAPPINGS.forEach(doNameAndDaName -> updateVal(lnAdapter, doNameAndDaName.doName, doNameAndDaName.daName, extRef, setting).ifPresent(sclReportItems::add))); - lDeviceAdapter.findLnAdapter(LN_RADR, setting.getChannelNum(), LN_PREFIX_A) - .ifPresent(lnAdapter -> DO_DA_MAPPINGS.forEach(doNameAndDaName -> updateVal(lnAdapter, doNameAndDaName.doName, doNameAndDaName.daName, extRef, setting).ifPresent(sclReportItems::add))); + lnEditor.findLn(tlDevice, tAnyLN -> LnId.from(tAnyLN).lnClass().equals(LN_RADR) && LnId.from(tAnyLN).lnInst().equals(setting.getChannelNum())) + .ifPresent(tln -> updateDaiValue(dtt, tied, tlDevice, tln, tExtRef, setting)); + lnEditor.findLn(tlDevice, tAnyLN -> LnId.from(tAnyLN).lnClass().equals(LN_RADR) && LnId.from(tAnyLN).lnInst().equals(setting.getChannelNum()) + && LnId.from(tAnyLN).prefix().equals(LN_PREFIX_A)) + .ifPresent(tln -> updateDaiValue(dtt, tied, tlDevice, tln, tExtRef, setting)); } - return sclReportItems; } - private Optional updateVal(AbstractLNAdapter lnAdapter, String doName, String daName, TExtRef extRef, TChannel setting) { - String value = switch (daName) { + private void updateDaiValue(TDataTypeTemplates dtt, TIED tied, TLDevice tlDevice, TAnyLN tln, TExtRef tExtRef, TChannel setting) { + DO_DA_MAPPINGS.forEach(doNameAndDaName -> Optional.ofNullable(getNewDaiValue(doNameAndDaName.daName, tln, tExtRef, setting)) + .ifPresent(newDaiValue -> updateDaiVal(dtt, tied, tlDevice, tln, DoLinkedToDaFilter.from(doNameAndDaName.doName, doNameAndDaName.daName), newDaiValue))); + }; + + private String getNewDaiValue(String daName,TAnyLN tAnyLN, TExtRef extRef, TChannel setting) { + return switch (daName) { case DU_DA_NAME -> setting.isSetChannelShortLabel() ? setting.getChannelShortLabel(): null; case SETVAL_DA_NAME -> { - if(LN_PREFIX_B.equals(lnAdapter.getPrefix()) || LN_PREFIX_A.equals(lnAdapter.getPrefix())){ + if(LN_PREFIX_B.equals(LnId.from(tAnyLN).prefix()) || LN_PREFIX_A.equals(LnId.from(tAnyLN).prefix())){ yield setting.isSetChannelLevModQ() && !setting.getChannelLevModQ().equals(TChannelLevMod.NA) ? setting.getChannelLevModQ().value(): null; } else { yield setting.isSetChannelLevMod() && !setting.getChannelLevMod().equals(TChannelLevMod.NA) ? setting.getChannelLevMod().value(): null; } } case STVAL_DA_NAME -> ActiveStatus.ON.getValue(); - case SETSRCREF_DA_NAME -> computeDaiValue(lnAdapter, extRef, setting.getDAName()); + case SETSRCREF_DA_NAME -> computeDaiValue(tAnyLN, extRef, setting.getDAName()); default -> null; }; - return Optional.ofNullable(value).flatMap(newDaiValue -> lnAdapter.getDOIAdapterByName(doName).updateDAI(daName, newDaiValue)); } - 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() + - extRef.getLdInst() + "/" + - trimToEmpty(extRef.getPrefix()) + - extRef.getLnClass().getFirst() + - trimToEmpty(extRef.getLnInst()) + "." + - extRef.getDoName() + "." + Q_DA_NAME; - } else { - return extRef.getIedName() + - extRef.getLdInst() + "/" + - trimToEmpty(extRef.getPrefix()) + - extRef.getLnClass().getFirst() + - trimToEmpty(extRef.getLnInst()) + "." + - extRef.getDoName() + "." + - daName; - } + private void updateDaiVal(TDataTypeTemplates dtt, TIED tied, TLDevice tlDevice, TAnyLN tln, DoLinkedToDaFilter doLinkedToDaFilter, String newDaiValue) { + dataTypeTemplatesService.getFilteredDoLinkedToDa(dtt, tln.getLnType(), doLinkedToDaFilter) + .map(doLinkedToDa1 -> lnEditor.getDoLinkedToDaCompletedFromDAI(tied, tlDevice.getInst(), tln, doLinkedToDa1)) + .findFirst() + .filter(doLinkedToDa1 -> { + if (!doLinkedToDa1.isUpdatable()){ + errorHandler.add(SclReportItem.warning(tied.getName() + "/" + LDEVICE_LDSUIED + "/" + LnId.from(tln).lnClass() + "/DOI@name=\"" + doLinkedToDaFilter.doName() + "\"/DAI@name=\"" + doLinkedToDaFilter.daName() + "\"/Val", "The DAI cannot be updated")); + } + return doLinkedToDa1.isUpdatable(); + }) + .ifPresent(doLinkedToDa1 -> { + TVal tVal = new TVal(); + tVal.setValue(newDaiValue); + doLinkedToDa1.dataAttribute().addDaVal(tVal); + lnEditor.updateOrCreateDOAndDAInstances(tln, doLinkedToDa1); + log.info("LDEPF - Update DOI => LN(lnClass=%s, inst=%s, prefix=%s) / DOI(name=%s)/DAI(name=%s) with value=%s".formatted(LnId.from(tln).lnClass(), LnId.from(tln).lnInst(), LnId.from(tln).prefix(), doLinkedToDaFilter.doName(), doLinkedToDaFilter.daName(), newDaiValue)); + }); } private record DoNameAndDaName(String doName, String daName) { diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/SclService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/SclService.java index cba99774f..9a46ec9b4 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/SclService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/SclService.java @@ -54,7 +54,7 @@ public class SclService implements SclEditor { private final DataTypeTemplateReader dataTypeTemplateService; @Getter - private final List errorHanlder = new ArrayList<>(); + private final List errorHandler = new ArrayList<>(); @Override public SCL initScl(final UUID hId, final String hVersion, final String hRevision) throws ScdException { @@ -221,7 +221,7 @@ public List updateDoInRef(SCL scd) { @Override public List manageMonitoringLns(SCL scd) { - errorHanlder.clear(); + errorHandler.clear(); iedService.getFilteredIeds(scd, ied -> !ied.getName().contains(IED_TEST_NAME)) .forEach(tied -> { Map> serviceTypeToIedSource = ldeviceService.getLdevices(tied) @@ -238,20 +238,20 @@ public List manageMonitoringLns(SCL scd) { .ifPresent(iedSourceKeys -> manageMonitoringLns(iedSourceKeys, scd, tied, ldSUIEDLDevice, DO_SVCBREF, MonitoringLnClassEnum.LSVS)); }); }); - return errorHanlder; + return errorHandler; } private void manageMonitoringLns(List iedSources, SCL scd, TIED tied, TLDevice ldsuiedLdevice, String doName, MonitoringLnClassEnum monitoringLnClassEnum) { List lgosOrLsvsLns = lnService.getFilteredLns(ldsuiedLdevice, tln -> monitoringLnClassEnum.value().equals(tln.getLnClass().getFirst())).toList(); if (lgosOrLsvsLns.isEmpty()) - errorHanlder.add(SclReportItem.warning(tied.getName() + "/" + LDEVICE_LDSUIED + "/" + monitoringLnClassEnum.value(), "There is no LN %s present in LDevice".formatted(monitoringLnClassEnum.value()))); + errorHandler.add(SclReportItem.warning(tied.getName() + "/" + LDEVICE_LDSUIED + "/" + monitoringLnClassEnum.value(), "There is no LN %s present in LDevice".formatted(monitoringLnClassEnum.value()))); DoLinkedToDaFilter doLinkedToDaFilter = new DoLinkedToDaFilter(doName, List.of(), DA_SETSRCREF, List.of()); lgosOrLsvsLns.forEach(lgosOrLsvs -> dataTypeTemplateService.getFilteredDoLinkedToDa(scd.getDataTypeTemplates(), lgosOrLsvs.getLnType(), doLinkedToDaFilter) .map(doLinkedToDa -> lnService.getDoLinkedToDaCompletedFromDAI(tied, LDEVICE_LDSUIED, lgosOrLsvs, doLinkedToDa)) .findFirst() .filter(doLinkedToDa -> { if (!doLinkedToDa.isUpdatable()) - errorHanlder.add(SclReportItem.warning(tied.getName() + "/" + LDEVICE_LDSUIED + "/" + monitoringLnClassEnum.value() + "/DOI@name=\"" + doName + "\"/DAI@name=\"setSrcRef\"/Val", "The DAI cannot be updated")); + errorHandler.add(SclReportItem.warning(tied.getName() + "/" + LDEVICE_LDSUIED + "/" + monitoringLnClassEnum.value() + "/DOI@name=\"" + doName + "\"/DAI@name=\"setSrcRef\"/Val", "The DAI cannot be updated")); return doLinkedToDa.isUpdatable(); }) .ifPresent(doLinkedToDa -> { diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/LnEditor.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/LnEditor.java index e6fd1f7a6..5fb94ea29 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/LnEditor.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/LnEditor.java @@ -4,14 +4,13 @@ package org.lfenergy.compas.sct.commons.api; -import org.lfenergy.compas.scl2007b4.model.TAnyLN; -import org.lfenergy.compas.scl2007b4.model.TDAI; -import org.lfenergy.compas.scl2007b4.model.TIED; +import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.domain.DoLinkedToDa; import org.lfenergy.compas.sct.commons.domain.DoLinkedToDaFilter; import org.lfenergy.compas.sct.commons.util.ActiveStatus; import java.util.Optional; +import java.util.function.Predicate; public interface LnEditor { @@ -24,4 +23,6 @@ public interface LnEditor { void updateOrCreateDOAndDAInstances(TAnyLN tAnyLN, DoLinkedToDa doLinkedToDa); DoLinkedToDa getDoLinkedToDaCompletedFromDAI(TIED tied, String ldInst, TAnyLN anyLN, DoLinkedToDa doLinkedToDa); + + Optional findLn(TLDevice tlDevice, Predicate lnPredicate); } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/domain/DataAttribute.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/domain/DataAttribute.java index b3d7469ef..cf45279e1 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/domain/DataAttribute.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/domain/DataAttribute.java @@ -46,6 +46,11 @@ public void addDaVal(List vals) { daiValues.add(new DaVal(settingGroup, tVal.getValue())); }); } + public void addDaVal(TVal tVal) { + Long settingGroup = tVal.isSetSGroup() ? tVal.getSGroup() : null; + daiValues.removeIf(daVal -> Objects.equals(daVal.settingGroup(), settingGroup)); + daiValues.add(new DaVal(settingGroup, tVal.getValue())); + } @Override public String toString(){ 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 0a7fab199..27ce66bf9 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 @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021 RTE FRANCE +// SPDX-FileCopyrightText: 2021 2025 RTE FRANCE // // SPDX-License-Identifier: Apache-2.0 @@ -25,8 +25,7 @@ import java.util.Optional; import java.util.stream.Stream; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.*; import static org.lfenergy.compas.sct.commons.util.CommonConstants.*; @@ -712,8 +711,8 @@ void epfPostProcessing_when_exist_unused_channel_should_update_setSrcRef() { } @ParameterizedTest() - @CsvSource({"'',''", "NA,NA"}) - void manageBindingForLDEPF_should_not_update_dai_setVal_when_channelLevMod_or_channelLevModq_are_empty_or_NA(String channelLevMod, String channelLevModq) { + @CsvSource({"'', '', 'ADF','ADF'", "NA, NA,'ADF','ADF'", "POSITIVE_OR_RISING, OTHER, 'Positive or Rising','Other'"}) + void manageBindingForLDEPF_should_bind_extRef_and_update_dai_within_radr_and_rbdr_ln_for_both_DIGITAL_and_ANALOG_channel(String channelLevMod, String channelLevModq, String expectedChannelLevMod, String expectedChannelLevModq) { //Given SCL scd = SclTestMarshaller.getSCLFromFile("/scd-ldepf/scd_ldepf_processing_bind_dai_update.xml"); TChannel digitalChannel = new TChannel(); @@ -772,6 +771,11 @@ void manageBindingForLDEPF_should_not_update_dai_setVal_when_channelLevMod_or_ch .extracting(TAccessPoint::getServer) .flatExtracting(TServer::getLDevice) .filteredOn(tlDevice -> tlDevice.getInst().equals(LDEVICE_LDEPF)) + // Binding properties should be set + .allSatisfy(tlDevice -> assertThat(tlDevice.getLN0().getInputs().getExtRef()) + .filteredOn(tExtRef -> tExtRef.getDesc().contains("ANALOG") || tExtRef.getDesc().contains("DIGITAL")) + .extracting(TExtRef::getIedName, TExtRef::getLdInst, TExtRef::getLnClass, TExtRef::getLnInst, TExtRef::getDoName) + .containsExactlyInAnyOrder(tuple("IED_NAME1", "LDPX", List.of("PTRC"), "0", "Str"), tuple("IED_NAME1", "LDPX", List.of("PTRC"), "0", "Str"))) //LN class RBDR: with and without prefix 'b' .allSatisfy(tlDevice -> { assertThat(tlDevice.getLN()) @@ -785,7 +789,7 @@ void manageBindingForLDEPF_should_not_update_dai_setVal_when_channelLevMod_or_ch .containsExactlyInAnyOrder("on"); assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)) .extracting(TVal::getValue) - .containsExactlyInAnyOrder("ADF"); + .containsExactlyInAnyOrder(expectedChannelLevMod); assertThat(getDaiValue(tln, SRCREF_DO_NAME, SETSRCREF_DA_NAME)) .extracting(TVal::getValue) .containsExactlyInAnyOrder("IED_NAME1LDPX/PTRC0.Str.general"); @@ -801,7 +805,7 @@ void manageBindingForLDEPF_should_not_update_dai_setVal_when_channelLevMod_or_ch .containsExactlyInAnyOrder("on"); assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)) .extracting(TVal::getValue) - .containsExactlyInAnyOrder("ADF"); + .containsExactlyInAnyOrder(expectedChannelLevModq); assertThat(getDaiValue(tln, SRCREF_DO_NAME, SETSRCREF_DA_NAME)) .extracting(TVal::getValue) .containsExactlyInAnyOrder("IED_NAME1LDPX/PTRC0.Str.q"); @@ -820,7 +824,7 @@ void manageBindingForLDEPF_should_not_update_dai_setVal_when_channelLevMod_or_ch .containsExactlyInAnyOrder("on"); assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)) .extracting(TVal::getValue) - .containsExactlyInAnyOrder("ADF"); + .containsExactlyInAnyOrder(expectedChannelLevMod); assertThat(getDaiValue(tln, SRCREF_DO_NAME, SETSRCREF_DA_NAME)) .extracting(TVal::getValue) .containsExactlyInAnyOrder("IED_NAME1LDPX/PTRC0.Str.general"); @@ -836,7 +840,7 @@ void manageBindingForLDEPF_should_not_update_dai_setVal_when_channelLevMod_or_ch .containsExactlyInAnyOrder("on"); assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)) .extracting(TVal::getValue) - .containsExactlyInAnyOrder("ADF"); + .containsExactlyInAnyOrder(expectedChannelLevModq); assertThat(getDaiValue(tln, SRCREF_DO_NAME, SETSRCREF_DA_NAME)) .extracting(TVal::getValue) .containsExactlyInAnyOrder("IED_NAME1LDPX/PTRC0.Str.q"); @@ -844,4 +848,47 @@ void manageBindingForLDEPF_should_not_update_dai_setVal_when_channelLevMod_or_ch }); } + @Test + void manageBindingForLDEPF_should_return_warning_when_dai_is_not_updatable() { + //Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-ldepf/scd_ldepf_processing_bind_dai_update.xml"); + scd.getIED().stream() + .filter(tied -> tied.getName().equals("IED_NAME1")) + .flatMap(tied -> tied.getAccessPoint().stream()) + .filter(TAccessPoint::isSetServer) + .flatMap(tAccessPoint -> tAccessPoint.getServer().getLDevice().stream()) + .filter(tlDevice -> tlDevice.getInst().equals(LDEVICE_LDEPF)) + .flatMap(tlDevice -> tlDevice.getLN().stream()) + .filter(tln -> tln.getLnClass().contains("RBDR") && tln.getInst().equals("1") && !tln.isSetPrefix()) + .flatMap(tln -> getDai(tln, CHNUM1_DO_NAME, DU_DA_NAME)) + .findFirst().ifPresent(tdai -> tdai.setValImport(false)); + + TChannel digitalChannel = new TChannel(); + digitalChannel.setBayScope(TCBScopeType.BAY_INTERNAL); + digitalChannel.setChannelType(TChannelType.DIGITAL); + digitalChannel.setChannelNum("1"); + digitalChannel.setChannelShortLabel("MR.PX1"); + digitalChannel.setChannelLevMod(TChannelLevMod.POSITIVE_OR_RISING); + digitalChannel.setChannelLevModQ(TChannelLevMod.OTHER); + digitalChannel.setIEDType("BCU"); + digitalChannel.setIEDRedundancy(TIEDredundancy.NONE); + digitalChannel.setIEDSystemVersionInstance("1"); + digitalChannel.setLDInst("LDPX"); + digitalChannel.setLNClass("PTRC"); + digitalChannel.setLNInst("0"); + digitalChannel.setDOName("Str"); + digitalChannel.setDOInst("0"); + digitalChannel.setDAName("general"); + EPF epf = new EPF(); + Channels channels = new Channels(); + channels.getChannel().add(digitalChannel); + epf.setChannels(channels); + // When + List sclReportItems = extRefEditorService.manageBindingForLDEPF(scd, epf); + // Then + assertThat(sclReportItems) + .hasSize(1) + .extracting(SclReportItem::isError, SclReportItem::xpath, SclReportItem::message) + .containsExactlyInAnyOrder(tuple(false, "IED_NAME1/LDSUIED/RBDR/DOI@name=\"ChNum1\"/DAI@name=\"dU\"/Val", "The DAI cannot be updated")); + } }