jvmMain.it.unibo.alchemist.boundary.monitors.GraphQLMonitor.kt Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2010-2024, Danilo Pianini and contributors
* listed, for each module, in the respective subproject's build.gradle.kts file.
*
* This file is part of Alchemist, and is distributed under the terms of the
* GNU General Public License, with a linking exception,
* as described in the file LICENSE in the Alchemist distribution's top directory.
*/
package it.unibo.alchemist.boundary.monitors
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import it.unibo.alchemist.boundary.OutputMonitor
import it.unibo.alchemist.boundary.graphql.monitor.EnvironmentSubscriptionMonitor
import it.unibo.alchemist.boundary.graphql.server.modules.graphQLModule
import it.unibo.alchemist.boundary.graphql.server.modules.graphQLRoutingModule
import it.unibo.alchemist.boundary.graphql.utils.DefaultGraphQLSettings
import it.unibo.alchemist.model.Environment
import it.unibo.alchemist.model.Position
import it.unibo.alchemist.model.Time
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
private val logger = LoggerFactory.getLogger(GraphQLMonitor::class.java)
/**
* An [OutputMonitor] observing the [environment] through a GraphQL server listening on [host]:[port].
* The server is started in a new coroutine on the [serverDispatcher] dispatcher.
* By default, the server is stopped after the simulation terminates.
* This behavior can be changed by setting [teardownOnSimulationTermination] to false.
*/
class GraphQLMonitor> @JvmOverloads constructor(
val environment: Environment,
val host: String = DefaultGraphQLSettings.DEFAULT_HOST,
val port: Int = DefaultGraphQLSettings.DEFAULT_PORT,
val teardownOnSimulationTermination: Boolean = true,
private val serverDispatcher: CoroutineDispatcher = Dispatchers.Default,
) : OutputMonitor {
private val subscriptionMonitor = EnvironmentSubscriptionMonitor()
private lateinit var server: ApplicationEngine
override fun initialized(environment: Environment) {
environment.simulation.addOutputMonitor(subscriptionMonitor)
server = makeServer()
val mutex = java.util.concurrent.Semaphore(0)
Thread(
{
runBlocking {
launch(serverDispatcher) {
mutex.release()
server.start(wait = true)
}
}
},
"alchemist-graphql-server@$host:$port",
).start()
runBlocking {
logger.info("Starting GraphQL server at $host:${server.resolvedConnectors().first().port}")
}
mutex.acquireUninterruptibly()
}
override fun finished(environment: Environment, time: Time, step: Long) {
if (teardownOnSimulationTermination) {
server.stop()
}
}
private fun makeServer(): ApplicationEngine =
embeddedServer(
Netty,
port = port,
host = host,
module = {
graphQLModule([email protected])
graphQLRoutingModule()
},
)
}