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 6ab770133..b4a05ee66 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 @@ -6,6 +6,7 @@ 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; @@ -21,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; @@ -33,6 +35,7 @@ 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"; @@ -48,9 +51,10 @@ public class ExtRefEditorService implements ExtRefEditor { private final IedService iedService; private final LdeviceService ldeviceService; private final LnEditor lnEditor; + private final DataTypeTemplatesService dataTypeTemplatesService; @Getter - private final List errorHandler = new ArrayList<>(); + private final ThreadLocal> errorHandler = ThreadLocal.withInitial(ArrayList::new); /** * Provides valid IED sources according to EPF configuration.
@@ -61,24 +65,27 @@ 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 -> PrivateUtils.extractStringPrivate(tlDevice.getLN0(), COMPAS_LNODE_STATUS).map(status -> status.equals(ActiveStatus.ON.getValue())).orElse(false)) + .map(tlDevice -> getActiveLNSourceByLDEPFChannel(tlDevice, channel) + .map(tAnyLN -> isValidDataTypeTemplate(scl.getDataTypeTemplates(), tAnyLN, channel)) .orElse(false)) .orElse(false)) - .map(IEDAdapter::getCurrentElem) .limit(2) .toList(); } @@ -94,9 +101,9 @@ private List getExtRefWithBayReferenceInLDEPF String lDevicePath = "SCL/IED[@name=\"" + tied.getName() + "\"]/AccessPoint/Server/LDevice[@inst=\"" + tlDevice.getInst() + "\"]"; Optional tCompasBay = PrivateUtils.extractCompasPrivate(tied, TCompasBay.class); if (tCompasBay.isEmpty()) { - errorHandler.add(SclReportItem.error(lDevicePath, "The IED has no Private Bay")); + errorHandler.get().add(SclReportItem.error(lDevicePath, "The IED has no Private Bay")); if (PrivateUtils.extractCompasPrivate(tied, TCompasICDHeader.class).isEmpty()) { - errorHandler.add(SclReportItem.error(lDevicePath, "The IED has no Private compas:ICDHeader")); + errorHandler.get().add(SclReportItem.error(lDevicePath, "The IED has no Private compas:ICDHeader")); } return Collections.emptyList(); } @@ -127,81 +134,58 @@ 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 -> PrivateUtils.extractStringPrivate(tlDevice.getLN0(), COMPAS_LNODE_STATUS).map(status -> status.equals(ActiveStatus.ON.getValue())).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 + * @return AnyLN 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()))) + private Optional getActiveLNSourceByLDEPFChannel(TLDevice tlDevice, TChannel channel) { + return lnEditor.getAnylns(tlDevice) + .filter(tAnyLN -> lnEditor.matchesLn(tAnyLN, channel.getLNClass(), channel.getLNInst(), channel.getLNPrefix())) .findFirst() - .filter(abstractLNAdapter -> PrivateUtils.extractStringPrivate(abstractLNAdapter.getCurrentElem(), COMPAS_LNODE_STATUS).map(status -> status.equals(ActiveStatus.ON.getValue())).orElse(true)); + .filter(tAnyLN -> PrivateUtils.extractStringPrivate(tAnyLN, COMPAS_LNODE_STATUS).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 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(); + return dataTypeTemplatesService.findDoLinkedToDa(dtt, tAnyLN.getLnType(), new DoLinkedToDaFilter(doName, sdoNames, daName, bdaNames)).isPresent(); } @Override @@ -264,30 +248,32 @@ public TExtRef updateExtRefSource(SCL scd, ExtRefInfo extRefInfo) throws ScdExce @Override public List manageBindingForLDEPF(SCL scd, EPF epf) { - errorHandler.clear(); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - if (!epf.isSetChannels()) return errorHandler; - iedService.getFilteredIeds(scd, ied -> !ied.getName().contains("TEST")) - .forEach(tied -> ldeviceService.findLdevice(tied, tlDevice -> tlDevice.getInst().equals(LDEVICE_LDEPF)) - .filter(ldepfLdevice -> PrivateUtils.extractStringPrivate(ldepfLdevice.getLN0(), COMPAS_LNODE_STATUS).map(status -> !status.equals(ActiveStatus.OFF.getValue())).orElse(false)) - .ifPresent(ldepfLdevice -> getExtRefWithBayReferenceInLDEPF(tied, ldepfLdevice) - .forEach(extRefBayRef -> epf.getChannels().getChannel().stream().filter(tChannel -> doesExtRefMatchLDEPFChannel(extRefBayRef.extRef(), tChannel)) - .findFirst().ifPresent(channel -> { - List iedSources = getIedSources(sclRootAdapter, extRefBayRef.compasBay(), channel); - if (iedSources.size() == 1) { - updateLDEPFExtRefBinding(extRefBayRef, iedSources.getFirst(), channel); - LDeviceAdapter lDeviceAdapter = new LDeviceAdapter(new IEDAdapter(sclRootAdapter, tied.getName()), ldepfLdevice); - updateLDEPFDos(lDeviceAdapter, extRefBayRef.extRef(), channel); - } else { - if (iedSources.size() > 1) { - 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())); + errorHandler.get().clear(); + if (!epf.isSetChannels()) return List.of(); + try { + 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, tlDevice -> tlDevice.getInst().equals(LDEVICE_LDEPF)) + .filter(ldepfLdevice -> PrivateUtils.extractStringPrivate(ldepfLdevice.getLN0(), COMPAS_LNODE_STATUS).map(status -> !status.equals(ActiveStatus.OFF.getValue())).orElse(false)) + .ifPresent(ldepfLdevice -> getExtRefWithBayReferenceInLDEPF(tied, ldepfLdevice) + .forEach(extRefBayRef -> epf.getChannels().getChannel().stream().filter(tChannel -> doesExtRefMatchLDEPFChannel(extRefBayRef.extRef(), tChannel)) + .findFirst().ifPresent(channel -> { + List iedSources = getIedSources(scd, extRefBayRef.compasBay(), channel); + if (iedSources.size() == 1) { + updateLDEPFExtRefBinding(extRefBayRef, iedSources.getFirst(), channel); + updateLDEPFDos(scd.getDataTypeTemplates(), tied, ldepfLdevice, extRefBayRef.extRef(), channel); + } else { + if (iedSources.size() > 1) { + errorHandler.get().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 errorHandler; + })))); + return errorHandler.get(); + } finally { + errorHandler.remove(); + } } @Override @@ -345,42 +331,6 @@ private void updateLDEPFExtRefBinding(ExtRefInfo.ExtRefWithBayReference extRefWi } } - private void updateLDEPFDos(LDeviceAdapter lDeviceAdapter, TExtRef extRef, TChannel setting) { - if (setting.getChannelType().equals(TChannelType.DIGITAL)) { - //digital - lDeviceAdapter.findLnAdapter(LN_RBDR, setting.getChannelNum(), null) - .ifPresent(lnAdapter -> DO_DA_MAPPINGS.forEach(doNameAndDaName -> updateVal(lnAdapter, doNameAndDaName, extRef, setting))); - lDeviceAdapter.findLnAdapter(LN_RBDR, setting.getChannelNum(), LN_PREFIX_B) - .ifPresent(lnAdapter -> DO_DA_MAPPINGS.forEach(doNameAndDaName -> updateVal(lnAdapter, doNameAndDaName, extRef, setting))); - } - if (setting.getChannelType().equals(TChannelType.ANALOG)) { - //analog - lDeviceAdapter.findLnAdapter(LN_RADR, setting.getChannelNum(), null) - .ifPresent(lnAdapter -> DO_DA_MAPPINGS.forEach(doNameAndDaName -> updateVal(lnAdapter, doNameAndDaName, extRef, setting))); - lDeviceAdapter.findLnAdapter(LN_RBDR, setting.getChannelNum(), LN_PREFIX_A) - .ifPresent(lnAdapter -> DO_DA_MAPPINGS.forEach(doNameAndDaName -> updateVal(lnAdapter, doNameAndDaName, extRef, setting))); - } - } - - private void updateVal(AbstractLNAdapter lnAdapter, DoNameAndDaName doDaName, TExtRef extRef, TChannel setting) { - String lnPrefix = lnAdapter.getPrefix(); - Optional sclReportItem = switch (doDaName.daName) { - case DU_DA_NAME -> setting.isSetChannelShortLabel() ? lnAdapter.getDOIAdapterByName(doDaName.doName).updateDAI(doDaName.daName, setting.getChannelShortLabel()) : - Optional.empty(); - case SETVAL_DA_NAME -> { - if (LN_PREFIX_B.equals(lnPrefix) || LN_PREFIX_A.equals(lnPrefix)) { - yield setting.isSetChannelLevModQ() && !setting.getChannelLevModQ().equals(TChannelLevMod.NA) ? lnAdapter.getDOIAdapterByName(doDaName.doName).updateDAI(doDaName.daName, setting.getChannelLevModQ().value()) : Optional.empty(); - } else { - yield setting.isSetChannelLevMod() && !setting.getChannelLevMod().equals(TChannelLevMod.NA) ? lnAdapter.getDOIAdapterByName(doDaName.doName).updateDAI(doDaName.daName, setting.getChannelLevMod().value()) : Optional.empty(); - } - } - case STVAL_DA_NAME -> lnAdapter.getDOIAdapterByName(doDaName.doName).updateDAI(doDaName.daName, ActiveStatus.ON.getValue()); - case SETSRCREF_DA_NAME -> lnAdapter.getDOIAdapterByName(doDaName.doName).updateDAI(doDaName.daName, computeDaiValue(lnPrefix, extRef, setting.getDAName())); - default -> throw new IllegalStateException("Unexpected value: " + doDaName.daName); - }; - sclReportItem.ifPresent(errorHandler::add); - } - private String computeDaiValue(String lnPrefix, TExtRef extRef, String daName) { if (LN_PREFIX_B.equals(lnPrefix) || LN_PREFIX_A.equals(lnPrefix)) { return extRef.getIedName() + @@ -400,6 +350,63 @@ private String computeDaiValue(String lnPrefix, TExtRef extRef, String daName) { } } + private void updateLDEPFDos(TDataTypeTemplates dtt, TIED tied, TLDevice tlDevice, TExtRef tExtRef, TChannel setting) { + // Digital + if (setting.getChannelType().equals(TChannelType.DIGITAL)) { + lnEditor.findLn(tlDevice, tAnyLN -> lnEditor.matchesLn(tAnyLN, LN_RBDR, setting.getChannelNum(), null)) + .ifPresent(tln -> updateDaiValue(dtt, tied, tlDevice, tln, tExtRef, setting)); + lnEditor.findLn(tlDevice, tAnyLN -> lnEditor.matchesLn(tAnyLN, LN_RBDR, setting.getChannelNum(), LN_PREFIX_B)) + .ifPresent(tln -> updateDaiValue(dtt, tied, tlDevice, tln, tExtRef, setting)); + } + // Analog + if (setting.getChannelType().equals(TChannelType.ANALOG)) { + lnEditor.findLn(tlDevice, tAnyLN -> lnEditor.matchesLn(tAnyLN, LN_RADR, setting.getChannelNum(), null)) + .ifPresent(tln -> updateDaiValue(dtt, tied, tlDevice, tln, tExtRef, setting)); + lnEditor.findLn(tlDevice, tAnyLN -> lnEditor.matchesLn(tAnyLN, LN_RBDR, setting.getChannelNum(), LN_PREFIX_A)) + .ifPresent(tln -> updateDaiValue(dtt, tied, tlDevice, tln, tExtRef, setting)); + } + } + + 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, LnId.from(tln).prefix(), tExtRef, setting)) + .ifPresent(newDaiValue -> updateDaiVal(dtt, tied, tlDevice, tln, DoLinkedToDaFilter.from(doNameAndDaName.doName, doNameAndDaName.daName), newDaiValue))); + } + + private String getNewDaiValue(String daName, String lnPrefix, 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(lnPrefix) || LN_PREFIX_A.equals(lnPrefix)){ + 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(lnPrefix, extRef, setting.getDAName()); + default -> throw new IllegalStateException("Unexpected value: " + setting.getDAName()); + }; + } + + 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.get().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..0b3a2c2e4 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 ThreadLocal> errorHandler = ThreadLocal.withInitial(ArrayList::new); @Override public SCL initScl(final UUID hId, final String hVersion, final String hRevision) throws ScdException { @@ -221,37 +221,41 @@ public List updateDoInRef(SCL scd) { @Override public List manageMonitoringLns(SCL scd) { - errorHanlder.clear(); - iedService.getFilteredIeds(scd, ied -> !ied.getName().contains(IED_TEST_NAME)) - .forEach(tied -> { - Map> serviceTypeToIedSource = ldeviceService.getLdevices(tied) - .flatMap(tlDevice -> extRefReaderService.getExtRefs(tlDevice.getLN0())) - .filter(tExtRef -> tExtRef.isSetServiceType() && tExtRef.isSetSrcCBName() && (tExtRef.getServiceType().equals(TServiceType.GOOSE) || tExtRef.getServiceType().equals(TServiceType.SMV))) - .collect(Collectors.groupingBy(tExtRef -> new IedSource(tExtRef.getIedName(), tExtRef.getSrcCBName(), tExtRef.getSrcLDInst(), tExtRef.getServiceType()))) - .keySet() - .stream() - .collect(Collectors.groupingBy(IedSource::serviceType)); - ldeviceService.findLdevice(tied, LDEVICE_LDSUIED).ifPresent(ldSUIEDLDevice -> { - Optional.ofNullable(serviceTypeToIedSource.get(TServiceType.GOOSE)) - .ifPresent(iedSourceKeys -> manageMonitoringLns(iedSourceKeys, scd, tied, ldSUIEDLDevice, DO_GOCBREF, MonitoringLnClassEnum.LGOS)); - Optional.ofNullable(serviceTypeToIedSource.get(TServiceType.SMV)) - .ifPresent(iedSourceKeys -> manageMonitoringLns(iedSourceKeys, scd, tied, ldSUIEDLDevice, DO_SVCBREF, MonitoringLnClassEnum.LSVS)); + errorHandler.get().clear(); + try { + iedService.getFilteredIeds(scd, ied -> !ied.getName().contains(IED_TEST_NAME)) + .forEach(tied -> { + Map> serviceTypeToIedSource = ldeviceService.getLdevices(tied) + .flatMap(tlDevice -> extRefReaderService.getExtRefs(tlDevice.getLN0())) + .filter(tExtRef -> tExtRef.isSetServiceType() && tExtRef.isSetSrcCBName() && (tExtRef.getServiceType().equals(TServiceType.GOOSE) || tExtRef.getServiceType().equals(TServiceType.SMV))) + .collect(Collectors.groupingBy(tExtRef -> new IedSource(tExtRef.getIedName(), tExtRef.getSrcCBName(), tExtRef.getSrcLDInst(), tExtRef.getServiceType()))) + .keySet() + .stream() + .collect(Collectors.groupingBy(IedSource::serviceType)); + ldeviceService.findLdevice(tied, LDEVICE_LDSUIED).ifPresent(ldSUIEDLDevice -> { + Optional.ofNullable(serviceTypeToIedSource.get(TServiceType.GOOSE)) + .ifPresent(iedSourceKeys -> manageMonitoringLns(iedSourceKeys, scd, tied, ldSUIEDLDevice, DO_GOCBREF, MonitoringLnClassEnum.LGOS)); + Optional.ofNullable(serviceTypeToIedSource.get(TServiceType.SMV)) + .ifPresent(iedSourceKeys -> manageMonitoringLns(iedSourceKeys, scd, tied, ldSUIEDLDevice, DO_SVCBREF, MonitoringLnClassEnum.LSVS)); + }); }); - }); - return errorHanlder; + return errorHandler.get(); + } finally { + errorHandler.remove(); + } } 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.get().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.get().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..99ca9f04f 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,14 @@ 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; +import java.util.stream.Stream; public interface LnEditor { @@ -24,4 +24,10 @@ public interface LnEditor { void updateOrCreateDOAndDAInstances(TAnyLN tAnyLN, DoLinkedToDa doLinkedToDa); DoLinkedToDa getDoLinkedToDaCompletedFromDAI(TIED tied, String ldInst, TAnyLN anyLN, DoLinkedToDa doLinkedToDa); + + Stream getAnylns(TLDevice tlDevice); + + Optional findLn(TLDevice tlDevice, Predicate lnPredicate); + + boolean matchesLn(TAnyLN tAnyLN, String lnClass, String lnInst, String lnPrefix); } 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..c1c171f1e 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 @@ -40,6 +40,7 @@ public DataAttribute deepCopy() { } public void addDaVal(List vals) { + vals.forEach(this::addDaVal); vals.forEach(tVal -> { Long settingGroup = tVal.isSetSGroup() ? tVal.getSGroup() : null; daiValues.removeIf(daVal -> Objects.equals(daVal.settingGroup(), settingGroup)); @@ -47,6 +48,12 @@ public void addDaVal(List vals) { }); } + 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(){ return daName + (getBdaNames().isEmpty() ? StringUtils.EMPTY : "." + String.join(".", getBdaNames())); 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 4463238f2..5684b7a9d 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 @@ -36,7 +36,7 @@ class ExtRefEditorServiceTest { @BeforeEach void init() { - extRefEditorService = new ExtRefEditorService(new IedService(), new LdeviceService(new LnService()), new LnService()); + extRefEditorService = new ExtRefEditorService(new IedService(), new LdeviceService(new LnService()), new LnService(), new DataTypeTemplatesService()); } @Test @@ -630,8 +630,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_internal_bind.xml"); TChannel digitalChannel = new TChannel(); @@ -690,6 +690,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()) @@ -697,7 +702,7 @@ void manageBindingForLDEPF_should_not_update_dai_setVal_when_channelLevMod_or_ch .allSatisfy(tln -> { assertThat(getDaiValue(tln, CHNUM1_DO_NAME, DU_DA_NAME)).isEqualTo("MR.PX1"); assertThat(getDaiValue(tln, MOD_DO_NAME, STVAL_DA_NAME)).isEqualTo("on"); - assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)).isEqualTo("ADF"); + assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)).isEqualTo(expectedChannelLevMod); assertThat(getDaiValue(tln, SRCREF_DO_NAME, SETSRCREF_DA_NAME)).isEqualTo("IED_NAME1LDPX/PTRC0.Str.general"); }); assertThat(tlDevice.getLN()) @@ -705,7 +710,7 @@ void manageBindingForLDEPF_should_not_update_dai_setVal_when_channelLevMod_or_ch .allSatisfy(tln -> { assertThat(getDaiValue(tln, CHNUM1_DO_NAME, DU_DA_NAME)).isEqualTo("MR.PX1"); assertThat(getDaiValue(tln, MOD_DO_NAME, STVAL_DA_NAME)).isEqualTo("on"); - assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)).isEqualTo("ADF"); + assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)).isEqualTo(expectedChannelLevModq); assertThat(getDaiValue(tln, SRCREF_DO_NAME, SETSRCREF_DA_NAME)).isEqualTo("IED_NAME1LDPX/PTRC0.Str.q"); }); }) @@ -716,7 +721,7 @@ void manageBindingForLDEPF_should_not_update_dai_setVal_when_channelLevMod_or_ch .allSatisfy(tln -> { assertThat(getDaiValue(tln, CHNUM1_DO_NAME, DU_DA_NAME)).isEqualTo("MR.PX1"); assertThat(getDaiValue(tln, MOD_DO_NAME, STVAL_DA_NAME)).isEqualTo("on"); - assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)).isEqualTo("ADF"); + assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)).isEqualTo(expectedChannelLevMod); assertThat(getDaiValue(tln, SRCREF_DO_NAME, SETSRCREF_DA_NAME)).isEqualTo("IED_NAME1LDPX/PTRC0.Str.general"); }); assertThat(tlDevice.getLN()) @@ -724,7 +729,7 @@ void manageBindingForLDEPF_should_not_update_dai_setVal_when_channelLevMod_or_ch .allSatisfy(tln -> { assertThat(getDaiValue(tln, CHNUM1_DO_NAME, DU_DA_NAME)).isEqualTo("MR.PX1"); assertThat(getDaiValue(tln, MOD_DO_NAME, STVAL_DA_NAME)).isEqualTo("on"); - assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)).isEqualTo("ADF"); + assertThat(getDaiValue(tln, LEVMOD_DO_NAME, SETVAL_DA_NAME)).isEqualTo(expectedChannelLevModq); assertThat(getDaiValue(tln, SRCREF_DO_NAME, SETSRCREF_DA_NAME)).isEqualTo("IED_NAME1LDPX/PTRC0.Str.q"); }); }); @@ -736,7 +741,6 @@ void manageBindingForLDEPF_should_return_error_report_when_missing_mandatory_pri SCL scd = SclTestMarshaller.getSCLFromFile("/scd-ldepf/scd_ldepf_processing_internal_bind.xml"); scd.getIED().forEach(tied -> tied.getPrivate().removeIf(tPrivate -> tPrivate.getType().equals(PrivateEnum.COMPAS_BAY.getPrivateType()) || tPrivate.getType().equals(PrivateEnum.COMPAS_ICDHEADER.getPrivateType()))); - TChannel digitalChannel = new TChannel(); digitalChannel.setBayScope(TCBScopeType.BAY_INTERNAL); digitalChannel.setChannelType(TChannelType.DIGITAL); @@ -764,6 +768,48 @@ void manageBindingForLDEPF_should_return_error_report_when_missing_mandatory_pri SclReportItem.error("SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LDEPF\"]", "The IED has no Private compas:ICDHeader")); } + @Test + void manageBindingForLDEPF_should_return_warning_when_dai_is_not_updatable() { + //Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-ldepf/scd_ldepf_processing_internal_bind.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) + .extracting(SclReportItem::isError, SclReportItem::xpath, SclReportItem::message) + .containsExactly(tuple(false, "IED_NAME1/LDSUIED/RBDR/DOI@name=\"ChNum1\"/DAI@name=\"dU\"/Val", "The DAI cannot be updated")); + } + @Test void manageBindingForLDEPF_should_not_been_configured_when_ldepf_ln0_is_off() { //Given