Prototyping with Kotlin

Image by Ольга Бережна from Pixabay

Java is the King in the Enterprise world. Very often I need some quick prototyping interacting with some Java libraries. But Java is verbose and sometimes a lot of boilerplate codes can not be avoided. When a prototype is required, I often turn to some alternatives. I used to use Jruby, but nowadays I am exploring Kotlin as my 1st choice.

It's preferable and sometimes a must, that the developed prototype/tool has to be run on JVM, which is available for almost all of the different platforms in the enterprise world. Secondly, no installation should be required, therefore a flat jar (uber jar) which bundled all the dependencies together should be the end product.

Kotlin, 100% native java compatible is a good candidate for the above two requirements. Let’s explore some use cases.

When we don’t need any java library, or the required Java module is included in one single jar file without any chained dependency, we can use the Kotlin compiler to package our prototype into a single jar file,

kotlinc -include-runtime -d myapp.jar myapp.kt

The -d option points to the destination jar file, while -include-runtime make the jar as a flat jar where the Kotlin runtime is included, so it can run on any JVM as,

java -jar myapp.jar

If the code is importing and calling some java library, say enterprise-lib.jar, then we can compile it by defining the classpath,

kotlinc -include-runtime -d myapp.jar -cp enterprise-lib.jar myapp.kt

and later run it as,

java -cp enterprise-lib.jar:myapp.jar Myappkt

Notice the main class’s naming convention.

Enterprise is complex, there might be a chain dependency of the libraries, we will need a tool to manage it.

Take the IBM MQ client as an example. As indicated in the maven repository, the MQ client library depends on many others. We don’t want to manually manage the dependency, we will leave it to Gradle.

Init a Gradle project by calling

gradle init

Then interactively select the type of the project as application, Kotlin as the language, …, and define project name.

A scaffolding project is created. Now let's add the MQ dependency in the app/build.gradle dependencies block,

dependencies {
...
implementation 'com.ibm.mq:com.ibm.mq.allclient:9.2.3.0'
}

In order to build the flat jar file, we use the shadow plugin, add it in the plugins part,

plugins {
...
id 'com.github.johnrengelman.shadow' version '7.1.0'
}

And define the main class for the jar file, by adding the following.

shadowJar{
manifest {
attributes 'Main-Class': "mqclient.AppKt"
}
}

The main class is using the naming convention of ${package}.${Main.kt file name + “Kt”}

Code the MQ connection in Kotlin,

package mqclientimport java.io.FileInputStream
import java.security.KeyStore
import java.security.SecureRandom
import java.util.*
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManagerFactory
import com.ibm.mq.constants.MQConstants
import com.ibm.mq.MQQueueManager
fun main() {
System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false")
val sslDebug = System.getenv("MQC_SSL_DEBUG") ?: "NO"
if (sslDebug == "YES") {
System.setProperty("javax.net.debug", "ssl:handshake")
}

val props = Hashtable<String, Any>()

val attrs = listOf(
hashMapOf("attr" to MQConstants.CHANNEL_PROPERTY, "env" to "MQC_CHANNEL", "default" to "channel1"),
hashMapOf("attr" to MQConstants.HOST_NAME_PROPERTY, "env" to "MQC_HOSTNAME", "default" to "localhost"),
hashMapOf("attr" to MQConstants.PORT_PROPERTY, "env" to "MQC_PORT", "default" to "1414"),
hashMapOf("attr" to MQConstants.TRANSPORT_PROPERTY, "env" to "MQC_TRANSPORT", "default" to "MQSeries Client"),
hashMapOf("attr" to MQConstants.SSL_CIPHER_SUITE_PROPERTY, "env" to "MQC_CIPHERSUITE", "default" to "TLS_RSA_WITH_AES_256_CBC_SHA256"),
)
for (a in attrs) {
props[a["attr"]] = System.getenv(a["env"]) ?: a["default"]
if (a["attr"] == "port") {
props[a["attr"]] = props[a["attr"]].toString().toIntOrNull()
}
}
println("props = ${props}") val keystoreFile = System.getenv("MQC_KEYSTORE") ?: "/app/cert/mqplugin.jks"
val keystorePass = System.getenv("MQC_KEYSTORE_PASSWORD") ?: "password"

val keystore = KeyStore.getInstance("JKS")
keystore.load(FileInputStream(keystoreFile), keystorePass.toCharArray())
val tmf=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
tmf.init(keystore)
val kmf = KeyManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
kmf.init(keystore, keystorePass.toCharArray())

val sslContext = SSLContext.getInstance("TLS")
sslContext.init(kmf.keyManagers, tmf.trustManagers, SecureRandom());
props["SSL Socket Factory"]=sslContext.socketFactory val qmName = System.getenv("MQC_QM_NAME") ?: "qm1"
val qmgr = MQQueueManager(qmName, props)
println(qmgr)

//....
}

You may need to change the test source file, as we have removed the default class definition.

Now build the package,

./gradlew clean build shadowJar

The flat jar is then created as in build/app/libs/app-all.jar

We are ready to test this prototype.

Cloud explorer