THRIFT-5548: add kotlin code gen
Client: kotlin
Patch: Jiayu Liu

This closes #2556
diff --git a/lib/kotlin/cross-test-server/build.gradle.kts b/lib/kotlin/cross-test-server/build.gradle.kts
new file mode 100644
index 0000000..6b20de1
--- /dev/null
+++ b/lib/kotlin/cross-test-server/build.gradle.kts
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+    kotlin("jvm") version "1.5.31"
+    id("com.ncorti.ktfmt.gradle") version "0.4.0"
+    java
+    application
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
+    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
+    // https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-jdk8
+    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.1")
+    // https://mvnrepository.com/artifact/org.apache.thrift/libthrift
+    implementation("org.apache.thrift:libthrift:INCLUDED")
+    // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
+    implementation("ch.qos.logback:logback-classic:1.3.0-alpha14")
+    testImplementation("org.jetbrains.kotlin:kotlin-test")
+    testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
+}
+
+tasks {
+    application {
+        applicationName = "TestServer"
+        mainClass.set("org.apache.thrift.test.TestServerKt")
+    }
+
+    ktfmt {
+        kotlinLangStyle()
+    }
+
+    task<Exec>("compileThrift") {
+        val thriftBin = if (hasProperty("thrift.compiler")) {
+            file(property("thrift.compiler"))
+        } else {
+            project.rootDir.resolve("../../compiler/cpp/thrift")
+        }
+        val outputDir = layout.buildDirectory.dir("generated-sources")
+        doFirst {
+            mkdir(outputDir)
+        }
+        commandLine = listOf(
+            thriftBin.absolutePath,
+            "-gen",
+            "kotlin",
+            "-out",
+            outputDir.get().toString(),
+            project.rootDir.resolve("../../test/ThriftTest.thrift").absolutePath
+        )
+        group = LifecycleBasePlugin.BUILD_GROUP
+    }
+
+    compileKotlin {
+        dependsOn("compileThrift")
+    }
+}
+
+sourceSets["main"].java {
+    srcDir(layout.buildDirectory.dir("generated-sources"))
+}
diff --git a/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestHandler.kt b/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestHandler.kt
new file mode 100644
index 0000000..4bbdb6a
--- /dev/null
+++ b/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestHandler.kt
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.thrift.test
+
+import java.nio.ByteBuffer
+import kotlinx.coroutines.delay
+import org.apache.thrift.TException
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import thrift.test.Insanity
+import thrift.test.Numberz
+import thrift.test.ThriftTest
+import thrift.test.Xception
+import thrift.test.Xception2
+import thrift.test.Xtruct
+import thrift.test.Xtruct2
+
+class TestHandler : ThriftTest {
+
+    companion object {
+        private val logger: Logger = LoggerFactory.getLogger(TestHandler::class.java)
+    }
+
+    override suspend fun testVoid() {
+        logger.info("testVoid()\n")
+    }
+
+    override suspend fun testString(thing: String): String {
+        logger.info("testString(\"$thing\")\n")
+        return thing
+    }
+
+    override suspend fun testBool(thing: Boolean): Boolean {
+        logger.info("testBool($thing)\n")
+        return thing
+    }
+
+    override suspend fun testByte(thing: Byte): Byte {
+        logger.info("testByte($thing)\n")
+        return thing
+    }
+
+    override suspend fun testI32(thing: Int): Int {
+        logger.info("testI32($thing)\n")
+        return thing
+    }
+
+    override suspend fun testI64(thing: Long): Long {
+        logger.info("testI64($thing)\n")
+        return thing
+    }
+
+    override suspend fun testDouble(thing: Double): Double {
+        logger.info("testDouble($thing)\n")
+        return thing
+    }
+
+    override suspend fun testBinary(thing: ByteArray): ByteArray {
+        val buffer = ByteBuffer.wrap(thing)
+        val sb = StringBuilder(buffer.remaining() * 3)
+        buffer.mark()
+        var limit = 0 // limit output to keep the log size sane
+        while (buffer.remaining() > 0 && ++limit < 1024) {
+            sb.append(String.format("%02X ", buffer.get()))
+        }
+        if (buffer.remaining() > 0) {
+            sb.append("...") // indicate we have more date
+        }
+        logger.info("testBinary($sb)\n")
+        buffer.reset()
+        return buffer.array()
+    }
+
+    override suspend fun testStruct(thing: Xtruct): Xtruct {
+        logger.info(
+            """
+testStruct({"${thing.string_thing}", ${thing.byte_thing}, ${thing.i32_thing}, ${thing.i64_thing}})
+
+""".trimIndent()
+        )
+        return thing
+    }
+
+    override suspend fun testNest(thing: Xtruct2): Xtruct2 {
+        val thing2: Xtruct = thing.struct_thing!!
+        logger.info(
+            """
+testNest({${thing.byte_thing}, {"${thing2.string_thing}", ${thing2.byte_thing}, ${thing2.i32_thing}, ${thing2.i64_thing}}, ${thing.i32_thing}})
+
+""".trimIndent()
+        )
+        return thing
+    }
+
+    override suspend fun testMap(thing: Map<Int, Int>): Map<Int, Int> {
+        logger.info("testMap({")
+        logger.info("{}", thing)
+        logger.info("})\n")
+        return thing
+    }
+
+    override suspend fun testStringMap(thing: Map<String, String>): Map<String, String> {
+        logger.info("testStringMap({")
+        logger.info("{}", thing)
+        logger.info("})\n")
+        return thing
+    }
+
+    override suspend fun testSet(thing: Set<Int>): Set<Int> {
+        logger.info("testSet({")
+        var first = true
+        for (elem in thing) {
+            if (first) {
+                first = false
+            } else {
+                logger.info(", ")
+            }
+            logger.info("{}", elem)
+        }
+        logger.info("})\n")
+        return thing
+    }
+
+    override suspend fun testList(thing: List<Int>): List<Int> {
+        logger.info("testList({")
+        var first = true
+        for (elem in thing) {
+            if (first) {
+                first = false
+            } else {
+                logger.info(", ")
+            }
+            logger.info("{}", elem)
+        }
+        logger.info("})\n")
+        return thing
+    }
+
+    override suspend fun testEnum(thing: Numberz): Numberz {
+        logger.info("testEnum($thing)\n")
+        return thing
+    }
+
+    override suspend fun testTypedef(thing: Long): Long {
+        logger.info("testTypedef($thing)\n")
+        return thing
+    }
+
+    override suspend fun testMapMap(hello: Int): Map<Int, Map<Int, Int>> {
+        logger.info("testMapMap($hello)\n")
+        val mapmap: MutableMap<Int, Map<Int, Int>> = HashMap()
+        val pos = HashMap<Int, Int>()
+        val neg = HashMap<Int, Int>()
+        for (i in 1..4) {
+            pos[i] = i
+            neg[-i] = -i
+        }
+        mapmap[4] = pos
+        mapmap[-4] = neg
+        return mapmap
+    }
+
+    override suspend fun testInsanity(argument: Insanity): Map<Long, Map<Numberz, Insanity>> {
+        logger.info("testInsanity()\n")
+        val firstMap = mutableMapOf<Numberz, Insanity>()
+        val secondMap = mutableMapOf<Numberz, Insanity>()
+        firstMap[Numberz.TWO] = argument
+        firstMap[Numberz.THREE] = argument
+        val looney = Insanity()
+        secondMap[Numberz.SIX] = looney
+        val insane: MutableMap<Long, Map<Numberz, Insanity>> = HashMap()
+        insane[1L] = firstMap
+        insane[2L] = secondMap
+        return insane
+    }
+
+    override suspend fun testMulti(
+        arg0: Byte,
+        arg1: Int,
+        arg2: Long,
+        arg3: Map<Short, String>,
+        arg4: Numberz,
+        arg5: Long
+    ): Xtruct {
+        logger.info("testMulti()\n")
+        val hello = Xtruct()
+        hello.string_thing = "Hello2"
+        hello.byte_thing = arg0
+        hello.i32_thing = arg1
+        hello.i64_thing = arg2
+        return hello
+    }
+
+    @Throws(Xception::class, TException::class)
+    override suspend fun testException(arg: String) {
+        logger.info("testException($arg)\n")
+        when (arg) {
+            "Xception" -> {
+                val x = Xception()
+                x.errorCode = 1001
+                x.setFieldValue(Xception._Fields.MESSAGE, arg)
+                throw x
+            }
+            "TException" -> {
+                // Unspecified exception should yield a TApplicationException on client side
+                throw RuntimeException(arg)
+            }
+            else -> {
+                val result = Xtruct()
+                result.string_thing = arg
+            }
+        }
+        return
+    }
+
+    @Throws(Xception::class, Xception2::class)
+    override suspend fun testMultiException(arg0: String, arg1: String): Xtruct {
+        logger.info("testMultiException($arg0, $arg1)\n")
+        if (arg0 == "Xception") {
+            val x = Xception()
+            x.errorCode = 1001
+            x.setFieldValue(Xception._Fields.MESSAGE, "This is an Xception")
+            throw x
+        } else if (arg0 == "Xception2") {
+            val x = Xception2()
+            x.errorCode = 2002
+            x.struct_thing = Xtruct().apply { string_thing = "This is an Xception2" }
+            throw x
+        }
+        val result = Xtruct()
+        result.string_thing = arg1
+        return result
+    }
+
+    override suspend fun testOneway(secondsToSleep: Int) {
+        logger.info("testOneway($secondsToSleep) => sleeping...")
+        try {
+            delay(secondsToSleep * 1000L)
+            logger.info("Done sleeping!")
+        } catch (ie: InterruptedException) {
+            throw RuntimeException(ie)
+        }
+    }
+}
diff --git a/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestServer.kt b/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestServer.kt
new file mode 100644
index 0000000..b04548d
--- /dev/null
+++ b/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestServer.kt
@@ -0,0 +1,358 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.thrift.test
+
+import kotlin.system.exitProcess
+import kotlinx.coroutines.GlobalScope
+import org.apache.thrift.TException
+import org.apache.thrift.TMultiplexedProcessor
+import org.apache.thrift.protocol.TBinaryProtocol
+import org.apache.thrift.protocol.TCompactProtocol
+import org.apache.thrift.protocol.TJSONProtocol
+import org.apache.thrift.protocol.TProtocol
+import org.apache.thrift.protocol.TProtocolFactory
+import org.apache.thrift.server.ServerContext
+import org.apache.thrift.server.TNonblockingServer
+import org.apache.thrift.server.TServer
+import org.apache.thrift.server.TServerEventHandler
+import org.apache.thrift.server.TSimpleServer
+import org.apache.thrift.server.TThreadPoolServer
+import org.apache.thrift.server.TThreadedSelectorServer
+import org.apache.thrift.transport.TNonblockingServerSocket
+import org.apache.thrift.transport.TNonblockingServerSocket.NonblockingAbstractServerSocketArgs
+import org.apache.thrift.transport.TSSLTransportFactory
+import org.apache.thrift.transport.TServerSocket
+import org.apache.thrift.transport.TServerSocket.ServerSocketTransportArgs
+import org.apache.thrift.transport.TTransport
+import org.apache.thrift.transport.TTransportFactory
+import org.apache.thrift.transport.TZlibTransport
+import org.apache.thrift.transport.layered.TFastFramedTransport
+import org.apache.thrift.transport.layered.TFramedTransport
+import thrift.test.SecondService
+import thrift.test.SecondServiceProcessor
+import thrift.test.ThriftTestProcessor
+
+object TestServer {
+
+    // Multiplexed Protocol Support Details:
+    //
+    // For multiplexed testing we always use binary protocol underneath.
+    //
+    // "ThriftTest" named service implements "ThriftTest" from ThriftTest.thrift
+    // "SecondService" named service implements "SecondService" from ThriftTest.thrift
+    // In addition, to support older non-multiplexed clients using the same concrete protocol
+    // the multiplexed processor is taught to use "ThriftTest" if the incoming request has no
+    // multiplexed call name decoration.
+    internal class SecondHandler : SecondService {
+        @Throws(TException::class)
+        override suspend fun secondtestString(thing: String): String {
+            return "testString(\"$thing\")"
+        }
+    }
+
+    internal class TestServerContext(var connectionId: Int) : ServerContext {
+
+        override fun <T> unwrap(iface: Class<T>): T {
+            try {
+                return if (isWrapperFor(iface)) {
+                    iface.cast(this)
+                } else {
+                    throw RuntimeException("The context is not a wrapper for " + iface.name)
+                }
+            } catch (e: Exception) {
+                throw RuntimeException(
+                    "The context is not a wrapper and does not implement the interface"
+                )
+            }
+        }
+
+        override fun isWrapperFor(iface: Class<*>): Boolean {
+            return iface.isInstance(this)
+        }
+    }
+
+    internal class TestServerEventHandler() : TServerEventHandler {
+        private var nextConnectionId = 1
+        override fun preServe() {
+            println(
+                "TServerEventHandler.preServe - called only once before server starts accepting connections"
+            )
+        }
+
+        override fun createContext(input: TProtocol, output: TProtocol): ServerContext {
+            // we can create some connection level data which is stored while connection is alive &
+            // served
+            val ctx = TestServerContext(nextConnectionId++)
+            println(
+                "TServerEventHandler.createContext - connection #" +
+                    ctx.connectionId +
+                    " established"
+            )
+            return ctx
+        }
+
+        override fun deleteContext(
+            serverContext: ServerContext,
+            input: TProtocol,
+            output: TProtocol
+        ) {
+            val ctx = serverContext.unwrap(TestServerContext::class.java)
+            println(
+                "TServerEventHandler.deleteContext - connection #" +
+                    ctx.connectionId +
+                    " terminated"
+            )
+        }
+
+        override fun processContext(
+            serverContext: ServerContext,
+            inputTransport: TTransport,
+            outputTransport: TTransport
+        ) {
+            val ctx = serverContext.unwrap(TestServerContext::class.java)
+            println(
+                "TServerEventHandler.processContext - connection #" +
+                    ctx.connectionId +
+                    " is ready to process next request"
+            )
+        }
+    }
+}
+
+fun main(args: Array<String>) {
+    try {
+        var port = 9090
+        var ssl = false
+        var zlib = false
+        var transportType = "buffered"
+        var protocolType = "binary"
+//        var serverType = "thread-pool"
+        var serverType = "nonblocking"
+        val domainSocket = ""
+        var stringLimit: Long = -1
+        var containerLimit: Long = -1
+        try {
+            for (i in args.indices) {
+                if (args[i].startsWith("--port")) {
+                    port = Integer.valueOf(args[i].split("=").toTypedArray()[1])
+                } else if (args[i].startsWith("--server-type")) {
+                    serverType = args[i].split("=").toTypedArray()[1]
+                    serverType.trim { it <= ' ' }
+                } else if (args[i].startsWith("--port")) {
+                    port = args[i].split("=").toTypedArray()[1].toInt()
+                } else if (args[i].startsWith("--protocol")) {
+                    protocolType = args[i].split("=").toTypedArray()[1]
+                    protocolType.trim { it <= ' ' }
+                } else if (args[i].startsWith("--transport")) {
+                    transportType = args[i].split("=").toTypedArray()[1]
+                    transportType.trim { it <= ' ' }
+                } else if (args[i] == "--ssl") {
+                    ssl = true
+                } else if (args[i] == "--zlib") {
+                    zlib = true
+                } else if (args[i].startsWith("--string-limit")) {
+                    stringLimit = args[i].split("=").toTypedArray()[1].toLong()
+                } else if (args[i].startsWith("--container-limit")) {
+                    containerLimit = args[i].split("=").toTypedArray()[1].toLong()
+                } else if (args[i] == "--help") {
+                    println("Allowed options:")
+                    println("  --help\t\t\tProduce help message")
+                    println("  --port=arg (=$port)\tPort number to connect")
+                    println(
+                        "  --transport=arg (=$transportType)\n\t\t\t\tTransport: buffered, framed, fastframed, zlib"
+                    )
+                    println(
+                        "  --protocol=arg (=$protocolType)\tProtocol: binary, compact, json, multi, multic, multij"
+                    )
+                    println("  --ssl\t\t\tEncrypted Transport using SSL")
+                    println("  --zlib\t\t\tCompressed Transport using Zlib")
+                    println(
+                        "  --server-type=arg (=$serverType)\n\t\t\t\tType of server: simple, thread-pool, nonblocking, threaded-selector"
+                    )
+                    println("  --string-limit=arg (=$stringLimit)\tString read length limit")
+                    println(
+                        "  --container-limit=arg (=$containerLimit)\tContainer read length limit"
+                    )
+                    exitProcess(0)
+                }
+            }
+        } catch (e: Exception) {
+            System.err.println("Can not parse arguments! See --help")
+            exitProcess(1)
+        }
+        try {
+            when (serverType) {
+                "simple" -> {}
+                "thread-pool" -> {}
+                "nonblocking" -> {
+                    if (ssl) {
+                        throw Exception("SSL is not supported over nonblocking servers!")
+                    }
+                }
+                "threaded-selector" -> {
+                    if (ssl) {
+                        throw Exception("SSL is not supported over nonblocking servers!")
+                    }
+                }
+                else -> {
+                    throw Exception("Unknown server type! $serverType")
+                }
+            }
+            when (protocolType) {
+                "binary" -> {}
+                "compact" -> {}
+                "json" -> {}
+                "multi" -> {}
+                "multic" -> {}
+                "multij" -> {}
+                else -> {
+                    throw Exception("Unknown protocol type! $protocolType")
+                }
+            }
+            when (transportType) {
+                "buffered" -> {}
+                "framed" -> {}
+                "fastframed" -> {}
+                "zlib" -> {}
+                else -> {
+                    throw Exception("Unknown transport type! $transportType")
+                }
+            }
+        } catch (e: Exception) {
+            System.err.println("Error: " + e.message)
+            exitProcess(1)
+        }
+
+        // Processors
+        val testHandler = TestHandler()
+        val testProcessor = ThriftTestProcessor(testHandler, scope = GlobalScope)
+        val secondHandler = TestServer.SecondHandler()
+        val secondProcessor = SecondServiceProcessor(secondHandler, scope = GlobalScope)
+
+        // Protocol factory
+        val tProtocolFactory: TProtocolFactory =
+            when (protocolType) {
+                "json", "multij" -> {
+                    TJSONProtocol.Factory()
+                }
+                "compact", "multic" -> {
+                    TCompactProtocol.Factory(stringLimit, containerLimit)
+                }
+                else -> { // also covers multi
+                    TBinaryProtocol.Factory(stringLimit, containerLimit)
+                }
+            }
+        val tTransportFactory: TTransportFactory =
+            when (transportType) {
+                "framed" -> {
+                    TFramedTransport.Factory()
+                }
+                "fastframed" -> {
+                    TFastFramedTransport.Factory()
+                }
+                "zlib" -> {
+                    TZlibTransport.Factory()
+                }
+                else -> { // .equals("buffered") => default value
+                    TTransportFactory()
+                }
+            }
+        val serverEngine: TServer
+        // If we are multiplexing services in one server...
+        val multiplexedProcessor = TMultiplexedProcessor()
+        multiplexedProcessor.registerDefault(testProcessor)
+        multiplexedProcessor.registerProcessor("ThriftTest", testProcessor)
+        multiplexedProcessor.registerProcessor("SecondService", secondProcessor)
+        if (serverType == "nonblocking" || serverType == "threaded-selector") {
+            // Nonblocking servers
+            val tNonblockingServerSocket =
+                TNonblockingServerSocket(NonblockingAbstractServerSocketArgs().port(port))
+            if (serverType.contains("nonblocking")) {
+                // Nonblocking Server
+                val tNonblockingServerArgs = TNonblockingServer.Args(tNonblockingServerSocket)
+                tNonblockingServerArgs.processor(
+                    if (protocolType.startsWith("multi")) multiplexedProcessor else testProcessor
+                )
+                tNonblockingServerArgs.protocolFactory(tProtocolFactory)
+                tNonblockingServerArgs.transportFactory(tTransportFactory)
+                serverEngine = TNonblockingServer(tNonblockingServerArgs)
+            } else { // server_type.equals("threaded-selector")
+                // ThreadedSelector Server
+                val tThreadedSelectorServerArgs =
+                    TThreadedSelectorServer.Args(tNonblockingServerSocket)
+                tThreadedSelectorServerArgs.processor(
+                    if (protocolType.startsWith("multi")) multiplexedProcessor else testProcessor
+                )
+                tThreadedSelectorServerArgs.protocolFactory(tProtocolFactory)
+                tThreadedSelectorServerArgs.transportFactory(tTransportFactory)
+                serverEngine = TThreadedSelectorServer(tThreadedSelectorServerArgs)
+            }
+        } else {
+            // Blocking servers
+
+            // SSL socket
+            val tServerSocket: TServerSocket = if (ssl) {
+                TSSLTransportFactory.getServerSocket(port, 0)
+            } else {
+                TServerSocket(ServerSocketTransportArgs().port(port))
+            }
+            if (serverType == "simple") {
+                // Simple Server
+                val tServerArgs = TServer.Args(tServerSocket)
+                tServerArgs.processor(
+                    if (protocolType.startsWith("multi")) multiplexedProcessor else testProcessor
+                )
+                tServerArgs.protocolFactory(tProtocolFactory)
+                tServerArgs.transportFactory(tTransportFactory)
+                serverEngine = TSimpleServer(tServerArgs)
+            } else { // server_type.equals("threadpool")
+                // ThreadPool Server
+                val tThreadPoolServerArgs = TThreadPoolServer.Args(tServerSocket)
+                tThreadPoolServerArgs.processor(
+                    if (protocolType.startsWith("multi")) multiplexedProcessor else testProcessor
+                )
+                tThreadPoolServerArgs.protocolFactory(tProtocolFactory)
+                tThreadPoolServerArgs.transportFactory(tTransportFactory)
+                serverEngine = TThreadPoolServer(tThreadPoolServerArgs)
+            }
+        }
+
+        // Set server event handler
+        serverEngine.setServerEventHandler(TestServer.TestServerEventHandler())
+
+        // Run it
+        println(
+            "Starting the " +
+                (if (ssl) "ssl server" else "server") +
+                " [" +
+                protocolType +
+                "/" +
+                transportType +
+                "/" +
+                serverType +
+                "] on " +
+                if (domainSocket === "") "port $port" else "unix socket $domainSocket"
+        )
+        serverEngine.serve()
+    } catch (x: Exception) {
+        x.printStackTrace()
+    }
+    println("done.")
+}
diff --git a/lib/kotlin/cross-test-server/src/main/resources/logback.xml b/lib/kotlin/cross-test-server/src/main/resources/logback.xml
new file mode 100644
index 0000000..98309d7
--- /dev/null
+++ b/lib/kotlin/cross-test-server/src/main/resources/logback.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<configuration>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <!-- encoders are assigned the type
+             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <root level="debug">
+        <appender-ref ref="STDOUT"/>
+    </root>
+</configuration>