I am using ktor as a web server. Users call a Kotlin Clikt command to start the server, passing to it a variable that is needed later when the backend processes certain REST requests.
The clikt class then starts the server takes a single parameter and this parameter is passed when starting the embedded server. That code is here:
class StartServer : CliktCommand(help = "Starts PHGv2 BrAPI Server") {
private val myLogger = LogManager.getLogger(StartServer::class.java)
val dbPath by option(help = "Full path to folder where TileDB datasets are stored. ")
.default("")
.validate {
require(it.isNotBlank()) {
"--db-path must not be blank"
}
}
override fun run() {
setupDebugLogging()
// Verify the uri is valid. We only care about the hvcf dataset,
// so check that one explicitly
val hvcfExists = verifyURI(dbPath,"hvcf_dataset")
if (!hvcfExists) {
myLogger.error("hvcf_dataset does not exist in $dbPath. Exiting.")
return
}
// Create an Args list to pass to the server
// This tells the endpoint code where the datasets are located.
val dbUri = "-P:TILEDB_URI=${dbPath}"
val args = arrayOf(dbUri)
// commandLineEnvironment reads the application.config file
// https://ktor.io/docs/configuration.html#hocon-file
embeddedServer(Netty, commandLineEnvironment(args)).start(wait = true)
}
}
Later, when servicing a query that needs this configuration variable, I have this code:
private val config = HoconApplicationConfig(ConfigFactory.load())
//val tiledbURI = environment.config.property("TILEDB_URI").getString()
val tiledbURI = config.property("TILEDB_URI").getString()
object SamplesService {
private val myLogger = LogManager.getLogger(SamplesService::class.java)
// Cached map of all taxa. Key is genoid mapped to Sample object
private val taxa: Map<String, Sample> by lazy {
taxaMap("${tiledbURI}/hvcf_dataset")
}
....
The value for tiledbURI is always null (but the code compiles). If I follow the examples from the the documentation, it shows grabbing the values from environment:
val tiledbURI = environment.config.property("TILEDB_URI").getString()
But "environment" is not known and will not compile. Is there a different import that is needed? My related imports are:
import com.typesafe.config.ConfigFactory
import io.ktor.server.config.*
Am I missing an import? Or does this variable only exist to start the server, and they are not stored in the config file for access further down?
UPDATE/EDIT:
The hcon config file is this:
callsPageSize=10
variantsPageSize=100
# For connecting to tiledb from ktor. Users should un-comment
# and edit the TILEDB_URI variable to point to their tiledb folder
# When running junit tests, replace ""/Users/lcj34" in the example below
# with the path to your home directory. The path should end with a /
# For other use cases, replace the path with the path to the tiledb folder
#TILEDB_URI="/Users/lcj34/temp/phgv2Tests/tempDir/testTileDBURI/"
# Server metadata params You will need to fill these out to match your setup
contactEmail = "[email protected]"
documentationURL = "https://github.com/maize-genetics/phg_v2"
location = "Ithaca NY"
organizationName = "Institute for Genetic Diversity at Cornell University"
organizationURL = "https://www.maizegenetics.net/"
serverDescription = "Server to connect to the Maize PHG Tiledb through BrAPI calls."
serverName = "Maize PHGv2"
ktor {
deployment {
port = 8080
watch = [ build ]
}
application {
modules = [ net.maizegenetics.phgv2.brapi.ApplicationKt.module ]
}
}
Note on the commented out TILEDB_URI variable. Users that run our application do not have easy access to the config file as it is bundled in a fat jar. The comment related to updating the TILEDB_URI variable in the config file is mostly for developer junit testing.
What we need is for the user to be able to pass us a value that we can set for this parameter.
I was able to get this to work by making the following changes. Basically, I pass the user parameter through to the ktor routing code. The files shown above are changed as follows:
class StartServer : CliktCommand(help = "Starts PHGv2 BrAPI Server") {
private val myLogger = LogManager.getLogger(StartServer::class.java)
val dbPath by option(help = "Full path to folder where TileDB datasets are stored. ")
.default("")
.validate {
require(it.isNotBlank()) {
"--db-path must not be blank"
}
}
override fun run() {
setupDebugLogging()
// Verify the uri is valid. We only care about the hvcf dataset,
// so check that one explicitly
val hvcfExists = verifyURI(dbPath,"hvcf_dataset")
if (!hvcfExists) {
myLogger.error("hvcf_dataset does not exist in $dbPath. Exiting.")
return
}
// Create an Args list to pass to the server
// This tells the endpoint code where the datasets are located.
val dbUri = "-P:TILEDB_URI=${dbPath}"
//val dbUri = "TILEDB_URI=${dbPath}"
val args = arrayOf(dbUri)
val server = embeddedServer(Netty,port=8080) {
module(args)
}
server.start(true)
}
}
this is then processed in Application.kt via:
fun Application.module(args:Array<String>) {
install(DefaultHeaders)
install(CallLogging)
install(ContentNegotiation) {
json(Json {
prettyPrint = false
isLenient = true
encodeDefaults = true
})
}
// Setup routing. Individual endpoints create Kotlin Route extensions
// to handle processing REST requests.
routing {
// this method routes brapi/v2/
// Within apiRoute(), specific endpoint calls are handled
apiRoute(args)
}
}
We continue to pass this argument until it reaches the function that actually processes the data:
fun Route.samples(args:Array<String>) {
val samplesService = SamplesService
val tiledbURI = args[0].substringAfter("TILEDB_URI=").substringBefore(" ")
route("/samples") {
get("") {
call.respond(
SampleListResponse(
Metadata(),
SampleListResponseResult(samplesService.lcjAllTaxaNames(tiledbURI).toTypedArray())
)
)
}
}
}
If there is a better way to pass the argument value, please let me know, but this works for now.