From 0985313ecb742b09ec389f111747cb33863af00a Mon Sep 17 00:00:00 2001 From: Nicolas Rol Date: Wed, 5 Feb 2025 17:26:53 +0100 Subject: [PATCH 1/7] Change GroovyScriptExtension.load to use a map of context objects Signed-off-by: Nicolas Rol --- afs-ext-base/pom.xml | 4 + .../afs/ext/base/AbstractProjectCase.java | 66 ++++++++++++++ .../powsybl/afs/ext/base/ImportedCase.java | 25 +----- .../ext/base/LocalNetworkCacheService.java | 60 +++++++++---- .../afs/ext/base/NetworkCacheService.java | 8 ++ .../com/powsybl/afs/ext/base/ProjectCase.java | 8 ++ .../com/powsybl/afs/ext/base/ScriptCache.java | 33 ++++--- .../com/powsybl/afs/ext/base/ScriptUtils.java | 17 +++- .../com/powsybl/afs/ext/base/VirtualCase.java | 34 ++----- .../afs/ext/base/ImportedCaseTest.java | 62 +++++++------ .../client/RemoteNetworkCacheService.java | 90 +++++++++---------- .../scripting/AfsGroovyScriptExtension.groovy | 25 +++--- .../scripting/AbstractGroovyScriptTest.java | 37 ++++---- 13 files changed, 280 insertions(+), 189 deletions(-) create mode 100644 afs-ext-base/src/main/java/com/powsybl/afs/ext/base/AbstractProjectCase.java diff --git a/afs-ext-base/pom.xml b/afs-ext-base/pom.xml index 661af4b7..165c5f4e 100644 --- a/afs-ext-base/pom.xml +++ b/afs-ext-base/pom.xml @@ -115,6 +115,10 @@ powsybl-iidm-impl test + + com.powsybl + powsybl-scripting + diff --git a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/AbstractProjectCase.java b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/AbstractProjectCase.java new file mode 100644 index 00000000..99a29641 --- /dev/null +++ b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/AbstractProjectCase.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025, RTE (https://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.afs.ext.base; + +import com.powsybl.afs.ProjectFile; +import com.powsybl.afs.ProjectFileCreationContext; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.NetworkListener; +import com.powsybl.scripting.groovy.GroovyScriptExtension; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * @author Nicolas Rol {@literal } + */ +public abstract class AbstractProjectCase extends ProjectFile implements ProjectCase { + + protected AbstractProjectCase(ProjectFileCreationContext context, int version) { + super(context, version); + } + + @Override + public String queryNetwork(ScriptType scriptType, String scriptContent) { + return queryNetwork(scriptType, scriptContent, Collections.emptyList(), Collections.emptyMap()); + } + + @Override + public String queryNetwork(ScriptType scriptType, String scriptContent, Iterable extensions, Map, Object> contextObjects) { + Objects.requireNonNull(scriptType); + Objects.requireNonNull(scriptContent); + return findService(NetworkCacheService.class).queryNetwork(this, scriptType, scriptContent, extensions, contextObjects); + } + + @Override + public Network getNetwork() { + return findService(NetworkCacheService.class).getNetwork(this); + } + + @Override + public Network getNetwork(Iterable extensions, Map, Object> contextObjects) { + return findService(NetworkCacheService.class).getNetwork(this, extensions, contextObjects); + } + + @Override + public Network getNetwork(List listeners) { + return findService(NetworkCacheService.class).getNetwork(this, listeners); + } + + @Override + public Network getNetwork(Iterable extensions, Map, Object> contextObjects, List listeners) { + return findService(NetworkCacheService.class).getNetwork(this, listeners, extensions, contextObjects); + } + + @Override + public void invalidateNetworkCache() { + findService(NetworkCacheService.class).invalidateCache(this); + } +} diff --git a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ImportedCase.java b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ImportedCase.java index 1c84e871..b0d76a67 100644 --- a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ImportedCase.java +++ b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ImportedCase.java @@ -14,7 +14,6 @@ import com.powsybl.iidm.network.Importer; import com.powsybl.iidm.network.ImportersLoader; import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.NetworkListener; import java.io.IOException; import java.io.InputStreamReader; @@ -31,7 +30,7 @@ * * @author Geoffroy Jamgotchian */ -public class ImportedCase extends ProjectFile implements ProjectCase { +public class ImportedCase extends AbstractProjectCase { public static final String PSEUDO_CLASS = "importedCase"; public static final int VERSION = 0; @@ -69,28 +68,6 @@ public Importer getImporter() { .orElseThrow(() -> new AfsException("Importer not found for format " + format)); } - @Override - public String queryNetwork(ScriptType scriptType, String scriptContent) { - Objects.requireNonNull(scriptType); - Objects.requireNonNull(scriptContent); - return findService(NetworkCacheService.class).queryNetwork(this, scriptType, scriptContent); - } - - @Override - public Network getNetwork() { - return findService(NetworkCacheService.class).getNetwork(this); - } - - @Override - public Network getNetwork(List listeners) { - return findService(NetworkCacheService.class).getNetwork(this, listeners); - } - - @Override - public void invalidateNetworkCache() { - findService(NetworkCacheService.class).invalidateCache(this); - } - @Override protected List invalidate() { List dependencies = super.invalidate(); diff --git a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/LocalNetworkCacheService.java b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/LocalNetworkCacheService.java index f98316ba..4d8e16dd 100644 --- a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/LocalNetworkCacheService.java +++ b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/LocalNetworkCacheService.java @@ -13,11 +13,17 @@ import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.NetworkFactory; import com.powsybl.iidm.network.NetworkListener; +import com.powsybl.scripting.groovy.GroovyScriptExtension; import groovy.json.JsonOutput; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.UUID; /** * @author Geoffroy Jamgotchian {@literal } @@ -29,11 +35,11 @@ public class LocalNetworkCacheService implements NetworkCacheService { private final ScriptCache cache; public LocalNetworkCacheService() { - cache = new ScriptCache<>(50, 1, projectFile -> { + cache = new ScriptCache<>(50, 1, (projectFile, extensions, contextObjects) -> { UUID taskId = projectFile.startTask(); try { projectFile.createLogger(taskId).log("Loading network..."); - return loadNetworkFromProjectCase((ProjectCase) projectFile); + return loadNetworkFromProjectCase((ProjectCase) projectFile, extensions, contextObjects); } finally { projectFile.stopTask(taskId); } @@ -55,8 +61,8 @@ private static ScriptResult loadNetworkFromImportedCase(ImportedCase im return ScriptResult.of(network); } - private static ScriptResult applyScript(Network network, String previousScriptOutput, ModificationScript script) { - ScriptResult result = ScriptUtils.runScript(network, script.getScriptType(), script.readScript(true)); + private static ScriptResult applyScript(Network network, String previousScriptOutput, ModificationScript script, Iterable extensions, Map, Object> contextObjects) { + ScriptResult result = ScriptUtils.runScript(network, script.getScriptType(), script.readScript(true), extensions, contextObjects); if (result.getError() == null) { return new ScriptResult<>(network, previousScriptOutput + result.getOutput(), null); } else { @@ -65,9 +71,8 @@ private static ScriptResult applyScript(Network network, String previou } } - private static ScriptResult loadNetworkFromVirtualCase(VirtualCase virtualCase, List listeners) { - ProjectCase baseCase = (ProjectCase) virtualCase.getCase() - .orElseThrow(() -> new AfsException("Case link is dead")); + private static ScriptResult loadNetworkFromVirtualCase(VirtualCase virtualCase, List listeners, Iterable extensions, Map, Object> contextObjects) { + ProjectCase baseCase = (ProjectCase) virtualCase.getCase().orElseThrow(() -> new AfsException("Case link is dead")); ScriptResult network = loadNetworkFromProjectCase(baseCase, listeners); @@ -75,23 +80,26 @@ private static ScriptResult loadNetworkFromVirtualCase(VirtualCase virt return network; } - ModificationScript script = virtualCase.getScript() - .orElseThrow(VirtualCase::createScriptLinkIsDeadException); + ModificationScript script = virtualCase.getScript().orElseThrow(VirtualCase::createScriptLinkIsDeadException); LOGGER.info("Applying script to network of project case {}", virtualCase.getId()); - return applyScript(network.getValue(), network.getOutput(), script); + return applyScript(network.getValue(), network.getOutput(), script, extensions, contextObjects); } - private static ScriptResult loadNetworkFromProjectCase(ProjectCase projectCase) { - return loadNetworkFromProjectCase(projectCase, Collections.emptyList()); + private static ScriptResult loadNetworkFromProjectCase(ProjectCase projectCase, List listeners) { + return loadNetworkFromProjectCase(projectCase, listeners, Collections.emptyList(), Collections.emptyMap()); } - private static ScriptResult loadNetworkFromProjectCase(ProjectCase projectCase, List listeners) { + private static ScriptResult loadNetworkFromProjectCase(ProjectCase projectCase, Iterable extensions, Map, Object> contextObjects) { + return loadNetworkFromProjectCase(projectCase, Collections.emptyList(), extensions, contextObjects); + } + + private static ScriptResult loadNetworkFromProjectCase(ProjectCase projectCase, List listeners, Iterable extensions, Map, Object> contextObjects) { if (projectCase instanceof ImportedCase importedCase) { return loadNetworkFromImportedCase(importedCase, listeners); } else if (projectCase instanceof VirtualCase virtualCase) { - return loadNetworkFromVirtualCase(virtualCase, listeners); + return loadNetworkFromVirtualCase(virtualCase, listeners, extensions, contextObjects); } else { throw new AssertionError("ProjectCase implementation " + projectCase.getClass().getName() + " not supported"); } @@ -99,13 +107,17 @@ private static ScriptResult loadNetworkFromProjectCase(ProjectCase proj @Override public String queryNetwork(T projectCase, ScriptType scriptType, String scriptContent) { + return queryNetwork(projectCase, scriptType, scriptContent, Collections.emptyList(), Collections.emptyMap()); + } + + @Override + public String queryNetwork(T projectCase, ScriptType scriptType, String scriptContent, Iterable extensions, Map, Object> contextObjects) { Objects.requireNonNull(projectCase); Objects.requireNonNull(scriptType); Objects.requireNonNull(scriptContent); Network network = getNetwork(projectCase); - - ScriptResult result = ScriptUtils.runScript(network, ScriptType.GROOVY, scriptContent); + ScriptResult result = ScriptUtils.runScript(network, ScriptType.GROOVY, scriptContent, extensions, contextObjects); if (result.getError() != null) { throw new ScriptException(projectCase, result.getError()); } @@ -114,12 +126,22 @@ public String queryNetwork(T projectCase, @Override public Network getNetwork(T projectCase) { - return cache.get(projectCase).getValueOrThrowIfError(projectCase); + return getNetwork(projectCase, Collections.emptyList(), Collections.emptyMap()); + } + + @Override + public Network getNetwork(T projectCase, Iterable extensions, Map, Object> contextObjects) { + return cache.get(projectCase, extensions, contextObjects).getValueOrThrowIfError(projectCase); } @Override public Network getNetwork(T projectCase, List listeners) { - ScriptResult network = loadNetworkFromProjectCase(projectCase, listeners); + return getNetwork(projectCase, listeners, Collections.emptyList(), Collections.emptyMap()); + } + + @Override + public Network getNetwork(T projectCase, List listeners, Iterable extensions, Map, Object> contextObjects) { + ScriptResult network = loadNetworkFromProjectCase(projectCase, listeners, extensions, contextObjects); return network.getValueOrThrowIfError(projectCase); } diff --git a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/NetworkCacheService.java b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/NetworkCacheService.java index c448a4e1..e138dd92 100644 --- a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/NetworkCacheService.java +++ b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/NetworkCacheService.java @@ -9,9 +9,11 @@ import com.powsybl.afs.ProjectFile; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.NetworkListener; +import com.powsybl.scripting.groovy.GroovyScriptExtension; import org.apache.commons.lang3.StringUtils; import java.util.List; +import java.util.Map; /** * Provides caching capabilities for loaded {@code Network} objects. @@ -22,10 +24,16 @@ public interface NetworkCacheService { Network getNetwork(T projectCase); + Network getNetwork(T projectCase, Iterable extensions, Map, Object> contextObjects); + Network getNetwork(T projectCase, List listeners); + Network getNetwork(T projectCase, List listeners, Iterable extensions, Map, Object> contextObjects); + String queryNetwork(T projectCase, ScriptType scriptType, String scriptContent); + String queryNetwork(T projectCase, ScriptType scriptType, String scriptContent, Iterable extensions, Map, Object> contextObjects); + void invalidateCache(T projectCase); void addListener(T projectCase, ProjectCaseListener listener); diff --git a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ProjectCase.java b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ProjectCase.java index 26f5c193..91b3f4f3 100644 --- a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ProjectCase.java +++ b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ProjectCase.java @@ -8,8 +8,10 @@ import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.NetworkListener; +import com.powsybl.scripting.groovy.GroovyScriptExtension; import java.util.List; +import java.util.Map; /** * Common interface for project files able to provide a Network. @@ -20,8 +22,12 @@ public interface ProjectCase { String queryNetwork(ScriptType scriptType, String scriptContent); + String queryNetwork(ScriptType scriptType, String scriptContent, Iterable extensions, Map, Object> contextObjects); + Network getNetwork(); + Network getNetwork(Iterable extensions, Map, Object> contextObjects); + /** * Get the network and add a listeners on it in order to listen changes due to virtual case script application. * The listeners will not be removed from the network at the end of the network loading, @@ -31,6 +37,8 @@ public interface ProjectCase { */ Network getNetwork(List listeners); + Network getNetwork(Iterable extensions, Map, Object> contextObjects, List listeners); + void invalidateNetworkCache(); void addListener(ProjectCaseListener l); diff --git a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ScriptCache.java b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ScriptCache.java index 0137b0b6..d1719d27 100644 --- a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ScriptCache.java +++ b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ScriptCache.java @@ -11,9 +11,12 @@ import com.google.common.util.concurrent.UncheckedExecutionException; import com.powsybl.afs.ProjectFile; import com.powsybl.commons.util.WeakListenerList; +import com.powsybl.scripting.groovy.GroovyScriptExtension; +import org.apache.commons.lang3.function.TriFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -21,10 +24,8 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; -import java.util.function.Function; /** - * * @author Geoffroy Jamgotchian {@literal } */ public class ScriptCache { @@ -35,30 +36,34 @@ public class ScriptCache { private final Map> listeners = new ConcurrentHashMap<>(); - private final Function> loader; + private final TriFunction, Map, Object>, ScriptResult> loader; - public ScriptCache(int maximumSize, int hoursExpiration, Function> loader, + public ScriptCache(int maximumSize, int hoursExpiration, TriFunction, Map, Object>, ScriptResult> loader, BiConsumer, List> notifier) { this.loader = Objects.requireNonNull(loader); Objects.requireNonNull(notifier); cache = CacheBuilder.newBuilder() - .maximumSize(maximumSize) - .expireAfterAccess(hoursExpiration, TimeUnit.HOURS) - .removalListener(notification -> { - String projectFileId = (String) notification.getKey(); + .maximumSize(maximumSize) + .expireAfterAccess(hoursExpiration, TimeUnit.HOURS) + .removalListener(notification -> { + String projectFileId = (String) notification.getKey(); - LOGGER.info("Project file {} cache removed ({})", projectFileId, notification.getCause()); + LOGGER.info("Project file {} cache removed ({})", projectFileId, notification.getCause()); - // notification - notifier.accept((ScriptResult) notification.getValue(), getListeners(projectFileId).toList()); - }) - .build(); + // notification + notifier.accept((ScriptResult) notification.getValue(), getListeners(projectFileId).toList()); + }) + .build(); } public ScriptResult get(F projectFile) { + return get(projectFile, Collections.emptyList(), Collections.emptyMap()); + } + + public ScriptResult get(F projectFile, Iterable extensions, Map, Object> contextObjects) { Objects.requireNonNull(projectFile); try { - return cache.get(projectFile.getId(), () -> loader.apply(projectFile)); + return cache.get(projectFile.getId(), () -> loader.apply(projectFile, extensions, contextObjects)); } catch (ExecutionException e) { throw new UncheckedExecutionException(e); } diff --git a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ScriptUtils.java b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ScriptUtils.java index da0dbdde..d8cf1d5c 100644 --- a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ScriptUtils.java +++ b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/ScriptUtils.java @@ -7,6 +7,7 @@ package com.powsybl.afs.ext.base; import com.powsybl.iidm.network.Network; +import com.powsybl.scripting.groovy.GroovyScriptExtension; import groovy.lang.Binding; import groovy.lang.GroovyShell; import groovy.lang.MissingMethodException; @@ -14,7 +15,12 @@ import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.control.MultipleCompilationErrorsException; -import java.io.*; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.util.Map; /** * @@ -27,7 +33,7 @@ public final class ScriptUtils { private ScriptUtils() { } - private static ScriptResult runGroovyScript(Network network, Reader reader) { + private static ScriptResult runGroovyScript(Network network, Reader reader, Iterable extensions, Map, Object> contextObjects) { String output = ""; ScriptError error = null; Object value = null; @@ -37,6 +43,9 @@ private static ScriptResult runGroovyScript(Network network, Reader read binding.setProperty("network", network); binding.setProperty("out", outputWriter); + // Bindings through extensions + extensions.forEach(extension -> extension.load(binding, contextObjects)); + CompilerConfiguration config = new CompilerConfiguration(); GroovyShell shell = new GroovyShell(binding, config); value = shell.evaluate(reader, SCRIPT_FILE_NAME); @@ -52,10 +61,10 @@ private static ScriptResult runGroovyScript(Network network, Reader read return new ScriptResult<>(value, output, error); } - public static ScriptResult runScript(Network network, ScriptType scriptType, String scriptContent) { + public static ScriptResult runScript(Network network, ScriptType scriptType, String scriptContent, Iterable extensions, Map, Object> contextObjects) { try (Reader reader = new StringReader(scriptContent)) { if (scriptType == ScriptType.GROOVY) { - return runGroovyScript(network, reader); + return runGroovyScript(network, reader, extensions, contextObjects); } else { throw new AssertionError("Script type " + scriptType + " not supported"); } diff --git a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/VirtualCase.java b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/VirtualCase.java index 92bc2a19..f1116334 100644 --- a/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/VirtualCase.java +++ b/afs-ext-base/src/main/java/com/powsybl/afs/ext/base/VirtualCase.java @@ -6,9 +6,11 @@ */ package com.powsybl.afs.ext.base; -import com.powsybl.afs.*; -import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.NetworkListener; +import com.powsybl.afs.AfsCircularDependencyException; +import com.powsybl.afs.AfsException; +import com.powsybl.afs.DependencyCache; +import com.powsybl.afs.ProjectFile; +import com.powsybl.afs.ProjectFileCreationContext; import java.util.Collections; import java.util.List; @@ -18,7 +20,7 @@ /** * @author Geoffroy Jamgotchian {@literal } */ -public class VirtualCase extends ProjectFile implements ProjectCase { +public class VirtualCase extends AbstractProjectCase { public static final String PSEUDO_CLASS = "virtualCase"; public static final int VERSION = 0; @@ -63,28 +65,6 @@ public String getOutput() { return findService(NetworkCacheService.class).getOutput(this); } - @Override - public String queryNetwork(ScriptType scriptType, String scriptContent) { - Objects.requireNonNull(scriptType); - Objects.requireNonNull(scriptContent); - return findService(NetworkCacheService.class).queryNetwork(this, scriptType, scriptContent); - } - - @Override - public Network getNetwork() { - return findService(NetworkCacheService.class).getNetwork(this); - } - - @Override - public Network getNetwork(List listeners) { - return findService(NetworkCacheService.class).getNetwork(this, listeners); - } - - @Override - public void invalidateNetworkCache() { - findService(NetworkCacheService.class).invalidateCache(this); - } - static AfsException createScriptLinkIsDeadException() { return new AfsException("Script link is dead"); } @@ -109,6 +89,6 @@ protected List invalidate() { @Override public boolean mandatoryDependenciesAreMissing() { - return !getCase().isPresent() || !getScript().isPresent(); + return getCase().isEmpty() || getScript().isEmpty(); } } diff --git a/afs-ext-base/src/test/java/com/powsybl/afs/ext/base/ImportedCaseTest.java b/afs-ext-base/src/test/java/com/powsybl/afs/ext/base/ImportedCaseTest.java index 9d732a2b..bce32cbc 100644 --- a/afs-ext-base/src/test/java/com/powsybl/afs/ext/base/ImportedCaseTest.java +++ b/afs-ext-base/src/test/java/com/powsybl/afs/ext/base/ImportedCaseTest.java @@ -6,22 +6,29 @@ */ package com.powsybl.afs.ext.base; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; -import com.powsybl.afs.*; +import com.powsybl.afs.AbstractProjectFileTest; +import com.powsybl.afs.AfsException; +import com.powsybl.afs.FileExtension; +import com.powsybl.afs.Folder; +import com.powsybl.afs.Project; +import com.powsybl.afs.ProjectFileExtension; +import com.powsybl.afs.ProjectFolder; +import com.powsybl.afs.ProjectNode; +import com.powsybl.afs.ServiceExtension; import com.powsybl.afs.mapdb.storage.MapDbAppStorage; import com.powsybl.afs.storage.AppStorage; import com.powsybl.afs.storage.InMemoryEventsBus; import com.powsybl.afs.storage.NodeGenericMetadata; import com.powsybl.afs.storage.NodeInfo; +import com.powsybl.iidm.network.DefaultNetworkListener; import com.powsybl.iidm.network.ExportersLoader; import com.powsybl.iidm.network.ExportersLoaderList; import com.powsybl.iidm.network.ImportConfig; import com.powsybl.iidm.network.ImportersLoader; import com.powsybl.iidm.network.ImportersLoaderList; -import com.powsybl.iidm.network.DefaultNetworkListener; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.NetworkListener; import com.powsybl.iidm.serde.XMLExporter; @@ -37,7 +44,12 @@ import java.util.Collections; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -64,17 +76,17 @@ private ImportersLoader createImportersLoader() { @Override protected List getFileExtensions() { - return ImmutableList.of(new CaseExtension(createImportersLoader())); + return List.of(new CaseExtension(createImportersLoader())); } @Override protected List getProjectFileExtensions() { - return ImmutableList.of(new ImportedCaseExtension(createExportersLoader(), createImportersLoader(), new ImportConfig())); + return List.of(new ImportedCaseExtension(createExportersLoader(), createImportersLoader(), new ImportConfig())); } @Override protected List getServiceExtensions() { - return ImmutableList.of(new LocalNetworkCacheServiceExtension()); + return List.of(new LocalNetworkCacheServiceExtension()); } @Override @@ -84,7 +96,7 @@ public void setup() throws IOException { NodeInfo rootFolderInfo = storage.createRootNodeIfNotExists("root", Folder.PSEUDO_CLASS); NodeInfo nodeInfo = storage.createNode(rootFolderInfo.getId(), "network", Case.PSEUDO_CLASS, "Test format", Case.VERSION, - new NodeGenericMetadata().setString("format", TestImporter.FORMAT)); + new NodeGenericMetadata().setString("format", TestImporter.FORMAT)); storage.setConsistent(nodeInfo.getId()); fileSystem = Jimfs.newFileSystem(Configuration.unix()); @@ -122,15 +134,15 @@ void test() { // import case into project try { folder.fileBuilder(ImportedCaseBuilder.class) - .build(); + .build(); fail(); } catch (AfsException ignored) { } ImportedCase importedCase = folder.fileBuilder(ImportedCaseBuilder.class) - .withCase(aCase) - .withParameter("param1", "true") - .withParameters(ImmutableMap.of("param2", "1")) - .build(); + .withCase(aCase) + .withParameter("param1", "true") + .withParameters(ImmutableMap.of("param2", "1")) + .build(); assertNotNull(importedCase); assertFalse(importedCase.isFolder()); assertNotNull(importedCase.getNetwork()); @@ -141,7 +153,7 @@ void test() { assertNotNull(importedCase.getNetwork(Collections.singletonList(mockedListener))); network.getSubstation("s1").setTso("tso_new"); verify(mockedListener, times(1)) - .onUpdate(network.getSubstation("s1"), "tso", null, "TSO", "tso_new"); + .onUpdate(network.getSubstation("s1"), "tso", null, "TSO", "tso_new"); // test network query assertEquals("[\"s1\"]", importedCase.queryNetwork(ScriptType.GROOVY, "network.substations.collect { it.id }")); @@ -150,7 +162,7 @@ void test() { assertEquals(1, folder.getChildren().size()); ProjectNode projectNode = folder.getChildren().get(0); assertNotNull(projectNode); - assertTrue(projectNode instanceof ImportedCase); + assertInstanceOf(ImportedCase.class, projectNode); ImportedCase importedCase2 = (ImportedCase) projectNode; assertEquals(TestImporter.FORMAT, importedCase2.getImporter().getFormat()); assertEquals(2, importedCase2.getParameters().size()); @@ -180,15 +192,15 @@ void testFile() { assertTrue(folder.getChildren().isEmpty()); ImportedCase importedCase = folder.fileBuilder(ImportedCaseBuilder.class) - .withFile(fileSystem.getPath("/work/network.tst")) - .withName("test") - .build(); + .withFile(fileSystem.getPath("/work/network.tst")) + .withName("test") + .build(); assertNotNull(importedCase); assertEquals("test", importedCase.getName()); ImportedCase importedCase2 = folder.fileBuilder(ImportedCaseBuilder.class) - .withFile(fileSystem.getPath("/work/network.tst")) - .build(); + .withFile(fileSystem.getPath("/work/network.tst")) + .build(); assertNotNull(importedCase2); assertEquals("network", importedCase2.getName()); } @@ -207,15 +219,15 @@ void testNetwork() { Network network = Network.create("NetworkID", "scripting"); ImportedCase importedCase1 = folder.fileBuilder(ImportedCaseBuilder.class) - .withName("test") - .withNetwork(network) - .build(); + .withName("test") + .withNetwork(network) + .build(); assertNotNull(importedCase1); assertEquals("test", importedCase1.getName()); ImportedCase importedCase2 = folder.fileBuilder(ImportedCaseBuilder.class) - .withNetwork(network) - .build(); + .withNetwork(network) + .build(); assertNotNull(importedCase2); assertEquals("NetworkID", importedCase2.getName()); } diff --git a/afs-network/afs-network-client/src/main/java/com/powsybl/afs/network/client/RemoteNetworkCacheService.java b/afs-network/afs-network-client/src/main/java/com/powsybl/afs/network/client/RemoteNetworkCacheService.java index dd172b4c..a92f5242 100644 --- a/afs-network/afs-network-client/src/main/java/com/powsybl/afs/network/client/RemoteNetworkCacheService.java +++ b/afs-network/afs-network-client/src/main/java/com/powsybl/afs/network/client/RemoteNetworkCacheService.java @@ -18,6 +18,7 @@ import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.NetworkListener; import com.powsybl.iidm.serde.NetworkSerDe; +import com.powsybl.scripting.groovy.GroovyScriptExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,11 +28,13 @@ import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; + import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.net.URI; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -59,8 +62,8 @@ class RemoteNetworkCacheService implements NetworkCacheService { private static WebTarget createWebTarget(Client client, URI baseUri) { return client.target(baseUri) - .path("rest") - .path("networkCache"); + .path("rest") + .path("networkCache"); } private RemoteServiceConfig getConfig() { @@ -75,32 +78,36 @@ public Network getNetwork(T projectCase, L return getNetwork(projectCase); } + @Override + public Network getNetwork(T projectCase, Iterable extensions, Map, Object> contextObjects) { + return getNetwork(projectCase); + } + + @Override + public Network getNetwork(T projectCase, List listeners, Iterable extensions, Map, Object> contextObjects) { + return getNetwork(projectCase); + } + @Override public Network getNetwork(T projectCase) { Objects.requireNonNull(projectCase); LOGGER.info("getNetwork(fileSystemName={}, nodeId={})", projectCase.getFileSystem().getName(), - projectCase.getId()); + projectCase.getId()); - Client client = ClientUtils.createClient(); - try { + try (Client client = ClientUtils.createClient()) { WebTarget webTarget = createWebTarget(client, getConfig().getRestUri()); - Response response = webTarget.path(NODE_PATH) - .resolveTemplate(FILE_SYSTEM_NAME, projectCase.getFileSystem().getName()) - .resolveTemplate(NODE_ID, projectCase.getId()) - .request(MediaType.APPLICATION_XML) - .header(HttpHeaders.AUTHORIZATION, token) - .get(); - try (InputStream is = readEntityIfOk(response, InputStream.class)) { + try (Response response = webTarget.path(NODE_PATH) + .resolveTemplate(FILE_SYSTEM_NAME, projectCase.getFileSystem().getName()) + .resolveTemplate(NODE_ID, projectCase.getId()) + .request(MediaType.APPLICATION_XML) + .header(HttpHeaders.AUTHORIZATION, token) + .get(); InputStream is = readEntityIfOk(response, InputStream.class)) { return NetworkSerDe.read(is); } catch (IOException e) { throw new UncheckedIOException(e); - } finally { - response.close(); } - } finally { - client.close(); } } @@ -111,53 +118,46 @@ public String queryNetwork(T projectCase, Objects.requireNonNull(scriptContent); LOGGER.info("queryNetwork(fileSystemName={}, nodeId={}, scriptType={}, scriptContent=...)", - projectCase.getFileSystem().getName(), projectCase.getId(), scriptType); + projectCase.getFileSystem().getName(), projectCase.getId(), scriptType); - Client client = ClientUtils.createClient(); - try { + try (Client client = ClientUtils.createClient()) { WebTarget webTarget = createWebTarget(client, getConfig().getRestUri()); - Response response = webTarget.path(NODE_PATH) - .resolveTemplate(FILE_SYSTEM_NAME, projectCase.getFileSystem().getName()) - .resolveTemplate(NODE_ID, projectCase.getId()) - .queryParam("scriptType", scriptType.name()) - .request(MediaType.TEXT_PLAIN) - .header(HttpHeaders.AUTHORIZATION, token) - .post(Entity.text(scriptContent)); - try { + try (Response response = webTarget.path(NODE_PATH) + .resolveTemplate(FILE_SYSTEM_NAME, projectCase.getFileSystem().getName()) + .resolveTemplate(NODE_ID, projectCase.getId()) + .queryParam("scriptType", scriptType.name()) + .request(MediaType.TEXT_PLAIN) + .header(HttpHeaders.AUTHORIZATION, token) + .post(Entity.text(scriptContent))) { return readEntityIfOk(response, String.class); - } finally { - response.close(); } - } finally { - client.close(); } } + @Override + public String queryNetwork(T projectCase, ScriptType scriptType, String scriptContent, Iterable extensions, Map, Object> contextObjects) { + return queryNetwork(projectCase, scriptType, scriptContent); + } + @Override public void invalidateCache(T projectCase) { Objects.requireNonNull(projectCase); LOGGER.info("invalidateCache(fileSystemName={}, nodeId={})", - projectCase.getFileSystem().getName(), projectCase.getId()); + projectCase.getFileSystem().getName(), projectCase.getId()); - Client client = ClientUtils.createClient(); - try { + try (Client client = ClientUtils.createClient()) { WebTarget webTarget = createWebTarget(client, getConfig().getRestUri()); - Response response = webTarget.path(NODE_PATH) - .resolveTemplate(FILE_SYSTEM_NAME, projectCase.getFileSystem().getName()) - .resolveTemplate(NODE_ID, projectCase.getId()) - .request() - .header(HttpHeaders.AUTHORIZATION, token) - .delete(); - try { + try (Response response = webTarget.path(NODE_PATH) + .resolveTemplate(FILE_SYSTEM_NAME, projectCase.getFileSystem().getName()) + .resolveTemplate(NODE_ID, projectCase.getId()) + .request() + .header(HttpHeaders.AUTHORIZATION, token) + .delete()) { checkOk(response); - } finally { - response.close(); } - } finally { - client.close(); } } diff --git a/afs-scripting/src/main/groovy/com/powsybl/afs/scripting/AfsGroovyScriptExtension.groovy b/afs-scripting/src/main/groovy/com/powsybl/afs/scripting/AfsGroovyScriptExtension.groovy index 77373d5a..f56fe505 100644 --- a/afs-scripting/src/main/groovy/com/powsybl/afs/scripting/AfsGroovyScriptExtension.groovy +++ b/afs-scripting/src/main/groovy/com/powsybl/afs/scripting/AfsGroovyScriptExtension.groovy @@ -15,7 +15,6 @@ import com.powsybl.afs.ProjectFileExtension import com.powsybl.afs.ServiceExtension import com.powsybl.afs.SoutTaskListener import com.powsybl.commons.util.ServiceLoaderCache -import com.powsybl.computation.ComputationManager import com.powsybl.computation.DefaultComputationManagerConfig import com.powsybl.scripting.groovy.GroovyScriptExtension @@ -25,7 +24,7 @@ import com.powsybl.scripting.groovy.GroovyScriptExtension @AutoService(GroovyScriptExtension.class) class AfsGroovyScriptExtension implements GroovyScriptExtension { - private final AppData data; + private final AppData data AfsGroovyScriptExtension() { this(new ServiceLoaderCache<>(AppFileSystemProvider.class).getServices(), @@ -38,28 +37,28 @@ class AfsGroovyScriptExtension implements GroovyScriptExtension { AfsGroovyScriptExtension(List fileSystemProviders, List fileExtensions, List projectFileExtensions, List serviceExtensions, DefaultComputationManagerConfig config) { - assert fileSystemProviders - assert fileExtensions - assert projectFileExtensions - assert serviceExtensions - assert config + Objects.requireNonNull(fileSystemProviders) + Objects.requireNonNull(fileExtensions) + Objects.requireNonNull(projectFileExtensions) + Objects.requireNonNull(serviceExtensions) + Objects.requireNonNull(config) data = new AppData(config.createShortTimeExecutionComputationManager(), config.createLongTimeExecutionComputationManager(), fileSystemProviders, fileExtensions, projectFileExtensions, - serviceExtensions); + serviceExtensions) } @Override - void load(Binding binding, ComputationManager computationManager) { - binding.afs = new AfsGroovyFacade(data); + void load(Binding binding, Map, Object> contextObjects) { + binding.afs = new AfsGroovyFacade(data) if (binding.hasProperty("out")) { - SoutTaskListener listener = new SoutTaskListener(binding.out); + SoutTaskListener listener = new SoutTaskListener(binding.out) for (AppFileSystem fileSystem : data.getFileSystems()) { - fileSystem.getTaskMonitor().addListener(listener); + fileSystem.getTaskMonitor().addListener(listener) } } } @@ -67,7 +66,7 @@ class AfsGroovyScriptExtension implements GroovyScriptExtension { @Override void unload() { if (data != null) { - data.close(); + data.close() } } } diff --git a/afs-scripting/src/test/java/com/powsybl/afs/scripting/AbstractGroovyScriptTest.java b/afs-scripting/src/test/java/com/powsybl/afs/scripting/AbstractGroovyScriptTest.java index e7dfd3dd..36cde9c3 100644 --- a/afs-scripting/src/test/java/com/powsybl/afs/scripting/AbstractGroovyScriptTest.java +++ b/afs-scripting/src/test/java/com/powsybl/afs/scripting/AbstractGroovyScriptTest.java @@ -25,6 +25,7 @@ import java.io.Reader; import java.util.Collections; import java.util.List; +import java.util.Map; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; @@ -35,18 +36,6 @@ */ public abstract class AbstractGroovyScriptTest { - private final class AfsGroovyScriptExtensionMock implements GroovyScriptExtension { - - @Override - public void load(Binding binding, ComputationManager computationManager) { - binding.setProperty("afs", new AfsGroovyFacade(data)); - } - - @Override - public void unload() { - } - } - protected AppData data; protected abstract Reader getCodeReader(); @@ -60,10 +49,10 @@ protected List getExtensions() { protected AppStorage createStorage() { AppStorage storage = Mockito.mock(AppStorage.class); Mockito.when(storage.createRootNodeIfNotExists(Mockito.anyString(), Mockito.anyString())) - .thenAnswer(invocationOnMock -> new NodeInfo("id", - (String) invocationOnMock.getArguments()[0], - (String) invocationOnMock.getArguments()[1], - "", 0L, 0L, 0, new NodeGenericMetadata())); + .thenAnswer(invocationOnMock -> new NodeInfo("id", + (String) invocationOnMock.getArguments()[0], + (String) invocationOnMock.getArguments()[1], + "", 0L, 0L, 0, new NodeGenericMetadata())); return storage; } @@ -83,8 +72,8 @@ protected List getServiceExtensions() { public void setUp() { ComputationManager computationManager = Mockito.mock(ComputationManager.class); data = new AppData(computationManager, computationManager, - singletonList(cm -> singletonList(new AppFileSystem("mem", false, createStorage()))), - getFileExtensions(), getProjectFileExtensions(), getServiceExtensions()); + singletonList(cm -> singletonList(new AppFileSystem("mem", false, createStorage()))), + getFileExtensions(), getProjectFileExtensions(), getServiceExtensions()); } @AfterEach @@ -100,4 +89,16 @@ void test() throws IOException { } assertEquals(getExpectedOutput(), out.toString()); } + + private final class AfsGroovyScriptExtensionMock implements GroovyScriptExtension { + + @Override + public void load(Binding binding, Map, Object> contextObjects) { + binding.setProperty("afs", new AfsGroovyFacade(data)); + } + + @Override + public void unload() { + } + } } From c32a334a63eb2775a16b791f4438b06394fa9d46 Mon Sep 17 00:00:00 2001 From: Nicolas Rol Date: Fri, 28 Feb 2025 16:34:22 +0100 Subject: [PATCH 2/7] implement new methods in ActionScript Signed-off-by: Nicolas Rol --- .../java/com/powsybl/afs/action/dsl/ActionScript.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/afs-action-dsl/src/main/java/com/powsybl/afs/action/dsl/ActionScript.java b/afs-action-dsl/src/main/java/com/powsybl/afs/action/dsl/ActionScript.java index e427129c..b3701d51 100644 --- a/afs-action-dsl/src/main/java/com/powsybl/afs/action/dsl/ActionScript.java +++ b/afs-action-dsl/src/main/java/com/powsybl/afs/action/dsl/ActionScript.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.ResourceBundle; @@ -51,8 +52,18 @@ public ActionDb load(Network network) { return new ActionDslLoader(readScript(true)).load(network); } + public ActionDb load(Network network, Map, Object> contextObjects) { + Objects.requireNonNull(network); + return new ActionDslLoader(readScript(true)).load(network, contextObjects); + } + @Override public List getContingencies(Network network) { return new ArrayList<>(load(network).getContingencies()); } + + @Override + public List getContingencies(Network network, Map, Object> contextObjects) { + return new ArrayList<>(load(network, contextObjects).getContingencies()); + } } From 57e805b59319fe2afb426a1edf369a04510a785c Mon Sep 17 00:00:00 2001 From: Nicolas Rol Date: Tue, 25 Mar 2025 16:50:49 +0100 Subject: [PATCH 3/7] update powsybl-core Signed-off-by: Nicolas Rol --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 38a5962e..055c3785 100644 --- a/pom.xml +++ b/pom.xml @@ -102,7 +102,7 @@ 1.20.1 2.1.5 - 6.6.0 + 6.7.0 From 10f27ac591e6683167722d29ac4fcc6af7cfaf57 Mon Sep 17 00:00:00 2001 From: Nicolas Rol Date: Tue, 25 Mar 2025 16:55:40 +0100 Subject: [PATCH 4/7] fix merge Signed-off-by: Nicolas Rol --- .../ext/base/LocalNetworkCacheServiceTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/afs-ext-base/src/test/java/com/powsybl/afs/ext/base/LocalNetworkCacheServiceTest.java b/afs-ext-base/src/test/java/com/powsybl/afs/ext/base/LocalNetworkCacheServiceTest.java index 0bd5cf59..9337f4ca 100644 --- a/afs-ext-base/src/test/java/com/powsybl/afs/ext/base/LocalNetworkCacheServiceTest.java +++ b/afs-ext-base/src/test/java/com/powsybl/afs/ext/base/LocalNetworkCacheServiceTest.java @@ -12,10 +12,12 @@ import com.powsybl.afs.ProjectFileCreationContext; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.NetworkListener; +import com.powsybl.scripting.groovy.GroovyScriptExtension; import org.junit.jupiter.api.Test; import java.util.Collections; import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -45,16 +47,31 @@ public String queryNetwork(ScriptType scriptType, String scriptContent) { return ""; } + @Override + public String queryNetwork(ScriptType scriptType, String scriptContent, Iterable extensions, Map, Object> contextObjects) { + return ""; + } + @Override public Network getNetwork() { return null; } + @Override + public Network getNetwork(Iterable extensions, Map, Object> contextObjects) { + return null; + } + @Override public Network getNetwork(List listeners) { return null; } + @Override + public Network getNetwork(Iterable extensions, Map, Object> contextObjects, List listeners) { + return null; + } + @Override public void invalidateNetworkCache() { // Nothing here From 52a34931831deedea03a0aa4f98e98a09488eedb Mon Sep 17 00:00:00 2001 From: Nicolas Rol Date: Tue, 25 Mar 2025 17:05:47 +0100 Subject: [PATCH 5/7] coverage on ActionScriptTest Signed-off-by: Nicolas Rol --- .../java/com/powsybl/afs/action/dsl/ActionScriptTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/afs-action-dsl/src/test/java/com/powsybl/afs/action/dsl/ActionScriptTest.java b/afs-action-dsl/src/test/java/com/powsybl/afs/action/dsl/ActionScriptTest.java index 163b1c91..e94a709f 100644 --- a/afs-action-dsl/src/test/java/com/powsybl/afs/action/dsl/ActionScriptTest.java +++ b/afs-action-dsl/src/test/java/com/powsybl/afs/action/dsl/ActionScriptTest.java @@ -22,8 +22,10 @@ import org.mockito.Mockito; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -60,7 +62,9 @@ void test() { Network network = Mockito.mock(Network.class); Mockito.when((Line) network.getIdentifiable("l1")).thenReturn(Mockito.mock(Line.class)); - Assertions.assertThat(contingencies).hasSameElementsAs(actionScript.getContingencies(network)); + assertThat(contingencies) + .hasSameElementsAs(actionScript.getContingencies(network)) + .hasSameElementsAs(actionScript.getContingencies(network, new HashMap<>())); } From 492d293d40eacb2a2985ceba49e2044128c035b7 Mon Sep 17 00:00:00 2001 From: Nicolas Rol Date: Tue, 25 Mar 2025 17:28:39 +0100 Subject: [PATCH 6/7] remove import Signed-off-by: Nicolas Rol --- .../test/java/com/powsybl/afs/action/dsl/ActionScriptTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/afs-action-dsl/src/test/java/com/powsybl/afs/action/dsl/ActionScriptTest.java b/afs-action-dsl/src/test/java/com/powsybl/afs/action/dsl/ActionScriptTest.java index e94a709f..4c2ed6e5 100644 --- a/afs-action-dsl/src/test/java/com/powsybl/afs/action/dsl/ActionScriptTest.java +++ b/afs-action-dsl/src/test/java/com/powsybl/afs/action/dsl/ActionScriptTest.java @@ -17,7 +17,6 @@ import com.powsybl.contingency.LineContingency; import com.powsybl.iidm.network.Line; import com.powsybl.iidm.network.Network; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; From cbf7dde6554f925f62355bec5fb6c8404e1914f2 Mon Sep 17 00:00:00 2001 From: Nicolas Rol Date: Wed, 26 Mar 2025 10:53:42 +0100 Subject: [PATCH 7/7] add tests on RemoteNetworkCacheService Signed-off-by: Nicolas Rol --- afs-network/afs-network-client/pom.xml | 20 ++ .../client/RemoteNetworkCacheService.java | 2 +- .../client/RemoteNetworkCacheServiceTest.java | 197 ++++++++++++++++++ 3 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 afs-network/afs-network-client/src/test/java/com/powsybl/afs/network/client/RemoteNetworkCacheServiceTest.java diff --git a/afs-network/afs-network-client/pom.xml b/afs-network/afs-network-client/pom.xml index 47aa05d3..4c1d85ec 100644 --- a/afs-network/afs-network-client/pom.xml +++ b/afs-network/afs-network-client/pom.xml @@ -68,11 +68,31 @@ + + ${project.groupId} + powsybl-iidm-impl + test + + + ${project.groupId} + powsybl-iidm-test + test + + + com.google.jimfs + jimfs + test + org.junit.jupiter junit-jupiter-engine test + + org.mockito + mockito-junit-jupiter + test + diff --git a/afs-network/afs-network-client/src/main/java/com/powsybl/afs/network/client/RemoteNetworkCacheService.java b/afs-network/afs-network-client/src/main/java/com/powsybl/afs/network/client/RemoteNetworkCacheService.java index a92f5242..fdcdd46c 100644 --- a/afs-network/afs-network-client/src/main/java/com/powsybl/afs/network/client/RemoteNetworkCacheService.java +++ b/afs-network/afs-network-client/src/main/java/com/powsybl/afs/network/client/RemoteNetworkCacheService.java @@ -6,7 +6,6 @@ */ package com.powsybl.afs.network.client; -import com.google.common.base.Supplier; import com.powsybl.afs.AfsException; import com.powsybl.afs.ProjectFile; import com.powsybl.afs.ext.base.NetworkCacheService; @@ -37,6 +36,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.function.Supplier; import static com.powsybl.afs.ws.client.utils.ClientUtils.checkOk; import static com.powsybl.afs.ws.client.utils.ClientUtils.readEntityIfOk; diff --git a/afs-network/afs-network-client/src/test/java/com/powsybl/afs/network/client/RemoteNetworkCacheServiceTest.java b/afs-network/afs-network-client/src/test/java/com/powsybl/afs/network/client/RemoteNetworkCacheServiceTest.java new file mode 100644 index 00000000..b27eb251 --- /dev/null +++ b/afs-network/afs-network-client/src/test/java/com/powsybl/afs/network/client/RemoteNetworkCacheServiceTest.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2025, RTE (https://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.afs.network.client; + +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import com.powsybl.afs.AfsException; +import com.powsybl.afs.AppFileSystem; +import com.powsybl.afs.ext.base.AbstractProjectCase; +import com.powsybl.afs.ext.base.ScriptType; +import com.powsybl.afs.ws.client.utils.ClientUtils; +import com.powsybl.afs.ws.client.utils.RemoteServiceConfig; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.Substation; +import com.powsybl.iidm.network.test.NetworkTest1Factory; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.Invocation; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Optional; +import java.util.Properties; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +/** + * @author Nicolas Rol {@literal } + */ +@ExtendWith(MockitoExtension.class) +class RemoteNetworkCacheServiceTest { + + @Mock + private Supplier> configSupplier; + + @Mock + private AbstractProjectCase projectCase; + + @Mock + private RemoteServiceConfig remoteServiceConfig; + + @Mock + private Client client; + + @Mock + private WebTarget webTarget; + + @Mock + private AppFileSystem appFileSystem; + + @Mock + Invocation.Builder builder; + + private FileSystem fileSystem; + private RemoteNetworkCacheService networkCacheService; + private Network network; + private Path networkPath; + + @BeforeEach + void setUp() throws IOException { + network = NetworkTest1Factory.create(); + fileSystem = Jimfs.newFileSystem(Configuration.unix()); + Path testDir = fileSystem.getPath("/tmp"); + Files.createDirectories(testDir); + networkPath = testDir.resolve("network.xiidm"); + network.write("XIIDM", new Properties(), networkPath); + networkCacheService = new RemoteNetworkCacheService(configSupplier, "test-token"); + } + + @AfterEach + void tearDown() throws IOException { + fileSystem.close(); + } + + @Test + void getNetworkShouldThrowWhenConfigMissing() { + when(configSupplier.get()).thenReturn(Optional.empty()); + when(projectCase.getFileSystem()).thenReturn(appFileSystem); + when(appFileSystem.getName()).thenReturn("test-app"); + + AfsException exception = assertThrows(AfsException.class, () -> networkCacheService.getNetwork(projectCase)); + assertEquals("Remote service config is missing", exception.getMessage()); + } + + @Test + void getNetworkTest() throws IOException { + // Configuration + when(configSupplier.get()).thenReturn(Optional.of(remoteServiceConfig)); + when(remoteServiceConfig.getRestUri()).thenReturn(URI.create("http://localhost:8080")); + when(projectCase.getFileSystem()).thenReturn(appFileSystem); + when(appFileSystem.getName()).thenReturn("test-app"); + when(projectCase.getId()).thenReturn("node-123"); + + // Call the tested method + Network result; + try (MockedStatic mockedClientUtils = mockStatic(ClientUtils.class, Mockito.CALLS_REAL_METHODS); + InputStream inputStream = Files.newInputStream(networkPath); + Response response = Response.ok(inputStream).build()) { + mockedClientUtils.when(ClientUtils::createClient).thenReturn(client); + when(client.target(any(URI.class))).thenReturn(webTarget); + when(webTarget.path(anyString())).thenReturn(webTarget); + when(webTarget.resolveTemplate(anyString(), any())).thenReturn(webTarget); + when(webTarget.request(MediaType.APPLICATION_XML)).thenReturn(builder); + when(builder.header(anyString(), anyString())).thenReturn(builder); + when(builder.get()).thenReturn(response); + + result = networkCacheService.getNetwork(projectCase); + } + + // Checks + assertNotNull(result); + assertEquals(network.getId(), result.getId()); + network.getVoltageLevels().forEach(voltageLevel -> assertNotNull(result.getVoltageLevel(voltageLevel.getId()))); + } + + @Test + void queryNetworkTest() { + // Configuration + when(configSupplier.get()).thenReturn(Optional.of(remoteServiceConfig)); + when(remoteServiceConfig.getRestUri()).thenReturn(URI.create("http://localhost:8080")); + when(projectCase.getFileSystem()).thenReturn(appFileSystem); + when(appFileSystem.getName()).thenReturn("test-app"); + when(projectCase.getId()).thenReturn("node-123"); + + // Call the tested method + String result; + try (MockedStatic mockedClientUtils = mockStatic(ClientUtils.class, Mockito.CALLS_REAL_METHODS); + Response response = Response.ok(network.getSubstationStream().map(Substation::getId).toList().toString()).build()) { + mockedClientUtils.when(ClientUtils::createClient).thenReturn(client); + when(client.target(any(URI.class))).thenReturn(webTarget); + when(webTarget.path(anyString())).thenReturn(webTarget); + when(webTarget.resolveTemplate(anyString(), any())).thenReturn(webTarget); + when(webTarget.queryParam(anyString(), any())).thenReturn(webTarget); + when(webTarget.request(MediaType.TEXT_PLAIN)).thenReturn(builder); + when(builder.header(anyString(), anyString())).thenReturn(builder); + when(builder.post(any())).thenReturn(response); + + result = networkCacheService.queryNetwork(projectCase, ScriptType.GROOVY, "network.substations.collect { it.id }", Collections.emptyList(), Collections.emptyMap()); + } + + // Checks + assertNotNull(result); + assertEquals("[substation1]", result); + } + + @Test + void invalidateCacheTest() { + // Configuration + when(configSupplier.get()).thenReturn(Optional.of(remoteServiceConfig)); + when(remoteServiceConfig.getRestUri()).thenReturn(URI.create("http://localhost:8080")); + when(projectCase.getFileSystem()).thenReturn(appFileSystem); + when(appFileSystem.getName()).thenReturn("test-app"); + when(projectCase.getId()).thenReturn("node-123"); + + // Call the tested method + try (MockedStatic mockedClientUtils = mockStatic(ClientUtils.class, Mockito.CALLS_REAL_METHODS); + Response response = Response.ok().build()) { + mockedClientUtils.when(ClientUtils::createClient).thenReturn(client); + when(client.target(any(URI.class))).thenReturn(webTarget); + when(webTarget.path(anyString())).thenReturn(webTarget); + when(webTarget.resolveTemplate(anyString(), any())).thenReturn(webTarget); + when(webTarget.request()).thenReturn(builder); + when(builder.header(anyString(), anyString())).thenReturn(builder); + when(builder.delete()).thenReturn(response); + + assertDoesNotThrow(() -> networkCacheService.invalidateCache(projectCase)); + } + } +}