diff --git a/core/config-kryo/src/main/java/io/gatehill/imposter/config/loader/kryo/BinarySerialiser.kt b/core/config-kryo/src/main/java/io/gatehill/imposter/config/loader/kryo/BinarySerialiser.kt
index 2b8fc799d..4720f9944 100644
--- a/core/config-kryo/src/main/java/io/gatehill/imposter/config/loader/kryo/BinarySerialiser.kt
+++ b/core/config-kryo/src/main/java/io/gatehill/imposter/config/loader/kryo/BinarySerialiser.kt
@@ -49,10 +49,6 @@ import com.esotericsoftware.kryo.io.Output
import org.apache.logging.log4j.LogManager
import java.io.File
-/**
- *
- * @author pete
- */
object BinarySerialiser {
private val logger = LogManager.getLogger(BinarySerialiser::class.java)
diff --git a/tools/kryo-util/build.gradle b/tools/kryo-util/build.gradle
new file mode 100644
index 000000000..5d3c30266
--- /dev/null
+++ b/tools/kryo-util/build.gradle
@@ -0,0 +1,68 @@
+apply plugin: 'java-library'
+apply plugin: 'kotlin'
+apply plugin: 'maven-publish'
+
+compileJava {
+ sourceCompatibility = JavaVersion.VERSION_11
+}
+
+dependencies {
+ implementation project(':core:imposter-api')
+ implementation project(':core:config')
+ implementation project(':core:config-kryo')
+
+ implementation project(':core:config-dynamic')
+ implementation project (':mock:mock-openapi')
+ implementation project (':mock:mock-rest')
+ implementation project (':mock:mock-soap')
+
+ // logging
+ implementation "org.apache.logging.log4j:log4j-core:$version_log4j"
+ implementation "org.apache.logging.log4j:log4j-slf4j-impl:$version_log4j"
+}
+
+task sourcesJar(type: Jar, dependsOn: classes) {
+ archiveClassifier = 'sources'
+ from sourceSets.main.allSource
+}
+
+artifacts {
+ archives sourcesJar
+}
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ from components.java
+ artifact sourcesJar
+
+ repositories {
+ maven {
+ url = version.endsWith('SNAPSHOT') ? mavenSnapshotRepository : mavenReleaseRepository
+ credentials(AwsCredentials) {
+ accessKey awsAccessKey
+ secretKey awsSecretKey
+ }
+ }
+ }
+ }
+ }
+}
+
+compileKotlin {
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_11
+
+ // see https://kotlinlang.org/docs/java-to-kotlin-interop.html#default-methods-in-interfaces
+ freeCompilerArgs = ["-Xjvm-default=all"]
+ }
+}
+
+compileTestKotlin {
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_11
+
+ // see https://kotlinlang.org/docs/java-to-kotlin-interop.html#default-methods-in-interfaces
+ freeCompilerArgs = ["-Xjvm-default=all"]
+ }
+}
diff --git a/tools/kryo-util/src/main/java/io/gatehill/imposter/config/loader/kryo/Snapshotter.kt b/tools/kryo-util/src/main/java/io/gatehill/imposter/config/loader/kryo/Snapshotter.kt
new file mode 100644
index 000000000..ba9535394
--- /dev/null
+++ b/tools/kryo-util/src/main/java/io/gatehill/imposter/config/loader/kryo/Snapshotter.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2024.
+ *
+ * This file is part of Imposter.
+ *
+ * "Commons Clause" License Condition v1.0
+ *
+ * The Software is provided to you by the Licensor under the License, as
+ * defined below, subject to the following condition.
+ *
+ * Without limiting other conditions in the License, the grant of rights
+ * under the License will not include, and the License does not grant to
+ * you, the right to Sell the Software.
+ *
+ * For purposes of the foregoing, "Sell" means practicing any or all of
+ * the rights granted to you under the License to provide to third parties,
+ * for a fee or other consideration (including without limitation fees for
+ * hosting or consulting/support services related to the Software), a
+ * product or service whose value derives, entirely or substantially, from
+ * the functionality of the Software. Any license notice or attribution
+ * required by the License must also include this Commons Clause License
+ * Condition notice.
+ *
+ * Software: Imposter
+ *
+ * License: GNU Lesser General Public License version 3
+ *
+ * Licensor: Peter Cornish
+ *
+ * Imposter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Imposter is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Imposter. If not, see .
+ */
+
+package io.gatehill.imposter.config.loader.kryo
+
+import io.gatehill.imposter.ImposterConfig
+import io.gatehill.imposter.config.util.ConfigUtil
+import io.gatehill.imposter.plugin.DynamicPluginDiscoveryStrategyImpl
+import io.gatehill.imposter.plugin.PluginManagerImpl
+import io.gatehill.imposter.plugin.config.BasicPluginConfig
+import io.gatehill.imposter.plugin.openapi.config.OpenApiPluginConfig
+import io.gatehill.imposter.plugin.rest.config.RestPluginConfig
+import io.gatehill.imposter.plugin.soap.config.SoapPluginConfig
+import org.apache.logging.log4j.LogManager
+
+/**
+ * Loads YAML/JSON config and snapshots as binary.
+ *
+ * @author pete
+ */
+object Snapshotter {
+ private val logger = LogManager.getLogger()
+
+ @JvmStatic
+ fun main(args: Array) {
+ val configDir = arrayOf(args[0])
+ logger.info("Config dir: {}", configDir)
+
+ val configFiles = ConfigUtil.discoverConfigFiles(configDir, ConfigUtil.scanRecursiveConfig)
+ logger.info("Config files: {}", configFiles)
+
+ val discoveryStrategy = DynamicPluginDiscoveryStrategyImpl()
+ val pluginManager = PluginManagerImpl(discoveryStrategy)
+ val pluginConfigs = ConfigUtil.readPluginConfigs(pluginManager, configFiles)
+ logger.info("Read {} configs", pluginConfigs.size)
+
+ val imposterConfig = ImposterConfig()
+ for (pluginConfig in pluginConfigs.values.flatten()) {
+ val configClass = determineConfigClass(pluginConfig.plugin)
+ logger.info("Parsing {}", configClass.canonicalName)
+ val config = ConfigUtil.loadPluginConfig(imposterConfig, pluginConfig, configClass)
+
+ val kryoFile = BinarySerialiser.buildKryoPath(pluginConfig.ref.file)
+ BinarySerialiser.serialise(config, kryoFile)
+ }
+ }
+
+ private fun determineConfigClass(plugin: String): Class {
+ return when (plugin) {
+ "openapi" -> OpenApiPluginConfig::class.java
+ "rest" -> RestPluginConfig::class.java
+ "soap" -> SoapPluginConfig::class.java
+ else -> throw IllegalStateException("Unsupported plugin: $plugin")
+ }
+ }
+}
diff --git a/tools/kryo-util/src/main/resources/log4j2.xml b/tools/kryo-util/src/main/resources/log4j2.xml
new file mode 100644
index 000000000..d7f51d206
--- /dev/null
+++ b/tools/kryo-util/src/main/resources/log4j2.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+