diff --git a/src/main/java/org/gridsuite/modification/dto/CompositeModificationInfos.java b/src/main/java/org/gridsuite/modification/dto/CompositeModificationInfos.java index 96ea4838..5941ed58 100644 --- a/src/main/java/org/gridsuite/modification/dto/CompositeModificationInfos.java +++ b/src/main/java/org/gridsuite/modification/dto/CompositeModificationInfos.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.powsybl.commons.report.ReportNode; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.NoArgsConstructor; @@ -33,6 +34,9 @@ @ModificationErrorTypeName("COMPOSITE_MODIFICATION_ERROR") public class CompositeModificationInfos extends ModificationInfos { + @Schema(description = "composite modification name") + private String compositeName; + @Schema(description = "composite modification list") @JsonInclude(JsonInclude.Include.NON_NULL) private List modifications; @@ -41,4 +45,12 @@ public class CompositeModificationInfos extends ModificationInfos { public AbstractModification toModification() { return new CompositeModification(this); } + + @Override + public ReportNode createSubReportNode(ReportNode reportNode) { + return reportNode.newReportNode() + .withMessageTemplate("network.modification.composite") + .withUntypedValue("modificationName", getCompositeName()) + .add(); + } } diff --git a/src/main/java/org/gridsuite/modification/modifications/CompositeModification.java b/src/main/java/org/gridsuite/modification/modifications/CompositeModification.java index 8feacfcd..66270398 100644 --- a/src/main/java/org/gridsuite/modification/modifications/CompositeModification.java +++ b/src/main/java/org/gridsuite/modification/modifications/CompositeModification.java @@ -7,20 +7,44 @@ package org.gridsuite.modification.modifications; import com.powsybl.commons.report.ReportNode; +import com.powsybl.commons.report.TypedValue; import com.powsybl.iidm.network.Network; import org.gridsuite.modification.dto.CompositeModificationInfos; +import org.gridsuite.modification.report.NetworkModificationReportResourceBundle; + +import static org.gridsuite.modification.modifications.byfilter.AbstractModificationByAssignment.VALUE_KEY_ERROR_MESSAGE; /** * @author Ghazwa Rehili */ public class CompositeModification extends AbstractModification { + private final CompositeModificationInfos compositeModificationInfos; + public CompositeModification(CompositeModificationInfos compositeModificationInfos) { + this.compositeModificationInfos = compositeModificationInfos; } @Override public void apply(Network network, ReportNode subReportNode) { - throw new UnsupportedOperationException(); + compositeModificationInfos.getModifications().forEach( + modif -> { + ReportNode modifNode = modif.createSubReportNode(subReportNode); + try { + modif.toModification().apply(network, modifNode); + } catch (Exception e) { + // in case of error in a network modification, the composite modification doesn't interrupt its execution : + // the following modifications will be carried out + modifNode.newReportNode() + .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) + .withMessageTemplate("network.modification.compositeReportException") + .withUntypedValue("modificationName", modif.toModification().getName()) + .withUntypedValue(VALUE_KEY_ERROR_MESSAGE, e.getMessage()) + .withSeverity(TypedValue.WARN_SEVERITY) + .add(); + } + } + ); } @Override diff --git a/src/main/resources/org/gridsuite/modification/reports.properties b/src/main/resources/org/gridsuite/modification/reports.properties index 845a8db5..5c8f29f0 100644 --- a/src/main/resources/org/gridsuite/modification/reports.properties +++ b/src/main/resources/org/gridsuite/modification/reports.properties @@ -190,6 +190,8 @@ network.modification.noLimitSetSelectedOnSide1 = No limit set selected on side 1 network.modification.noLimitSetSelectedOnSide2 = No limit set selected on side 2 network.modification.limitSetAbsentOnSide1 = limit set '${selectedOperationalLimitsGroup}' on side 1 does not exist network.modification.limitSetAbsentOnSide2 = limit set '${selectedOperationalLimitsGroup}' on side 2 does not exist +network.modification.composite = Composite modification : '${modificationName}' +network.modification.compositeReportException = Cannot execute ${modificationName} : ${errorMessage} network.modification.applicabilityChanged = limit set ${operationalLimitsGroupName} applicability changed to ${applicability} network.modification.limits = Limits network.modification.activeLimitsSets = Active limits sets diff --git a/src/test/java/org/gridsuite/modification/modifications/CompositeModificationsTest.java b/src/test/java/org/gridsuite/modification/modifications/CompositeModificationsTest.java index ac004b93..ed6644c3 100644 --- a/src/test/java/org/gridsuite/modification/modifications/CompositeModificationsTest.java +++ b/src/test/java/org/gridsuite/modification/modifications/CompositeModificationsTest.java @@ -6,18 +6,22 @@ */ package org.gridsuite.modification.modifications; +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.LoadType; import com.powsybl.iidm.network.Network; import org.gridsuite.modification.ModificationType; -import org.gridsuite.modification.dto.CompositeModificationInfos; -import org.gridsuite.modification.dto.ModificationInfos; +import org.gridsuite.modification.dto.*; +import org.gridsuite.modification.report.NetworkModificationReportResourceBundle; import org.gridsuite.modification.utils.ModificationCreation; import org.gridsuite.modification.utils.NetworkCreation; import java.util.List; import java.util.UUID; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.gridsuite.modification.utils.TestUtils.*; +import static org.junit.jupiter.api.Assertions.*; /** * @author Ghazwa Rehili @@ -27,13 +31,48 @@ class CompositeModificationsTest extends AbstractNetworkModificationTest { @Override public void checkModification() { + Network network = getNetwork(); + CompositeModificationInfos compositeModificationInfos = (CompositeModificationInfos) buildModification(); + + // checks that the sub sub sub netmod is executed at the right depth + ReportNode report = compositeModificationInfos.createSubReportNode(ReportNode.newRootReportNode() + .withResourceBundles(NetworkModificationReportResourceBundle.BASE_NAME) + .withMessageTemplate("test") + .build()); + CompositeModification netmod = (CompositeModification) compositeModificationInfos.toModification(); + assertDoesNotThrow(() -> netmod.apply(network, report)); + assertLogMessageAtDepth( + "Generator with id=idGenerator modified :", + "network.modification.generatorModification", + report, + 4 + ); + assertLogMessageAtDepth( + "Composite modification : 'sub sub composite'", + "network.modification.composite", + report, + 2 + ); + + // regular throwing exception netmod + GeneratorCreation throwingExceptionNetMod = (GeneratorCreation) buildThrowingModification().toModification(); + assertThrows(PowsyblException.class, () -> throwingExceptionNetMod.apply(network)); + // but doesn't throw once inside a composite modification + compositeModificationInfos.setModifications(List.of(buildThrowingModification())); + CompositeModification netmodContainingError = (CompositeModification) compositeModificationInfos.toModification(); + assertDoesNotThrow(() -> netmodContainingError.apply(network, report)); + // but the thrown message is inside the report : + assertLogMessageWithoutRank( + "Cannot execute GeneratorCreation : The network " + getNetwork().getId() + " already contains an object 'GeneratorImpl' with the id 'idGenerator'", + "network.modification.compositeReportException", + report + ); } - @Override - public void testApply() throws Exception { - CompositeModificationInfos compositeModificationInfos = (CompositeModificationInfos) buildModification(); - compositeModificationInfos.getModifications().forEach(modificationInfos -> modificationInfos.toModification().apply(getNetwork())); - assertAfterNetworkModificationApplication(); + private GeneratorCreationInfos buildThrowingModification() { + return ModificationCreation.getCreationGenerator( + "v1", "idGenerator", "nameGenerator", "1B", "v2load", "LOAD", "v1" + ); } @Override @@ -44,11 +83,34 @@ protected Network createNetwork(UUID networkUuid) { @Override protected ModificationInfos buildModification() { List modifications = List.of( - ModificationCreation.getCreationGenerator("v1", "idGenerator", "nameGenerator", "1B", "v2load", "LOAD", - "v1"), + CompositeModificationInfos.builder() + .compositeName("sub composite 1") + .modifications( + List.of( + ModificationCreation.getModificationGenerator("idGenerator", "other idGenerator name"), + // this should throw an error but not stop the execution of the composite modification and all the other content + buildThrowingModification() + ) + ).build(), + ModificationCreation.getModificationGenerator("idGenerator", "new idGenerator name"), ModificationCreation.getCreationLoad("v1", "idLoad", "nameLoad", "1.1", LoadType.UNDEFINED), - ModificationCreation.getCreationBattery("v1", "idBattery", "nameBattry", "1.1")); + ModificationCreation.getCreationBattery("v1", "idBattery", "nameBattery", "1.1"), + // test of a composite modification inside a composite modification inside a composite modification + CompositeModificationInfos.builder() + .compositeName("sub composite 2") + .modifications( + List.of( + CompositeModificationInfos.builder() + .compositeName("sub sub composite") + .modifications( + List.of(ModificationCreation.getModificationGenerator("idGenerator", "other idGenerator name again")) + ).build(), + ModificationCreation.getModificationGenerator("idGenerator", "even newer idGenerator name") + ) + ).build() + ); return CompositeModificationInfos.builder() + .compositeName("main composite") .modifications(modifications) .stashed(false) .build(); @@ -56,7 +118,9 @@ protected ModificationInfos buildModification() { @Override protected void assertAfterNetworkModificationApplication() { - assertNotNull(getNetwork().getGenerator("idGenerator")); + Generator gen = getNetwork().getGenerator("idGenerator"); + assertNotNull(gen); + assertEquals("even newer idGenerator name", gen.getOptionalName().orElseThrow()); assertNotNull(getNetwork().getLoad("idLoad")); assertNotNull(getNetwork().getBattery("idBattery")); } diff --git a/src/test/java/org/gridsuite/modification/utils/TestUtils.java b/src/test/java/org/gridsuite/modification/utils/TestUtils.java index 52c73bdb..bf93c9e0 100644 --- a/src/test/java/org/gridsuite/modification/utils/TestUtils.java +++ b/src/test/java/org/gridsuite/modification/utils/TestUtils.java @@ -58,6 +58,12 @@ public static void assertLogNthMessage(String expectedMessage, String reportKey, assertEquals(expectedMessage, message.get().trim()); } + public static void assertLogMessageAtDepth(String expectedMessage, String reportKey, ReportNode reportNode, int depth) { + Optional message = getMessageFromReporterAtDepth(reportKey, reportNode, 1, depth); + assertTrue(message.isPresent()); + assertEquals(expectedMessage, message.get().trim()); + } + public static void assertLogMessage(String expectedMessage, String reportKey, ReportNode reportNode) { assertLogNthMessage(expectedMessage, reportKey, reportNode, 1); } @@ -85,6 +91,28 @@ private static boolean assertMessageFoundFromReporter(String expectedMessage, St return foundInSubReporters; } + private static Optional getMessageFromReporterAtDepth(String reportKey, ReportNode reporterModel, int currentDepth, int expectedDepth) { + Optional message = Optional.empty(); + + Iterator reportersIterator = reporterModel.getChildren().iterator(); + while (message.isEmpty() && reportersIterator.hasNext() && currentDepth < expectedDepth) { + message = getMessageFromReporterAtDepth(reportKey, reportersIterator.next(), currentDepth + 1, expectedDepth); + } + + Iterator reportsIterator = reporterModel.getChildren().iterator(); + while (message.isEmpty() && reportsIterator.hasNext()) { + ReportNode report = reportsIterator.next(); + if (currentDepth == expectedDepth && report.getMessageKey().equals(reportKey)) { + message = Optional.of(formatReportMessage(report, reporterModel)); + } + } + + return message; + } + + /** + * @param rank order position inside reporterModel + */ private static Optional getMessageFromReporter(String reportKey, ReportNode reporterModel, int rank) { Optional message = Optional.empty();