Fun with Kotlin extensions... Kotlin examples from Solr-Undertow with TypeSafe Config, undertow.io


#1

I pushed Solr-Undertow on Github: https://github.com/bremeld/solr-undertow

It is a high performance standalone http/servlet host for Solr search engine.  And has some examples of using undertow.io and TypeSafe Config which is a great configuration library.  

For Kotlin programmers, its nice that we can redefine an API we don’t like.  

So for example, instead of…

val tempDir = Paths.resolve(cfg.getString("something.tempDir")!!)!! val enableSaving = cfg.getBoolean("something.allowSaves")

change to a consistent and human readable:

val tempDir = configured.value("something.tempDir").asPath() val enableSaving = configured.value("something.allowSaves").asBoolean()

And instead of different ways to check existance than from getting the property, pass around the property value reference as a place-holder.  

So Instead of:

if (cfg.hasPath("my.property")) {   val myProp = cfg.getString("my.property")!!   if (myProp.isNotEmpty()) {   server.setFancyProp(myProp)   } }

We can do things more like:

val myProp = configured.value("my.property") if (myProp.isNotEmpty()) {   server.setFancyProp(myProp.asString()) }

Or instead of:

val cores = Runtime.getRuntime().availableProcessors() val ioThreads = Math.max(2, cfg.getInt("httpIoThreads")), cores) val workerThreads = Math.min(Math.max(ioThreads, cfg.getInt("httpWorkerThreads"), ioThreads*16)

can have:

val cores = Runtime.getRuntime().availableProcessors() val ioThreads = configured.value("httpIoThreads").asInt().minimum(2).maximum(cores) val workerThreads = configured.value("httpWorkerThreads").asInt().minimum(ioThreads).maximum(ioThreasd*16)

or even:

val cores = Runtime.getRuntime().availableProcessors() val ioThreads = configured.value("httpIoThreads").asInt().coerce(2..cores)  // it is a range afterall val workerThreads = configured.value("httpWorkerThreads").asInt().coerce(ioThreads, ioThreasd*16)  // or as parameters

(See Config.kt for more examples of usage, and Utils.kt for the extensions)

Then I had cases where I needed to initialize something but do something else with the same value, and didn’t want to create an anonymous initializer for the constructor since that would move the logic away from setting the property.   I wanted a “initialize the variable then do this…” or “initialize the variable then verify the contents…”  Here are some examples of the two:

For verifying after assignment, from the main() of solr-undertow:

public fun main(args: Array<String>) {   try {   if (args.size != 1) {            printErrorAndExit("A Configuration file must be passed on the command-line (i.e. /my/path/to/solr-undertow.conf)")   }   val configFile = Paths.get(args[0])!!.toAbsolutePath() verifiedBy { path ->            if (!Files.exists(path)) {            printErrorAndExit("Configuration file does not exist: ${path.toString()}")            }   }

  &nbsp;&nbsp;val configLoader = ServerConfigLoader(configFile) <strong >verifiedBy</strong> { configLoader -&gt;

           if (!configLoader.hasLoggingDir()) {
           printErrorAndExit(“solr.undertow.solrLogs is missing from configFile, is required for logging”)
           }
  }

  &nbsp;&nbsp;val (serverStarted, message) = Server(configLoader).run()
  &nbsp;&nbsp;if (!serverStarted) {

           printErrorAndExit(message)
  }

  } catch (ex: Throwable) {
  ex.printStackTrace()
  printErrorAndExit(ex.getMessage())
  }
}


And verifiedBy also can be used during instantiation of class members.  There are other options like .let() but those expect a return, whereas verifiedBy automatically continues with “this” after you call so you can chain other methods after.  And let() doesn’t say what I’m intending to do, whereas a quick extension verifiedBy says what I’m intending, even if identical to an existing extension function in terms of functionality.

Another case of doing the same thing, but with different intent:  “initialize something then do something with it”:

val resolvedConfig = ConfigFactory.load(fullConfig, ConfigResolveOptions.noSystem())!! then { config ->   setRelevantSystemProperties(config) }

Or if I have further initlization to do, say after creating a mutable Map with intializedBy({ }) which is identical to the declaration of then({ }):

val requestLimiters = HashMap<String, RequestLimitConfig>() initializedBy { requestLimiters ->   val namedConfigs = configured.nested("requestLimits")   activeRequestLimits.forEach { name ->            requestLimiters.put(name, RequestLimitConfig(log, name, namedConfigs.getConfig(name)!!))   } }

Obviously the same functionality, but now you don't have to guess what the code is doing, the naming helps to define the logic.  And lastly using the actual .let() extension function because it is a case where the value can actually change during the lamda:

val solrContextPath = configured.value(OUR_PROP_HOST_CONTEXT).asString() let { solrContextPath ->   if (solrContextPath.isEmpty()) "/" else solrContextPath }

Lastly a chain would look like...

val serverConfig = loadConfig() initializedBy { config->   ... modify the config slightly } verifiedBy {   ... verify it is all now correct }

Where obviously I could have code in initializedBy that checks that validity, but with the chain it has been made more clear what each code block does, instead of random if statements appearing in the initializer.

On a side note, I’ll be releasing a TypeSafe Konfig for Kotlin with a happier and more clear API soon, maybe for Kotlinx?

Have fun with Kotlin!