diff --git a/pom.xml b/pom.xml
index 4cb9740..50b3b7f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -153,6 +153,14 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 9
+
+
diff --git a/src/main/ApiDataModels.kt b/src/main/ApiDataModels.kt
index 910b442..578ae9f 100644
--- a/src/main/ApiDataModels.kt
+++ b/src/main/ApiDataModels.kt
@@ -1,7 +1,5 @@
package com.github.statnett.loadflowservice
-import com.powsybl.commons.reporter.Reporter
-import com.powsybl.commons.reporter.ReporterModel
import com.powsybl.iidm.network.Network
import kotlinx.serialization.Serializable
diff --git a/src/main/ApiUtil.kt b/src/main/ApiUtil.kt
index 7405146..3fd2d9b 100644
--- a/src/main/ApiUtil.kt
+++ b/src/main/ApiUtil.kt
@@ -8,31 +8,17 @@ import com.powsybl.sld.SingleLineDiagram
import com.powsybl.sld.SldParameters
import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.http.content.*
-import java.io.ByteArrayInputStream
import java.io.StringWriter
-import java.security.MessageDigest
private val logger = KotlinLogging.logger {}
fun busesFromRequest(
- type: String,
- body: ByteArray,
+ content: FileContent
): List {
- val network = networkFromStream(type, ByteArrayInputStream(body))
+ val network = networkFromFileContent(content)
return busPropertiesFromNetwork(network)
}
-class FileContent(val name: String, val bytes: ByteArray) {
- fun contentHash(): String {
- val md = MessageDigest.getInstance("MD5")
- return md.digest(this.bytes).joinToString(separator = "") { eachByte -> "%02x".format(eachByte) }
- }
-
- fun contentAsStream(): ByteArrayInputStream {
- return ByteArrayInputStream(this.bytes)
- }
-}
-
/**
* Convenience class used to deserialize and update a load parameter instance
*/
diff --git a/src/main/App.kt b/src/main/App.kt
index 5a069db..7f56770 100644
--- a/src/main/App.kt
+++ b/src/main/App.kt
@@ -46,7 +46,7 @@ fun Application.module() {
if (files.isEmpty()) {
call.response.status(HttpStatusCode.UnprocessableEntity)
} else {
- val busProps = busesFromRequest(files[0].name, files[0].bytes)
+ val busProps = busesFromRequest(files[0])
call.respond(busProps)
}
}
diff --git a/src/main/FileContent.kt b/src/main/FileContent.kt
new file mode 100644
index 0000000..ee5dc70
--- /dev/null
+++ b/src/main/FileContent.kt
@@ -0,0 +1,44 @@
+package com.github.statnett.loadflowservice
+
+import com.powsybl.commons.datasource.DataSourceUtil
+import com.powsybl.commons.datasource.ReadOnlyMemDataSource
+import java.io.ByteArrayInputStream
+import java.security.MessageDigest
+import java.util.zip.ZipInputStream
+
+class FileContent(val name: String, val bytes: ByteArray) {
+ fun contentHash(): String {
+ val md = MessageDigest.getInstance("MD5")
+ return md.digest(this.bytes).joinToString(separator = "") { eachByte -> "%02x".format(eachByte) }
+ }
+
+ fun contentAsStream(): ByteArrayInputStream {
+ return ByteArrayInputStream(this.bytes)
+ }
+
+ fun asReadOnlyMemDataSource(): ReadOnlyMemDataSource {
+ if (name.endsWith(".zip")) {
+ return zippedArchiveReadOnlyMemDataSource()
+ }
+ return singleFileReadOnlyMemDataSource()
+ }
+
+ private fun singleFileReadOnlyMemDataSource(): ReadOnlyMemDataSource {
+ val dataSource = ReadOnlyMemDataSource(DataSourceUtil.getBaseName(name))
+ dataSource.putData(name, bytes)
+ return dataSource
+ }
+
+ private fun zippedArchiveReadOnlyMemDataSource(): ReadOnlyMemDataSource {
+ val dataSource = ReadOnlyMemDataSource(DataSourceUtil.getBaseName((name)))
+ ZipInputStream(contentAsStream())
+ .use { stream ->
+ generateSequence { stream.nextEntry }
+ .filterNot { entry -> entry.isDirectory }
+ .forEach { entry ->
+ dataSource.putData(entry.name, stream.readAllBytes())
+ }
+ }
+ return dataSource
+ }
+}
\ No newline at end of file
diff --git a/src/main/Solver.kt b/src/main/Solver.kt
index 424e5d5..a501220 100644
--- a/src/main/Solver.kt
+++ b/src/main/Solver.kt
@@ -1,26 +1,20 @@
package com.github.statnett.loadflowservice
+import com.powsybl.commons.PowsyblException
+import com.powsybl.commons.reporter.Reporter
import com.powsybl.commons.reporter.ReporterModel
import com.powsybl.computation.local.LocalComputationManager
-import com.powsybl.iidm.network.ImportersServiceLoader
-import com.powsybl.iidm.network.Network
+import com.powsybl.iidm.network.*
import com.powsybl.loadflow.LoadFlow
import com.powsybl.loadflow.LoadFlowParameters
import com.powsybl.loadflow.json.JsonLoadFlowParameters
import io.github.oshai.kotlinlogging.KotlinLogging
import java.io.ByteArrayOutputStream
-import java.io.InputStream
import java.io.StringWriter
+
private val logger = KotlinLogging.logger {}
-fun networkFromStream(
- filename: String,
- content: InputStream,
-): Network {
- warnOnFewAvailableImporters()
- return Network.read(filename, content)
-}
fun warnOnFewAvailableImporters() {
val numLoaders = ImportersServiceLoader().loadImporters().size
@@ -34,9 +28,23 @@ fun warnOnFewAvailableImporters() {
}
}
+// This function follows closely the functionality implemented in Powsybl-core Network.read
+// However, here we create the ReadOnlyMemDataSource our self which supports constructing it
+// from a zip archive (e.g. CIM/XML files zipped).
fun networkFromFileContent(content: FileContent): Network {
logger.info { "Loading network from file ${content.name}" }
- return networkFromStream(content.name, content.contentAsStream())
+
+ val importConfig = ImportConfig.CACHE.get()
+ val loader = ImportersServiceLoader()
+ val reporter = Reporter.NO_OP
+ val computationManager = LocalComputationManager.getDefault()
+ val dataSource = content.asReadOnlyMemDataSource()
+ val importer = Importer.find(dataSource, loader, computationManager, importConfig)
+ if (importer != null) {
+ val networkFactory = NetworkFactory.findDefault()
+ return importer.importData(dataSource, networkFactory, null, reporter)
+ }
+ throw PowsyblException("No importer found")
}
fun defaultLoadFlowParameters(): String {
diff --git a/src/test/AppTest.kt b/src/test/AppTest.kt
index 761cec6..003a56a 100644
--- a/src/test/AppTest.kt
+++ b/src/test/AppTest.kt
@@ -4,13 +4,9 @@ import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
-import io.ktor.http.content.*
import io.ktor.server.testing.*
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.TestFactory
-import java.io.File
-import java.nio.file.Paths
-import java.util.*
import kotlin.math.abs
import kotlin.test.Test
import kotlin.test.assertContains
@@ -135,6 +131,16 @@ class ApplicationTest {
)
}
+ @Test
+ fun `test 14 bus ok`() =
+ testApplication {
+ val response = client.submitFormWithBinaryData(
+ url = "/buses",
+ formData = formDataFromFile(ieeeCdfNetwork14CgmesFile())
+ )
+ assertEquals(HttpStatusCode.OK, response.status)
+ }
+
@Test
fun `test dc solve ok`() =
testApplication {
@@ -264,38 +270,6 @@ class ApplicationTest {
}
}
-fun formDataFromFile(file: File): List {
- return formData {
- append(
- "network",
- file.readBytes(),
- Headers.build {
- append(HttpHeaders.ContentDisposition, "filename=${file.name}")
- },
- )
- }
-}
-
-fun formDataWithEmptyNetwork(): List {
- return formData {
- append(
- "network",
- byteArrayOf(),
- Headers.build {
- append(HttpHeaders.ContentDisposition, "filename=emptyFile.xiidm")
- }
- )
- }
-}
-
-fun ieeeCdfNetwork14File(): File {
- // Initialize temporary file
- val file = File.createTempFile("network", ".xiidm")
- file.deleteOnExit()
-
- IeeeCdfNetworkFactory.create14().write("XIIDM", Properties(), Paths.get(file.path))
- return file
-}
// Function for checking some properties of a body to verify that the returned body
// is a valid svg image
@@ -303,28 +277,4 @@ fun isPlausibleSvg(body: String): Boolean {
return body.contains("