diff --git a/iidm/iidm-serde/src/main/java/com/powsybl/iidm/serde/IidmVersion.java b/iidm/iidm-serde/src/main/java/com/powsybl/iidm/serde/IidmVersion.java index 2ae095c49ab..91ebf553128 100644 --- a/iidm/iidm-serde/src/main/java/com/powsybl/iidm/serde/IidmVersion.java +++ b/iidm/iidm-serde/src/main/java/com/powsybl/iidm/serde/IidmVersion.java @@ -14,8 +14,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.powsybl.iidm.serde.IidmSerDeConstants.ITESLA_DOMAIN; -import static com.powsybl.iidm.serde.IidmSerDeConstants.POWSYBL_DOMAIN; +import static com.powsybl.iidm.serde.IidmSerDeConstants.*; /** * @author Miora Ralambotiana {@literal } @@ -83,6 +82,15 @@ public String getXsd(boolean valid) { return "iidm_equipment_V" + toString("_") + ".xsd"; } + public static IidmVersion of(String version, String separator) { + Objects.requireNonNull(version); + return Stream.of(IidmVersion.values()) + .filter(v -> version.equals(v.toString(separator))) + .findFirst() // there can only be 0 or exactly 1 match + .orElseThrow(() -> new PowsyblException("IIDM Version " + version + " is not supported. Max supported version: " + + CURRENT_IIDM_VERSION.toString(separator))); + } + public static IidmVersion fromNamespaceURI(String namespaceURI) { String version = namespaceURI.substring(namespaceURI.lastIndexOf('/') + 1); IidmVersion v = of(version, "_"); @@ -97,11 +105,4 @@ public static IidmVersion fromNamespaceURI(String namespaceURI) { return v; } - public static IidmVersion of(String version, String separator) { - Objects.requireNonNull(version); - return Stream.of(IidmVersion.values()) - .filter(v -> version.equals(v.toString(separator))) - .findFirst() // there can only be 0 or exactly 1 match - .orElseThrow(() -> new PowsyblException("Version " + version + " is not supported.")); - } } diff --git a/iidm/iidm-serde/src/main/java/com/powsybl/iidm/serde/XMLImporter.java b/iidm/iidm-serde/src/main/java/com/powsybl/iidm/serde/XMLImporter.java index 9d954ba1105..face054753d 100644 --- a/iidm/iidm-serde/src/main/java/com/powsybl/iidm/serde/XMLImporter.java +++ b/iidm/iidm-serde/src/main/java/com/powsybl/iidm/serde/XMLImporter.java @@ -58,32 +58,73 @@ public String getComment() { } protected boolean exists(ReadOnlyDataSource dataSource, String ext) throws IOException { - try { - if (ext != null) { - try (InputStream is = dataSource.newInputStream(null, ext)) { - // check the first root element is network and namespace is IIDM - XMLStreamReader xmlsr = getXMLInputFactory().createXMLStreamReader(is); - try { - while (xmlsr.hasNext()) { - int eventType = xmlsr.next(); - if (eventType == XMLStreamConstants.START_ELEMENT) { - String name = xmlsr.getLocalName(); - String ns = xmlsr.getNamespaceURI(); - return NetworkSerDe.NETWORK_ROOT_ELEMENT_NAME.equals(name) - && (Stream.of(IidmVersion.values()).anyMatch(v -> v.getNamespaceURI().equals(ns)) - || Stream.of(IidmVersion.values()).filter(v -> v.compareTo(IidmVersion.V_1_7) >= 0).anyMatch(v -> v.getNamespaceURI(false).equals(ns))); - } - } - } finally { - cleanClose(xmlsr); + if (ext == null) { + return false; + } + + try (InputStream is = dataSource.newInputStream(null, ext)) { + XMLStreamReader xmlsr = getXMLInputFactory().createXMLStreamReader(is); + try { + return isValidNetworkRoot(xmlsr); + } finally { + cleanClose(xmlsr); + } + } catch (XMLStreamException e) { + return false; // not a valid XML file + } + } + + private boolean isValidNetworkRoot(XMLStreamReader xmlsr) throws XMLStreamException { + while (xmlsr.hasNext()) { + if (xmlsr.next() == XMLStreamConstants.START_ELEMENT) { + String name = xmlsr.getLocalName(); + String ns = xmlsr.getNamespaceURI(); + + if (!NetworkSerDe.NETWORK_ROOT_ELEMENT_NAME.equals(name) || ns.isEmpty()) { + return false; + } + + String currentPrefix = extractPrefix(ns); + if (isValidPrefix(currentPrefix)) { + String version = extractVersion(ns); + if (!version.isEmpty()) { + // If it is not supported, this will throw an exception giving details on the encountered version + // and the maximum supported version. + IidmVersion.of(version, "_"); + return isValidNamespace(ns); } } } - return false; - } catch (XMLStreamException e) { - // not a valid xml file - return false; } + return false; + } + + private String extractPrefix(String namespace) { + int lastSlash = namespace.lastIndexOf('/'); + return (lastSlash > 0) ? namespace.substring(0, lastSlash) : namespace; + } + + private String extractVersion(String namespace) { + int lastSlash = namespace.lastIndexOf('/'); + return (lastSlash >= 0 && lastSlash < namespace.length() - 1) ? namespace.substring(lastSlash + 1) : ""; + } + + private boolean isValidPrefix(String prefix) { + return Stream.of(IidmVersion.values()) + .map(v -> extractPrefix(v.getNamespaceURI(true))) + .anyMatch(prefix::equals) + || Stream.of(IidmVersion.values()) + .filter(v -> v.compareTo(IidmVersion.V_1_7) >= 0) + .map(v -> extractPrefix(v.getNamespaceURI(false))) + .anyMatch(prefix::equals); + } + + private boolean isValidNamespace(String ns) { + return Stream.of(IidmVersion.values()) + .anyMatch(v -> v.getNamespaceURI().equals(ns)) + || Stream.of(IidmVersion.values()) + .filter(v -> v.compareTo(IidmVersion.V_1_7) >= 0) + .anyMatch(v -> v.getNamespaceURI(false).equals(ns)); } private void cleanClose(XMLStreamReader xmlStreamReader) { diff --git a/iidm/iidm-serde/src/test/java/com/powsybl/iidm/serde/XMLImporterTest.java b/iidm/iidm-serde/src/test/java/com/powsybl/iidm/serde/XMLImporterTest.java index 850f42aa4f5..e9e74a31fea 100644 --- a/iidm/iidm-serde/src/test/java/com/powsybl/iidm/serde/XMLImporterTest.java +++ b/iidm/iidm-serde/src/test/java/com/powsybl/iidm/serde/XMLImporterTest.java @@ -8,6 +8,7 @@ package com.powsybl.iidm.serde; import com.google.common.io.ByteStreams; +import com.powsybl.commons.PowsyblException; import com.powsybl.commons.datasource.*; import com.powsybl.commons.report.PowsyblCoreReportResourceBundle; import com.powsybl.commons.test.PowsyblTestReportResourceBundle; @@ -23,6 +24,7 @@ import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.Properties; @@ -90,6 +92,22 @@ private void writeNetworkWithComment(String fileName) throws IOException { } } + private void writeNetworkUnsupportedIidmVersion(String fileName) throws IOException { + writeNetwork(fileName, unsupportedIidmVersionNamespaceURI(), false); + } + + private static String unsupportedIidmVersion() { + String[] currentIidmVersionSplit = CURRENT_IIDM_VERSION.toString().split("_"); + int unsupportedIidmVersionMajor = Integer.parseInt(currentIidmVersionSplit[1]); + int unsupportedIidmVersionMinor = Integer.parseInt(currentIidmVersionSplit[2]) + 1; + return String.format("%d.%d", unsupportedIidmVersionMajor, unsupportedIidmVersionMinor); + } + + private String unsupportedIidmVersionNamespaceURI() { + String currentIidmNamespaceURI = CURRENT_IIDM_VERSION.getNamespaceURI(); + return currentIidmNamespaceURI.substring(0, currentIidmNamespaceURI.lastIndexOf("/") + 1) + unsupportedIidmVersion(); + } + @BeforeEach public void setUp() throws IOException { super.setUp(); @@ -116,6 +134,7 @@ public void setUp() throws IOException { } writeNetworkWithComment("/test7.xiidm"); writeNetworkWithExtension("/test8.xiidm", CURRENT_IIDM_VERSION.getNamespaceURI()); + writeNetworkUnsupportedIidmVersion("/test9.xiidm"); importer = new XMLImporter(); } @@ -154,6 +173,15 @@ void exists() { assertFalse(importer.exists(new DirectoryDataSource(fileSystem.getPath("/"), "testDummy"))); // namespace URI is not defined } + @Test + void testUnsupportedIidmVersion() { + Path dir = fileSystem.getPath("/"); + ReadOnlyDataSource dataSource = new DirectoryDataSource(dir, "test9"); + String expectedError = "IIDM Version " + unsupportedIidmVersion() + "is not supported. Max supported version: " + + CURRENT_IIDM_VERSION.toString("."); + assertThrows(PowsyblException.class, () -> importer.exists(dataSource), expectedError); + } + @Test void copy() throws Exception { importer.copy(new DirectoryDataSource(fileSystem.getPath("/"), "test0"), new DirectoryDataSource(fileSystem.getPath("/"), "test0_copy"));