Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ dependencies {
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.15.1")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.1")

implementation("com.google.flogger:flogger:0.9")
implementation("com.google.flogger:flogger-system-backend:0.9")

implementation("com.google.guava:guava:33.0.0-jre")

implementation("com.google.protobuf:protobuf-java:4.32.0")
Expand All @@ -73,11 +76,10 @@ dependencies {
implementation("jakarta.annotation:jakarta.annotation-api:3.0.0")

implementation("org.apache.commons:commons-jexl3:3.3")
implementation("org.apache.commons:commons-lang3:3.19.0")

implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1")

implementation("org.apache.logging.log4j:log4j-core:2.23.1")

implementation("org.glassfish.expressly:expressly:5.0.0")

implementation("org.hibernate.validator:hibernate-validator:8.0.1.Final")
Expand Down
66 changes: 29 additions & 37 deletions src/main/java/at/ac/uibk/dps/cirrina/cirrina/Cirrina.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,88 +7,80 @@ import at.ac.uibk.dps.cirrina.execution.`object`.event.NatsEventHandler
import at.ac.uibk.dps.cirrina.execution.service.RandomServiceImplementationSelector
import at.ac.uibk.dps.cirrina.execution.service.ServiceImplementationBuilder
import at.ac.uibk.dps.cirrina.io.parsing.CsmParser
import com.google.common.flogger.FluentLogger
import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk
import java.net.URI
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.core.LoggerContext

private val logger: FluentLogger = FluentLogger.forEnclosingClass()

/** Cirrina entry class. */
class Cirrina {
companion object {
const val NATS_CONNECTION_TIMEOUT = 60000L

val logger = LogManager.getLogger()

init {
setupLogging()
setupnewHealthService()
}

// Set up the logger.
private fun setupLogging() {
(LogManager.getContext(false) as LoggerContext).apply {
configuration.getLoggerConfig(logger.name).level = Level.INFO
updateLoggers()
}
}

// Create the health service.
private fun setupnewHealthService(): HealthService =
logger.atFine().log("Starting health service")
runCatching { HealthService(EnvironmentVariables.healthPort.get()) }
.getOrElse { e -> throw RuntimeException("Failed to start the health service: $e", e) }
.getOrElse { e -> logger.atSevere().withCause(e).log("Could not start the health service") }
}
}

/** Run Cirrina as configured. */
fun run() {
try {
logger.atFine().log("Creating the event handler")
newEventHandler()
.apply {
if (this is NatsEventHandler) {
logger.atFine().log("Awaiting connection to NATS as the event handler")
awaitInitialConnection(NATS_CONNECTION_TIMEOUT)
}
}
.use { eventHandler ->
logger.atFiner().log("Subscribing to event sources")
eventHandler.subscribe(NatsEventHandler.GLOBAL_SOURCE, "*")
eventHandler.subscribe(NatsEventHandler.PERIPHERAL_SOURCE, "*")

logger.atFine().log("Creating the persistent context")
newPersistentContext()
.apply {
if (this is NatsContext) {
logger.atFine().log("Awaiting connection to NATS as the persistent context")
awaitInitialConnection(NATS_CONNECTION_TIMEOUT)
}
}
.use { persistentContext ->
val openTelemetry = getOpenTelemetry()
val serviceImplementationSelector =
RandomServiceImplementationSelector(
ServiceImplementationBuilder.from(
CsmParser.parseServiceImplementationBindings(
URI(EnvironmentVariables.serviceBindingsPath.get())
)
.bindings
)
.build()
)

Runtime(
logger.atFine().log("Loading service implementation bindings")
var serviceImplementationBindings =
CsmParser.parseServiceImplementationBindings(
URI(EnvironmentVariables.serviceBindingsPath.get())
)
.bindings

logger.atFine().log("Creating the runtime")
val runtime =
Runtime(
URI(EnvironmentVariables.appPath.get()),
EnvironmentVariables.instantiate.get(),
openTelemetry,
serviceImplementationSelector,
RandomServiceImplementationSelector(
ServiceImplementationBuilder.from(serviceImplementationBindings).build()
),
eventHandler,
persistentContext,
)
.run()

logger.info("Done running")
logger.atFine().log("Running the runtime")
runtime.run()
}
}
} catch (e: EnvironmentVariableError) {
logger.error("There is an error in the current configuration", e)
} catch (e: ConfigurationError) {
logger.atSevere().withCause(e).log("There is an error in the current configuration")
} catch (e: Exception) {
logger.error("There was an unknown in the runtime execution", e)
logger.atSevere().withCause(e).log("There was an unknown in the runtime execution")
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/main/java/at/ac/uibk/dps/cirrina/cirrina/Error.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package at.ac.uibk.dps.cirrina.cirrina

sealed class EnvironmentVariableError(message: String) : RuntimeException(message) {
sealed class ConfigurationError(message: String) : RuntimeException(message) {
class Unknown(what: String, `is`: Any) : ConfigurationError("Unknown $what which is '$`is`'")
}

sealed class EnvironmentVariableError(message: String) : ConfigurationError(message) {
class Missing(name: String) :
EnvironmentVariableError("Missing required environment variable '$name'")

Expand All @@ -9,7 +13,3 @@ sealed class EnvironmentVariableError(message: String) : RuntimeException(messag
"Invalid value for environment variable '$name', the value is '$value', allowed values are '${allowed}'"
)
}

sealed class ConfigurationError(message: String) : RuntimeException(message) {
class Unknown(what: String, `is`: Any) : ConfigurationError("Unknown $what which is '$`is`'")
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public HealthService(int port) {
* Stops the health service.
*/
@Override
public void close() throws Exception {
public void close() {
httpServer.stop(0);
}
}
47 changes: 26 additions & 21 deletions src/main/java/at/ac/uibk/dps/cirrina/cirrina/Runtime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import at.ac.uibk.dps.cirrina.execution.`object`.statemachine.StateMachine
import at.ac.uibk.dps.cirrina.execution.service.ServiceImplementationSelector
import at.ac.uibk.dps.cirrina.io.parsing.CsmParser
import at.ac.uibk.dps.cirrina.utils.Id
import com.google.common.flogger.FluentLogger
import io.opentelemetry.api.OpenTelemetry
import java.io.IOException
import java.net.URI
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger

private val logger: FluentLogger = FluentLogger.forEnclosingClass()

/**
* Runtime for executing state machines defined in a Cirrina CSML project.
Expand All @@ -37,11 +37,7 @@ class Runtime(
val eventHandler: EventHandler,
val persistentContext: Context,
) {
companion object {
private val logger: Logger = LogManager.getLogger()
}

// Instantiated state machines.
/** Instantiated state machines. */
val stateMachines: List<StateMachine>

/** Top-level extent. */
Expand All @@ -51,24 +47,33 @@ class Runtime(
val collaborativeStateMachineClass =
CollaborativeStateMachineClassBuilder.from(CsmParser.parseCsml(main)).build()

logger.atFine().log("Creating persistent context variables")
collaborativeStateMachineClass.persistentContextVariables.forEach { variable ->
try {
logger.info("Creating persistent context variable '{}'", variable.name())
persistentContext.create(variable.name(), variable.value())
} catch (_: IOException) {
logger.info(
"Did not create persistent context variable '{}', does it already exist?",
variable.name(),
)
}
runCatching {
logger.atFiner().log("Creating persistent context variable '${variable.name()}'")
persistentContext.create(variable.name(), variable.value())
}
.onFailure { _ ->
logger
.atWarning()
.log(
"Did not create persistent context variable '${variable.name()}', does it already exist?"
)
}
}

stateMachines =
stateMachineNames
.map { name ->
collaborativeStateMachineClass.findStateMachineClassByName(name).orElseThrow {
IllegalArgumentException("No state machine found with name: $name")
}
.mapNotNull { name ->
collaborativeStateMachineClass.findStateMachineClassByName(name)
?: run {
logger
.atWarning()
.log(
"A state machine with name '$name' could not be instantiated, because it does not exist"
)
null
}
}
.flatMap { buildInstances(it, null) }
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package at.ac.uibk.dps.cirrina.classes.collaborativestatemachine

import at.ac.uibk.dps.cirrina.classes.statemachine.StateMachineClass
import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariable
import at.ac.uibk.dps.cirrina.execution.`object`.event.Event
import org.jgrapht.graph.DirectedPseudograph

/**
* Collaborative state machine class, describes the structure of a collaborative state machine.
*
* A collaborative state machine is a graph with state machine classes as vertices and events as
* edges. An edge in the collaborative state machine graph represents how a state machine can be
* activated by another state machine based on an event.
*/
class CollaborativeStateMachineClass
internal constructor(
// The name of the collaborative state machine.
private val name: String,

/** The collection of persistent context variables. */
val persistentContextVariables: MutableList<ContextVariable>,
) : DirectedPseudograph<StateMachineClass, Event>(Event::class.java) {

/**
* Returns a state machine class by its name.
*
* @param name Name of the state machine to return.
* @return The state machine with the supplied name or null if none or multiple are found.
*/
fun findStateMachineClassByName(name: String): StateMachineClass? {
val matches = vertexSet().filter { it.name == name }
return matches.singleOrNull()
}

/** Returns the collection of state machine classes. */
val stateMachineClasses: List<StateMachineClass>
get() = vertexSet().toList()

override fun toString(): String = name
}
Loading