Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow alternative serde for extensions #3122

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2024, RTE (http://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.commons.extensions;

/**
* @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
*/
public abstract class AbstractAlternativeExtensionSerDe<T extends Extendable, E extends Extension<T>>
extends AbstractExtensionSerDe<T, E>
implements AlternativeExtensionSerDe<T, E> {

protected AbstractAlternativeExtensionSerDe(String extensionName, String categoryName, Class<? super E> extensionClass,
String xsdFileName, String namespaceUri, String namespacePrefix) {
super(extensionName, categoryName, extensionClass, xsdFileName, namespaceUri, namespacePrefix);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2024, RTE (http://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.commons.extensions;

/**
* @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
*/
public interface AlternativeExtensionSerDe<T extends Extendable, E extends Extension<T>> extends ExtensionProviderAlternative<T, E>, ExtensionSerDe<T, E> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2024, RTE (http://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.commons.extensions;

/**
* @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
*/
public interface ExtensionProviderAlternative<T extends Extendable, E extends Extension<T>> extends ExtensionSerDe<T, E> {
String getOriginalExtensionName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* @author Mathieu Bague {@literal <mathieu.bague at rte-france.com>}
*/
public final class ExtensionProviders<T extends ExtensionProvider> {

private final Map<String, T> providers;
private final Map<String, T> alternativeProviders;

public static <T extends ExtensionProvider> ExtensionProviders<T> createProvider(Class<T> clazz) {
return new ExtensionProviders<>(clazz);
Expand All @@ -33,9 +35,7 @@ public static <T extends ExtensionProvider> ExtensionProviders<T> createProvider
}

private ExtensionProviders(Class<T> clazz) {
Objects.requireNonNull(clazz);
providers = new ServiceLoaderCache<>(clazz).getServices().stream()
.collect(Collectors.toMap(T::getExtensionName, e -> e));
this(clazz, null, null);
}

private ExtensionProviders(Class<T> clazz, String categoryName) {
Expand All @@ -44,29 +44,65 @@ private ExtensionProviders(Class<T> clazz, String categoryName) {

private ExtensionProviders(Class<T> clazz, String categoryName, Set<String> extensionNames) {
Objects.requireNonNull(clazz);
Objects.requireNonNull(categoryName);

List<T> services = new ServiceLoaderCache<>(clazz).getServices();
providers = services.stream()
.filter(s -> s.getCategoryName().equals(categoryName) && (extensionNames == null || extensionNames.contains(s.getExtensionName())))
.collect(Collectors.toMap(T::getExtensionName, e -> e));
Stream<T> providersStream = new ServiceLoaderCache<>(clazz).getServices().stream();
if (categoryName != null) {
providersStream = providersStream.filter(s -> s.getCategoryName().equals(categoryName) &&
(extensionNames == null || extensionNames.contains(s.getExtensionName())));
}
providers = providersStream.collect(Collectors.toMap(T::getExtensionName, e -> e));

Class<? extends ExtensionProviderAlternative> alternativeClass = getExtensionProviderAlternativeClass(clazz);
if (alternativeClass != null) {
Stream<? extends ExtensionProviderAlternative> providerAlternativeStream = new ServiceLoaderCache<>(alternativeClass).getServices().stream();
if (categoryName != null) {
providerAlternativeStream = providerAlternativeStream.filter(s -> s.getCategoryName().equals(categoryName) &&
(extensionNames == null || extensionNames.contains(s.getOriginalExtensionName())));
}
alternativeProviders = providerAlternativeStream
.collect(Collectors.toMap(ExtensionProviderAlternative::getOriginalExtensionName, e -> (T) e));
} else {
alternativeProviders = Collections.emptyMap();
}
}

private Class<? extends ExtensionProviderAlternative> getExtensionProviderAlternativeClass(Class<T> clazz) {
if (clazz.equals(ExtensionSerDe.class)) {
return AlternativeExtensionSerDe.class;
}
return null;
}

public T findProvider(String name) {
return findProvider(name, null);
}

public T findProvider(String name, ExtensionProvidersOptions options) {
if (options != null && options.useAlternativeVersion(name)) {
T alternative = alternativeProviders.get(name);
if (alternative != null) {
return alternative;
}
}
return providers.get(name);
}

public T findProviderOrThrowException(String name) {
T serializer = findProvider(name);
if (serializer == null) {
return findProviderOrThrowException(name, null);
}

public T findProviderOrThrowException(String name, ExtensionProvidersOptions options) {
T provider = findProvider(name, options);
if (provider == null) {
throw new PowsyblException("Provider not found for extension " + name);
}

return serializer;
return provider;
}

public Collection<T> getProviders() {
return providers.values();
return providers.keySet().stream()
.map(this::findProvider)
.toList();
}

public <T> void addExtensions(Extendable<T> extendable, Collection<Extension<T>> extensions) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2024, RTE (http://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.commons.extensions;

/**
* @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
*/
public interface ExtensionProvidersOptions {

boolean useAlternativeVersion(String extensionName);

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import com.google.common.collect.Sets;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.extensions.ExtensionProvidersOptions;
import com.powsybl.iidm.network.TopologyLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -23,7 +24,7 @@
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
* @author Miora Ralambotiana {@literal <miora.ralambotiana at rte-france.com>}
*/
public class ExportOptions extends AbstractOptions<ExportOptions> {
public class ExportOptions extends AbstractOptions<ExportOptions> implements ExtensionProvidersOptions {

public enum IidmVersionIncompatibilityBehavior {
THROW_EXCEPTION,
Expand All @@ -32,6 +33,8 @@ public enum IidmVersionIncompatibilityBehavior {

private static final Logger LOGGER = LoggerFactory.getLogger(ExportOptions.class);

public static final String ALTERNATIVE_VERSION = "alternative";

private boolean withBranchSV = true;

private boolean indent = true;
Expand Down Expand Up @@ -225,7 +228,8 @@ public ExportOptions addExtensionVersion(String extensionName, String extensionV
* If it has never been added, return an empty optional.
*/
public Optional<String> getExtensionVersion(String extensionName) {
return Optional.ofNullable(extensionsVersions.get(extensionName));
return Optional.ofNullable(extensionsVersions.get(extensionName))
.map(v -> ALTERNATIVE_VERSION.equals(v) ? null : v);
}

public boolean isSorted() {
Expand All @@ -245,4 +249,10 @@ public ExportOptions setWithAutomationSystems(boolean withAutomationSystems) {
this.withAutomationSystems = withAutomationSystems;
return this;
}

@Override
public boolean useAlternativeVersion(String extensionName) {
String v = extensionsVersions.get(extensionName);
return ALTERNATIVE_VERSION.equals(v);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ private static void writeExtension(Extension<? extends Identifiable<?>> extensio
throw new IllegalStateException("Extension Serializer of " + extension.getName() + " should not be null");
}
String namespaceUri = getNamespaceUri(extensionSerDe, context.getOptions());
writer.writeStartNode(namespaceUri, extension.getName());
writer.writeStartNode(namespaceUri, extensionSerDe.getExtensionName());
context.getExtensionVersion(extension.getName()).ifPresent(extensionSerDe::checkExtensionVersionSupported);
extensionSerDe.write(extension, context);
writer.writeEndNode();
Expand All @@ -167,8 +167,8 @@ private static void writeExtension(Extension<? extends Identifiable<?>> extensio
private static ExtensionSerDe getExtensionSerializer(ExportOptions options, Extension<? extends Identifiable<?>> extension) {
if (options.withExtension(extension.getName())) {
ExtensionSerDe extensionSerDe = options.isThrowExceptionIfExtensionNotFound()
? EXTENSIONS_SUPPLIER.get().findProviderOrThrowException(extension.getName())
: EXTENSIONS_SUPPLIER.get().findProvider(extension.getName());
? EXTENSIONS_SUPPLIER.get().findProviderOrThrowException(extension.getName(), options)
: EXTENSIONS_SUPPLIER.get().findProvider(extension.getName(), options);
if (extensionSerDe == null) {
String message = "XmlSerializer for " + extension.getName() + " not found";
throwExceptionIfOption(options, message);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2024, RTE (http://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.iidm.serde.extensions;

import com.google.common.collect.ImmutableSortedSet;
import com.powsybl.commons.extensions.AlternativeExtensionSerDe;
import com.powsybl.commons.extensions.Extendable;
import com.powsybl.commons.extensions.Extension;
import com.powsybl.iidm.serde.IidmVersion;

import java.util.Map;

/**
* @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
*/
public abstract class AbstractAlternativeVersionableNetworkExtensionSerDe<T extends Extendable, E extends Extension<T>>
extends AbstractVersionableNetworkExtensionSerDe<T, E>
implements AlternativeExtensionSerDe<T, E> {

protected AbstractAlternativeVersionableNetworkExtensionSerDe(String extensionName, Class<? super E> extensionClass, String namespacePrefix,
Map<IidmVersion, ImmutableSortedSet<String>> extensionVersions, Map<String, String> namespaceUris) {
super(extensionName, extensionClass, namespacePrefix, extensionVersions, namespaceUris);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* Copyright (c) 2024, RTE (http://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.iidm.serde.extensions;

import com.powsybl.iidm.network.Battery;
import com.powsybl.iidm.network.Generator;
import com.powsybl.iidm.network.Line;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.extensions.ActivePowerControl;
import com.powsybl.iidm.network.extensions.OperatingStatus;
import com.powsybl.iidm.network.impl.extensions.ActivePowerControlImpl;
import com.powsybl.iidm.network.impl.extensions.OperatingStatusImpl;
import com.powsybl.iidm.network.test.BatteryNetworkFactory;
import com.powsybl.iidm.serde.AbstractIidmSerDeTest;
import com.powsybl.iidm.serde.ExportOptions;
import com.powsybl.iidm.serde.IidmVersion;
import org.junit.jupiter.api.Test;

import java.io.IOException;

import static org.junit.jupiter.api.Assertions.*;

/**
* @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
*/
class AlternativeExtensionXmlTest extends AbstractIidmSerDeTest {

// This test is the same as OperatingStatusXmlTest::test, but the extension is exported/imported
// using the defined alternative (which is not versioned).
@Test
void nonVersionedAlternativeTest() throws IOException {
Network network = OperatingStatusXmlTest.createTestNetwork();

// extend line
Line line = network.getLine("L");
assertNotNull(line);
OperatingStatus<Line> lineOperatingStatus = new OperatingStatusImpl<>(line,
OperatingStatus.Status.PLANNED_OUTAGE);
line.addExtension(OperatingStatus.class, lineOperatingStatus);

var exportOptions = new ExportOptions().addExtensionVersion(OperatingStatus.NAME, "alternative");
Network network2 = allFormatsRoundTripTest(network, "/alternativeOperatingStatusRef.xml", exportOptions);

Line line2 = network2.getLine("L");
assertNotNull(line2);
OperatingStatus<Line> lineOperatingStatus2 = line2.getExtension(OperatingStatus.class);
assertNotNull(lineOperatingStatus2);
assertEquals(lineOperatingStatus.getStatus(), lineOperatingStatus2.getStatus());

lineOperatingStatus2.setStatus(OperatingStatus.Status.IN_OPERATION);
assertEquals(OperatingStatus.Status.IN_OPERATION, lineOperatingStatus2.getStatus());
}

@Test
void versionedAlternativeWithDefaultVersionTest() throws IOException {
Network network = BatteryNetworkFactory.create();

Battery bat = network.getBattery("BAT");
bat.addExtension(ActivePowerControl.class, new ActivePowerControlImpl<>(bat, true, 4.0, 1.2));

Generator generator = network.getGenerator("GEN");
generator.addExtension(ActivePowerControl.class, new ActivePowerControlImpl<>(generator, false, 3.0, 1.0));

// No version is specified for the alternative: it should use the default version (v1.1)
var exportOptions = new ExportOptions().addExtensionVersion(ActivePowerControl.NAME, ExportOptions.ALTERNATIVE_VERSION);
Network network2 = allFormatsRoundTripTest(network, "/alternativeActivePowerControlV1_1.xml", IidmVersion.V_1_0, exportOptions);

Battery bat2 = network2.getBattery("BAT");
assertNotNull(bat2);
ActivePowerControl<Battery> activePowerControl2 = bat2.getExtension(ActivePowerControl.class);
assertNotNull(activePowerControl2);
assertEquals(1.2, activePowerControl2.getParticipationFactor(), 0.001d); // default version supports participationFactor
}

@Test
void versionedAlternativeWithSpecificVersionTest() throws IOException {
Network network = BatteryNetworkFactory.create();

Battery bat = network.getBattery("BAT");
bat.addExtension(ActivePowerControl.class, new ActivePowerControlImpl<>(bat, true, 4.0, 1.2));

Generator generator = network.getGenerator("GEN");
generator.addExtension(ActivePowerControl.class, new ActivePowerControlImpl<>(generator, false, 3.0, 1.0));

// Explicitly ask for version 1.0 of the alternative
var exportOptions = new ExportOptions()
.addExtensionVersion("legacyActivePowerControl", "1.0")
.addExtensionVersion(ActivePowerControl.NAME, ExportOptions.ALTERNATIVE_VERSION);
Network network2 = allFormatsRoundTripTest(network, "/alternativeActivePowerControlV1_0.xml", IidmVersion.V_1_0, exportOptions);

Battery bat2 = network2.getBattery("BAT");
assertNotNull(bat2);
ActivePowerControl<Battery> activePowerControl2 = bat2.getExtension(ActivePowerControl.class);
assertNotNull(activePowerControl2);
assertTrue(Double.isNaN(activePowerControl2.getParticipationFactor())); // version 1.0 does NOT support participationFactor
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
*/
class OperatingStatusXmlTest extends AbstractIidmSerDeTest {

private static Network createTestNetwork() {
protected static Network createTestNetwork() {
Network network = Network.create("test", "test");
network.setCaseDate(ZonedDateTime.parse("2016-06-27T12:27:58.535+02:00"));
Substation s = network.newSubstation()
.setId("S")
.setCountry(Country.FR)
.add();
VoltageLevel vl = s.newVoltageLevel()
s.newVoltageLevel()
.setId("VL")
.setNominalV(400)
.setTopologyKind(TopologyKind.NODE_BREAKER)
Expand All @@ -40,7 +40,7 @@ private static Network createTestNetwork() {
.setId("S2")
.setCountry(Country.FR)
.add();
VoltageLevel vl2 = s2.newVoltageLevel()
s2.newVoltageLevel()
.setId("VL2")
.setNominalV(400)
.setTopologyKind(TopologyKind.NODE_BREAKER)
Expand Down
Loading