From 4a5a05922ef1fa47fb17f56ae41ad3893e155601 Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Tue, 3 Feb 2026 10:38:28 +0100 Subject: [PATCH 01/15] feat: add service invocation output variables --- src/main/resources/pkl/csm/csml.pkl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/resources/pkl/csm/csml.pkl b/src/main/resources/pkl/csm/csml.pkl index 57275ad5..e9f92d4d 100644 --- a/src/main/resources/pkl/csm/csml.pkl +++ b/src/main/resources/pkl/csm/csml.pkl @@ -99,6 +99,7 @@ class InvokeDescription extends ActionDescription { type: InvocationType mode: InvocationMode = "remote" input: Context + output: Listing raises: Listing } @@ -126,6 +127,7 @@ class CaseDescription { } class MatchDescription extends ActionDescription { + value: Expression cases: Listing default: ActionDescription? } @@ -134,6 +136,10 @@ class LogDescription extends ActionDescription { message: Expression } +class ContextVariableReferenceDescription { + reference: String +} + typealias EventTopic = String(matches(Regex(#"^[a-zA-Z_]\w*$"#))) typealias EventChannel = "internal"|"external"|"global"|"peripheral" From ebbc2214fd9fe2c8746cbec85e389617f24789bd Mon Sep 17 00:00:00 2001 From: Marlon Etheredge <80909536+Frnd-me@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:48:19 +0100 Subject: [PATCH 02/15] refactor: refactoring objects (#85) --- .../execution/command/ActionCommand.kt | 165 ++++++++++++++++++ .../command/CommandExecutionContext.kt | 15 ++ .../cirrina/execution/object/action/Action.kt | 109 ++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionCommand.kt create mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/command/CommandExecutionContext.kt create mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/Action.kt diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionCommand.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionCommand.kt new file mode 100644 index 00000000..e5585c20 --- /dev/null +++ b/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionCommand.kt @@ -0,0 +1,165 @@ +package at.ac.uibk.dps.cirrina.execution.command + +import at.ac.uibk.dps.cirrina.csm.Csml.EventChannel +import at.ac.uibk.dps.cirrina.execution.`object`.action.* +import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariable +import at.ac.uibk.dps.cirrina.execution.`object`.context.Extent +import at.ac.uibk.dps.cirrina.execution.service.ServiceImplementation +import io.micrometer.core.instrument.MeterRegistry +import kotlinx.coroutines.launch +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {} + +interface ActionCommandFactory { + fun create(action: Action, commandExecutionContext: CommandExecutionContext): ActionCommand +} + +abstract class ActionCommand( + protected val commandExecutionContext: CommandExecutionContext, + protected val commandFactory: ActionCommandFactory, + protected val meterRegistry: MeterRegistry, +) { + abstract fun execute(): List +} + +class ActionCommandFactoryImpl(private val meterRegistry: MeterRegistry) : ActionCommandFactory { + override fun create( + action: Action, + commandExecutionContext: CommandExecutionContext, + ): ActionCommand = + when (action) { + is EvalAction -> ActionEvalCommand(action, commandExecutionContext, this, meterRegistry) + is InvokeAction -> ActionInvokeCommand(action, commandExecutionContext, this, meterRegistry) + is MatchAction -> ActionMatchCommand(action, commandExecutionContext, this, meterRegistry) + is RaiseAction -> ActionRaiseCommand(action, commandExecutionContext, this, meterRegistry) + is TimeoutAction -> ActionTimeoutCommand(action, commandExecutionContext, this, meterRegistry) + is TimeoutResetAction -> + ActionTimeoutResetCommand(action, commandExecutionContext, this, meterRegistry) + else -> error("Unexpected action type: ${action::class.simpleName}") + } +} + +class ActionEvalCommand +internal constructor( + private val evalAction: EvalAction, + commandExecutionContext: CommandExecutionContext, + commandFactory: ActionCommandFactory, + meterRegistry: MeterRegistry, +) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { + + override fun execute(): List = + evalAction.expression.execute(commandExecutionContext.scope.extent).run { emptyList() } +} + +class ActionInvokeCommand +internal constructor( + private val invokeAction: InvokeAction, + commandExecutionContext: CommandExecutionContext, + commandFactory: ActionCommandFactory, + meterRegistry: MeterRegistry, +) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { + + override fun execute(): List { + val service = selectServiceImplementation() + val input = prepareInput(commandExecutionContext.scope.extent) + + commandExecutionContext.coroutineScope.launch { + runCatching { service.invoke(input) } + .onSuccess { raiseEvents(it) } + .onFailure { logger.error(it) { "service invocation failed" } } + } + + return emptyList() + } + + private fun selectServiceImplementation(): ServiceImplementation = + commandExecutionContext.serviceImplementationSelector.select( + invokeAction.type, + invokeAction.mode, + ) ?: error("no service implementation found for type '${invokeAction.type}'") + + private fun prepareInput(extent: Extent): List = + invokeAction.input.map { it.evaluate(extent) } + + private fun raiseEvents(output: List) { + invokeAction.raises.forEach { eventTemplate -> + val event = eventTemplate.copy(data = output) + val handler = + when (event.channel) { + EventChannel.INTERNAL -> + commandExecutionContext.stateMachineEventHandler::propagateToParent + else -> commandExecutionContext.stateMachineEventHandler::sendEvent + } + handler(event) + } + } +} + +class ActionMatchCommand +internal constructor( + private val matchAction: MatchAction, + commandExecutionContext: CommandExecutionContext, + commandFactory: ActionCommandFactory, + meterRegistry: MeterRegistry, +) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { + + override fun execute(): List { + val extent = commandExecutionContext.scope.extent + val matchValue = matchAction.value.execute(extent) + + val selectedActions = + matchAction.cases.entries + .filter { (expression, _) -> expression.execute(extent) == matchValue } + .map { it.value } + .ifEmpty { listOfNotNull(matchAction.default) } + + return selectedActions.map { commandFactory.create(it, commandExecutionContext) } + } +} + +class ActionRaiseCommand +internal constructor( + private val raiseAction: RaiseAction, + commandExecutionContext: CommandExecutionContext, + commandFactory: ActionCommandFactory, + meterRegistry: MeterRegistry, +) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { + + override fun execute(): List { + val event = + raiseAction.event.evaluateData(commandExecutionContext.scope.extent).run { + val target = raiseAction.target?.execute(commandExecutionContext.scope.extent) as? String + if (target != null) copy(target = target) else this + } + + with(commandExecutionContext.stateMachineEventHandler) { + if (event.channel == EventChannel.INTERNAL) propagateToParent(event) else sendEvent(event) + } + + return emptyList() + } +} + +class ActionTimeoutCommand +internal constructor( + private val timeoutAction: TimeoutAction, + commandExecutionContext: CommandExecutionContext, + commandFactory: ActionCommandFactory, + meterRegistry: MeterRegistry, +) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { + + override fun execute(): List = + listOf(commandFactory.create(timeoutAction.`do`, commandExecutionContext)) +} + +class ActionTimeoutResetCommand +internal constructor( + val timeoutResetAction: TimeoutResetAction, + commandExecutionContext: CommandExecutionContext, + commandFactory: ActionCommandFactory, + meterRegistry: MeterRegistry, +) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { + + override fun execute(): List = emptyList() +} diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/command/CommandExecutionContext.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/command/CommandExecutionContext.kt new file mode 100644 index 00000000..0b06a22a --- /dev/null +++ b/src/main/java/at/ac/uibk/dps/cirrina/execution/command/CommandExecutionContext.kt @@ -0,0 +1,15 @@ +package at.ac.uibk.dps.cirrina.execution.command + +import at.ac.uibk.dps.cirrina.execution.`object`.event.Event +import at.ac.uibk.dps.cirrina.execution.`object`.statemachine.StateMachine +import at.ac.uibk.dps.cirrina.execution.service.ServiceImplementationSelector +import kotlinx.coroutines.CoroutineScope + +data class CommandExecutionContext( + val scope: Scope, + val serviceImplementationSelector: ServiceImplementationSelector, + val stateMachineEventHandler: StateMachine.StateMachineEventHandler, + val coroutineScope: CoroutineScope, + val raisingEvent: Event? = null, + val isWhile: Boolean = false, +) diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/Action.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/Action.kt new file mode 100644 index 00000000..bbfa628d --- /dev/null +++ b/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/Action.kt @@ -0,0 +1,109 @@ +package at.ac.uibk.dps.cirrina.execution.`object`.action + +import at.ac.uibk.dps.cirrina.csm.Csml.* +import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariable +import at.ac.uibk.dps.cirrina.execution.`object`.event.Event +import at.ac.uibk.dps.cirrina.execution.`object`.expression.Expression + +interface Action { + + companion object { + fun create(description: ActionDescription, name: String? = null): Result = runCatching { + when (description) { + is EvalDescription -> Expression.from(description.expression).map(::EvalAction).getOrThrow() + + is InvokeDescription -> + InvokeAction( + description.type, + description.mode, + buildVariables(description.input).getOrThrow(), + buildEvents(description.raises).getOrThrow(), + ) + + is MatchDescription -> + MatchAction( + Expression.from(description.value).getOrThrow(), + description.cases.associate { + Expression.from(it.of).getOrThrow() to create(it.then).getOrThrow() + }, + description.default?.let { create(it).getOrThrow() }, + ) + + is RaiseDescription -> + RaiseAction( + Event.from(description.event).getOrThrow(), + description.target?.let { Expression.from(it).getOrThrow() }, + ) + + is TimeoutDescription -> + TimeoutAction( + name ?: error("timeout action name required"), + Expression.from(description.delay).getOrThrow(), + create(description.`do`).getOrThrow(), + ) + + is ResetDescription -> TimeoutResetAction(description.name) + + else -> error("unknown type: ${description.javaClass.simpleName}") + } + } + + private fun buildVariables(context: Map) = runCatching { + context.map { (k, v) -> + val expression = Expression.from(v).getOrThrow() + ContextVariable.lazy(k, expression) + } + } + + private fun buildEvents(events: List) = runCatching { + events.map { Event.from(it).getOrThrow() } + } + } +} + +interface EventRaisingAction : Action { + + fun raises(): List +} + +class EvalAction internal constructor(val expression: Expression) : Action + +class InvokeAction +internal constructor( + val type: String, + val mode: InvocationMode, + val input: List, + val raises: List, +) : EventRaisingAction { + + override fun raises(): List = raises +} + +class MatchAction +internal constructor( + val value: Expression, + val cases: Map, + val default: Action? = null, +) : EventRaisingAction { + + override fun raises(): List = + (cases.values + listOfNotNull(default)).filterIsInstance().flatMap { + it.raises() + } +} + +class RaiseAction internal constructor(val event: Event, val target: Expression?) : + EventRaisingAction { + + override fun raises(): List = listOf(event) +} + +class TimeoutAction +internal constructor(val name: String, val delay: Expression, val `do`: Action) : + EventRaisingAction { + + override fun raises(): List = + (`do` as? RaiseAction)?.let { listOf(it.event) } ?: emptyList() +} + +class TimeoutResetAction internal constructor(val action: String) : Action From a5362239c8070e98ec08253a2f0d5138caaac847 Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Tue, 3 Feb 2026 10:38:28 +0100 Subject: [PATCH 03/15] feat: add service invocation output variables # Conflicts: # Dockerfile # src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionInvokeCommand.kt # src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/ActionBuilder.kt # src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/InvokeAction.kt --- Dockerfile | 4 +- .../execution/command/ActionInvokeCommand.kt | 82 +++++++++++++ .../execution/object/action/ActionBuilder.kt | 112 ++++++++++++++++++ .../execution/object/action/InvokeAction.kt | 33 ++++++ 4 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionInvokeCommand.kt create mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/ActionBuilder.kt create mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/InvokeAction.kt diff --git a/Dockerfile b/Dockerfile index cf9d43fb..03ea4c44 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,8 +13,8 @@ RUN gradle distZip RUN unzip build/distributions/cirrina.zip -d /tmp \ && chmod +x /tmp/cirrina/bin/cirrina -FROM gcr.io/distroless/java25-debian13 AS runtime +FROM eclipse-temurin:25-jdk-jammy AS runtime COPY --from=build /tmp/cirrina /opt/cirrina -ENTRYPOINT ["java", "-cp", "/opt/cirrina/lib/*", "at.ac.uibk.dps.cirrina.CirrinaKt"] \ No newline at end of file +ENTRYPOINT ["java", "-cp", "/opt/cirrina/lib/*", "at.ac.uibk.dps.cirrina.cirrina.CirrinaKt"] \ No newline at end of file diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionInvokeCommand.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionInvokeCommand.kt new file mode 100644 index 00000000..a2c71d21 --- /dev/null +++ b/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionInvokeCommand.kt @@ -0,0 +1,82 @@ +package at.ac.uibk.dps.cirrina.execution.command + +import at.ac.uibk.dps.cirrina.csm.Csml.EventChannel +import at.ac.uibk.dps.cirrina.execution.`object`.action.InvokeAction +import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariable +import at.ac.uibk.dps.cirrina.execution.`object`.context.Extent +import at.ac.uibk.dps.cirrina.execution.service.ServiceImplementation +import io.micrometer.core.instrument.MeterRegistry +import kotlinx.coroutines.launch +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {} + +/** + * A command responsible for invoking an external service as defined by an [InvokeAction] within the + * provided [executionContext]. + * + * This command selects an appropriate service implementation, prepares the required input + * variables, and triggers the service invocation asynchronously. + * + * @property invokeAction the definition of the service call and associated events. + * @property executionContext the context in which the invocation occurs. + * @property meterRegistry the registry used for collecting metrics. + */ +class ActionInvokeCommand +internal constructor( + private val invokeAction: InvokeAction, + executionContext: ExecutionContext, + meterRegistry: MeterRegistry, +) : ActionCommand(executionContext, meterRegistry) { + + /** + * Executes the service invocation logic. + * + * @return an empty list of [ActionCommand]s. + * @throws Exception if the command execution fails due to an internal error. + */ + override fun execute(): List = + selectServiceImplementation() + .let { service -> service to prepareInput(executionContext.scope.extent) } + .run { + executionContext.coroutineScope.launch { + runCatching { first.invoke(second) } + .onSuccess { + assignServiceOutput(it, executionContext.scope.extent) + raiseEvents(it) + } + .onFailure { logger.error(it) { "service invocation failed" } } + } + emptyList() + } + + private fun selectServiceImplementation(): ServiceImplementation = + executionContext.serviceImplementationSelector.select(invokeAction.type, invokeAction.mode) + ?: error("no service implementation found for type '${invokeAction.type}'") + + private fun prepareInput(extent: Extent): List = + invokeAction.input.map { it.evaluate(extent) } + + private fun assignServiceOutput(output: List, extent: Extent) = + invokeAction.output.forEach { variable -> + output + .firstOrNull { it.name.equals(variable.reference) } + ?.let { + runCatching { extent.setOrCreate(variable.reference, it.value) } + .onFailure { e -> + logger.warn("Failed to assign service output to variable '${variable.reference}'", e) + } + } + ?: logger.warn("Service output does not contain expected variable '${variable.reference}'") + } + + private fun raiseEvents(output: List) = + invokeAction.raises + .map { it.withData(output) } + .forEach { event -> + when (event.channel) { + EventChannel.INTERNAL -> executionContext.stateMachineEventHandler::propagateToParent + else -> executionContext.stateMachineEventHandler::sendEvent + }.invoke(event) + } +} diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/ActionBuilder.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/ActionBuilder.kt new file mode 100644 index 00000000..49fac907 --- /dev/null +++ b/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/ActionBuilder.kt @@ -0,0 +1,112 @@ +package at.ac.uibk.dps.cirrina.execution.`object`.action + +import at.ac.uibk.dps.cirrina.csm.Csml.* +import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariable +import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariableBuilder +import at.ac.uibk.dps.cirrina.execution.`object`.event.Event +import at.ac.uibk.dps.cirrina.execution.`object`.event.EventBuilder +import at.ac.uibk.dps.cirrina.execution.`object`.expression.Expression +import at.ac.uibk.dps.cirrina.execution.`object`.expression.ExpressionBuilder + +/** + * A builder responsible for transforming [ActionDescription]s into executable [Action] objects. + * + * @property actionDescription the source description used to construct the action. + * @property name an optional identifier, required specifically for [TimeoutAction] instances. + */ +class ActionBuilder +private constructor( + private val actionDescription: ActionDescription, + private val name: String? = null, +) { + + /** + * Associates a name with the action being built. Primarily used for tracking and resetting + * [TimeoutAction]s. + */ + fun withName(name: String): ActionBuilder = ActionBuilder(actionDescription, name) + + /** + * Attempts to build an [Action] instance based on the provided description. + * + * @return a [Result] containing the built action or an error if validation fails. + */ + fun build(): Result = + when (actionDescription) { + is EvalDescription -> buildEvalAction(actionDescription) + is InvokeDescription -> buildInvokeAction(actionDescription) + is MatchDescription -> buildMatchAction(actionDescription) + is RaiseDescription -> buildRaiseAction(actionDescription) + is TimeoutDescription -> buildTimeoutAction(actionDescription) + is ResetDescription -> buildResetAction(actionDescription) + else -> + Result.failure( + UnsupportedOperationException( + "Unknown action type: ${actionDescription.javaClass.simpleName}" + ) + ) + } + + private fun buildEvalAction(eval: EvalDescription): Result = + ExpressionBuilder.from(eval.expression).build().map(::EvalAction) + + private fun buildInvokeAction(invoke: InvokeDescription): Result = runCatching { + val input = buildVariableList(invoke.input).getOrThrow() + val raises = buildEvents(invoke.raises).getOrThrow() + InvokeAction(invoke.type, invoke.mode, input, invoke.output, raises) + } + + private fun buildMatchAction(match: MatchDescription): Result = runCatching { + val valueExpression = ExpressionBuilder.from(match.value).build().getOrThrow() + val cases = buildCases(match.cases).getOrThrow() + val default = match.default?.let { from(it).build().getOrThrow() } + MatchAction(valueExpression, cases, default) + } + + private fun buildRaiseAction(raise: RaiseDescription): Result = runCatching { + val target = raise.target?.let { ExpressionBuilder.from(raise.target).build().getOrThrow() } + val event = EventBuilder.from(raise.event).build().getOrThrow() + RaiseAction(event, target) + } + + private fun buildTimeoutAction(timeout: TimeoutDescription): Result = runCatching { + val actionName = name ?: error("timeout action name is required") + val delay = ExpressionBuilder.from(timeout.delay).build().getOrThrow() + val todo = from(timeout.`do`).build().getOrThrow() + + TimeoutAction(actionName, delay, todo) + } + + private fun buildResetAction(reset: ResetDescription): Result = + Result.success(TimeoutResetAction(reset.name)) + + private fun buildCases(cases: List): Result> = + runCatching { + cases.associate { case -> + val of = ExpressionBuilder.from(case.of).build().getOrThrow() + val then = from(case.then).build().getOrThrow() + of to then + } + } + + companion object { + /** Creates a new [ActionBuilder] starting from the given [ActionDescription]. */ + fun from(actionDescription: ActionDescription): ActionBuilder = ActionBuilder(actionDescription) + + private fun buildVariableList(context: Map): Result> = + runCatching { + context.map { (key, value) -> + ContextVariableBuilder.empty() + .name(key) + .expression(ExpressionBuilder.from(value).build().getOrThrow()) + .build() + .getOrThrow() + } + } + + private fun buildEvents(eventDescriptions: List): Result> = + runCatching { + eventDescriptions.map { EventBuilder.from(it).build().getOrThrow() } + } + } +} diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/InvokeAction.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/InvokeAction.kt new file mode 100644 index 00000000..38f74758 --- /dev/null +++ b/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/InvokeAction.kt @@ -0,0 +1,33 @@ +package at.ac.uibk.dps.cirrina.execution.`object`.action + +import at.ac.uibk.dps.cirrina.csm.Csml +import at.ac.uibk.dps.cirrina.csm.Csml.InvocationMode +import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariable +import at.ac.uibk.dps.cirrina.execution.`object`.event.Event + +/** + * An action that invokes a specific service type. + * + * @property type the identifier of the service to be invoked. + * @property mode the [InvocationMode] for the service call. + * @property input the list of context variables passed as input to the service. + * @property raises the list of events to be raised upon successful service completion. + */ +class InvokeAction +internal constructor( + val type: String, + val mode: InvocationMode, + val input: List, + val output: List, + val raises: List, +) : Action(), EventRaisingAction { + + /** + * Returns the list of [Event]s to be triggered by this action. + * + * @return the list of events associated with the completion of this invocation. + */ + override fun raises(): List = raises + + fun outputs(): List = output +} From 7e1361a6b29d0e167e20df7ac85770c17eced54e Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Mon, 9 Feb 2026 10:47:56 +0100 Subject: [PATCH 04/15] feat: add service invocation output variables --- .../execution/command/ActionInvokeCommand.kt | 82 ------------- .../execution/object/action/ActionBuilder.kt | 112 ------------------ .../execution/object/action/InvokeAction.kt | 33 ------ .../dps/cirrina/execution/object/Action.kt | 3 + .../cirrina/execution/object/ActionCommand.kt | 18 ++- 5 files changed, 20 insertions(+), 228 deletions(-) delete mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionInvokeCommand.kt delete mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/ActionBuilder.kt delete mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/InvokeAction.kt diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionInvokeCommand.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionInvokeCommand.kt deleted file mode 100644 index a2c71d21..00000000 --- a/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionInvokeCommand.kt +++ /dev/null @@ -1,82 +0,0 @@ -package at.ac.uibk.dps.cirrina.execution.command - -import at.ac.uibk.dps.cirrina.csm.Csml.EventChannel -import at.ac.uibk.dps.cirrina.execution.`object`.action.InvokeAction -import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariable -import at.ac.uibk.dps.cirrina.execution.`object`.context.Extent -import at.ac.uibk.dps.cirrina.execution.service.ServiceImplementation -import io.micrometer.core.instrument.MeterRegistry -import kotlinx.coroutines.launch -import mu.KotlinLogging - -private val logger = KotlinLogging.logger {} - -/** - * A command responsible for invoking an external service as defined by an [InvokeAction] within the - * provided [executionContext]. - * - * This command selects an appropriate service implementation, prepares the required input - * variables, and triggers the service invocation asynchronously. - * - * @property invokeAction the definition of the service call and associated events. - * @property executionContext the context in which the invocation occurs. - * @property meterRegistry the registry used for collecting metrics. - */ -class ActionInvokeCommand -internal constructor( - private val invokeAction: InvokeAction, - executionContext: ExecutionContext, - meterRegistry: MeterRegistry, -) : ActionCommand(executionContext, meterRegistry) { - - /** - * Executes the service invocation logic. - * - * @return an empty list of [ActionCommand]s. - * @throws Exception if the command execution fails due to an internal error. - */ - override fun execute(): List = - selectServiceImplementation() - .let { service -> service to prepareInput(executionContext.scope.extent) } - .run { - executionContext.coroutineScope.launch { - runCatching { first.invoke(second) } - .onSuccess { - assignServiceOutput(it, executionContext.scope.extent) - raiseEvents(it) - } - .onFailure { logger.error(it) { "service invocation failed" } } - } - emptyList() - } - - private fun selectServiceImplementation(): ServiceImplementation = - executionContext.serviceImplementationSelector.select(invokeAction.type, invokeAction.mode) - ?: error("no service implementation found for type '${invokeAction.type}'") - - private fun prepareInput(extent: Extent): List = - invokeAction.input.map { it.evaluate(extent) } - - private fun assignServiceOutput(output: List, extent: Extent) = - invokeAction.output.forEach { variable -> - output - .firstOrNull { it.name.equals(variable.reference) } - ?.let { - runCatching { extent.setOrCreate(variable.reference, it.value) } - .onFailure { e -> - logger.warn("Failed to assign service output to variable '${variable.reference}'", e) - } - } - ?: logger.warn("Service output does not contain expected variable '${variable.reference}'") - } - - private fun raiseEvents(output: List) = - invokeAction.raises - .map { it.withData(output) } - .forEach { event -> - when (event.channel) { - EventChannel.INTERNAL -> executionContext.stateMachineEventHandler::propagateToParent - else -> executionContext.stateMachineEventHandler::sendEvent - }.invoke(event) - } -} diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/ActionBuilder.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/ActionBuilder.kt deleted file mode 100644 index 49fac907..00000000 --- a/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/ActionBuilder.kt +++ /dev/null @@ -1,112 +0,0 @@ -package at.ac.uibk.dps.cirrina.execution.`object`.action - -import at.ac.uibk.dps.cirrina.csm.Csml.* -import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariable -import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariableBuilder -import at.ac.uibk.dps.cirrina.execution.`object`.event.Event -import at.ac.uibk.dps.cirrina.execution.`object`.event.EventBuilder -import at.ac.uibk.dps.cirrina.execution.`object`.expression.Expression -import at.ac.uibk.dps.cirrina.execution.`object`.expression.ExpressionBuilder - -/** - * A builder responsible for transforming [ActionDescription]s into executable [Action] objects. - * - * @property actionDescription the source description used to construct the action. - * @property name an optional identifier, required specifically for [TimeoutAction] instances. - */ -class ActionBuilder -private constructor( - private val actionDescription: ActionDescription, - private val name: String? = null, -) { - - /** - * Associates a name with the action being built. Primarily used for tracking and resetting - * [TimeoutAction]s. - */ - fun withName(name: String): ActionBuilder = ActionBuilder(actionDescription, name) - - /** - * Attempts to build an [Action] instance based on the provided description. - * - * @return a [Result] containing the built action or an error if validation fails. - */ - fun build(): Result = - when (actionDescription) { - is EvalDescription -> buildEvalAction(actionDescription) - is InvokeDescription -> buildInvokeAction(actionDescription) - is MatchDescription -> buildMatchAction(actionDescription) - is RaiseDescription -> buildRaiseAction(actionDescription) - is TimeoutDescription -> buildTimeoutAction(actionDescription) - is ResetDescription -> buildResetAction(actionDescription) - else -> - Result.failure( - UnsupportedOperationException( - "Unknown action type: ${actionDescription.javaClass.simpleName}" - ) - ) - } - - private fun buildEvalAction(eval: EvalDescription): Result = - ExpressionBuilder.from(eval.expression).build().map(::EvalAction) - - private fun buildInvokeAction(invoke: InvokeDescription): Result = runCatching { - val input = buildVariableList(invoke.input).getOrThrow() - val raises = buildEvents(invoke.raises).getOrThrow() - InvokeAction(invoke.type, invoke.mode, input, invoke.output, raises) - } - - private fun buildMatchAction(match: MatchDescription): Result = runCatching { - val valueExpression = ExpressionBuilder.from(match.value).build().getOrThrow() - val cases = buildCases(match.cases).getOrThrow() - val default = match.default?.let { from(it).build().getOrThrow() } - MatchAction(valueExpression, cases, default) - } - - private fun buildRaiseAction(raise: RaiseDescription): Result = runCatching { - val target = raise.target?.let { ExpressionBuilder.from(raise.target).build().getOrThrow() } - val event = EventBuilder.from(raise.event).build().getOrThrow() - RaiseAction(event, target) - } - - private fun buildTimeoutAction(timeout: TimeoutDescription): Result = runCatching { - val actionName = name ?: error("timeout action name is required") - val delay = ExpressionBuilder.from(timeout.delay).build().getOrThrow() - val todo = from(timeout.`do`).build().getOrThrow() - - TimeoutAction(actionName, delay, todo) - } - - private fun buildResetAction(reset: ResetDescription): Result = - Result.success(TimeoutResetAction(reset.name)) - - private fun buildCases(cases: List): Result> = - runCatching { - cases.associate { case -> - val of = ExpressionBuilder.from(case.of).build().getOrThrow() - val then = from(case.then).build().getOrThrow() - of to then - } - } - - companion object { - /** Creates a new [ActionBuilder] starting from the given [ActionDescription]. */ - fun from(actionDescription: ActionDescription): ActionBuilder = ActionBuilder(actionDescription) - - private fun buildVariableList(context: Map): Result> = - runCatching { - context.map { (key, value) -> - ContextVariableBuilder.empty() - .name(key) - .expression(ExpressionBuilder.from(value).build().getOrThrow()) - .build() - .getOrThrow() - } - } - - private fun buildEvents(eventDescriptions: List): Result> = - runCatching { - eventDescriptions.map { EventBuilder.from(it).build().getOrThrow() } - } - } -} diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/InvokeAction.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/InvokeAction.kt deleted file mode 100644 index 38f74758..00000000 --- a/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/InvokeAction.kt +++ /dev/null @@ -1,33 +0,0 @@ -package at.ac.uibk.dps.cirrina.execution.`object`.action - -import at.ac.uibk.dps.cirrina.csm.Csml -import at.ac.uibk.dps.cirrina.csm.Csml.InvocationMode -import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariable -import at.ac.uibk.dps.cirrina.execution.`object`.event.Event - -/** - * An action that invokes a specific service type. - * - * @property type the identifier of the service to be invoked. - * @property mode the [InvocationMode] for the service call. - * @property input the list of context variables passed as input to the service. - * @property raises the list of events to be raised upon successful service completion. - */ -class InvokeAction -internal constructor( - val type: String, - val mode: InvocationMode, - val input: List, - val output: List, - val raises: List, -) : Action(), EventRaisingAction { - - /** - * Returns the list of [Event]s to be triggered by this action. - * - * @return the list of events associated with the completion of this invocation. - */ - override fun raises(): List = raises - - fun outputs(): List = output -} diff --git a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt index d63caa37..114918d8 100644 --- a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt +++ b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt @@ -1,5 +1,6 @@ package at.ac.uibk.dps.cirrina.execution.`object` +import at.ac.uibk.dps.cirrina.csm.Csml import at.ac.uibk.dps.cirrina.csm.Csml.* import org.jgrapht.graph.DefaultEdge import org.jgrapht.graph.SimpleDirectedGraph @@ -34,6 +35,7 @@ sealed interface Action { description.type, description.mode, buildVariables(description.input), + description.output, buildEvents(description.raises), ) @@ -88,6 +90,7 @@ internal constructor( val type: String, val mode: InvocationMode, val input: List, + val output: List, val raises: List, ) : EventRaisingAction { override fun raises(): List = raises diff --git a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt index 9eb4c1c1..e1f982f7 100644 --- a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt +++ b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt @@ -80,7 +80,10 @@ internal constructor( commandExecutionContext.coroutineScope.launch { runCatching { service.invoke(input) } - .onSuccess { raiseEvents(it) } + .onSuccess { + assignServiceOutput(it, commandExecutionContext.scope.extent) + raiseEvents(it) + } .onFailure { logger.error(it) { "service invocation failed" } } } @@ -96,6 +99,19 @@ internal constructor( private fun prepareInput(extent: Extent): List = invokeAction.input.map { it.evaluate(extent) } + private fun assignServiceOutput(output: List, extent: Extent) = + invokeAction.output.forEach { variable -> + output + .firstOrNull { it.name == variable.reference } + ?.let { + runCatching { extent.set(variable.reference, it.value) } + .onFailure { e -> + logger.warn("Failed to assign service output to variable '${variable.reference}'", e) + } + } + ?: logger.warn("Service output does not contain expected variable '${variable.reference}'") + } + private fun raiseEvents(output: List) { invokeAction.raises.forEach { eventTemplate -> val event = eventTemplate.copy(data = output) From 458bd9cf2540535cf6cdf6fc1ca9c1cb0653e71b Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Mon, 9 Feb 2026 11:16:24 +0100 Subject: [PATCH 05/15] chore: remove outdated files --- .../execution/command/ActionCommand.kt | 165 ------------------ .../command/CommandExecutionContext.kt | 15 -- .../cirrina/execution/object/action/Action.kt | 109 ------------ 3 files changed, 289 deletions(-) delete mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionCommand.kt delete mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/command/CommandExecutionContext.kt delete mode 100644 src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/Action.kt diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionCommand.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionCommand.kt deleted file mode 100644 index e5585c20..00000000 --- a/src/main/java/at/ac/uibk/dps/cirrina/execution/command/ActionCommand.kt +++ /dev/null @@ -1,165 +0,0 @@ -package at.ac.uibk.dps.cirrina.execution.command - -import at.ac.uibk.dps.cirrina.csm.Csml.EventChannel -import at.ac.uibk.dps.cirrina.execution.`object`.action.* -import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariable -import at.ac.uibk.dps.cirrina.execution.`object`.context.Extent -import at.ac.uibk.dps.cirrina.execution.service.ServiceImplementation -import io.micrometer.core.instrument.MeterRegistry -import kotlinx.coroutines.launch -import mu.KotlinLogging - -private val logger = KotlinLogging.logger {} - -interface ActionCommandFactory { - fun create(action: Action, commandExecutionContext: CommandExecutionContext): ActionCommand -} - -abstract class ActionCommand( - protected val commandExecutionContext: CommandExecutionContext, - protected val commandFactory: ActionCommandFactory, - protected val meterRegistry: MeterRegistry, -) { - abstract fun execute(): List -} - -class ActionCommandFactoryImpl(private val meterRegistry: MeterRegistry) : ActionCommandFactory { - override fun create( - action: Action, - commandExecutionContext: CommandExecutionContext, - ): ActionCommand = - when (action) { - is EvalAction -> ActionEvalCommand(action, commandExecutionContext, this, meterRegistry) - is InvokeAction -> ActionInvokeCommand(action, commandExecutionContext, this, meterRegistry) - is MatchAction -> ActionMatchCommand(action, commandExecutionContext, this, meterRegistry) - is RaiseAction -> ActionRaiseCommand(action, commandExecutionContext, this, meterRegistry) - is TimeoutAction -> ActionTimeoutCommand(action, commandExecutionContext, this, meterRegistry) - is TimeoutResetAction -> - ActionTimeoutResetCommand(action, commandExecutionContext, this, meterRegistry) - else -> error("Unexpected action type: ${action::class.simpleName}") - } -} - -class ActionEvalCommand -internal constructor( - private val evalAction: EvalAction, - commandExecutionContext: CommandExecutionContext, - commandFactory: ActionCommandFactory, - meterRegistry: MeterRegistry, -) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { - - override fun execute(): List = - evalAction.expression.execute(commandExecutionContext.scope.extent).run { emptyList() } -} - -class ActionInvokeCommand -internal constructor( - private val invokeAction: InvokeAction, - commandExecutionContext: CommandExecutionContext, - commandFactory: ActionCommandFactory, - meterRegistry: MeterRegistry, -) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { - - override fun execute(): List { - val service = selectServiceImplementation() - val input = prepareInput(commandExecutionContext.scope.extent) - - commandExecutionContext.coroutineScope.launch { - runCatching { service.invoke(input) } - .onSuccess { raiseEvents(it) } - .onFailure { logger.error(it) { "service invocation failed" } } - } - - return emptyList() - } - - private fun selectServiceImplementation(): ServiceImplementation = - commandExecutionContext.serviceImplementationSelector.select( - invokeAction.type, - invokeAction.mode, - ) ?: error("no service implementation found for type '${invokeAction.type}'") - - private fun prepareInput(extent: Extent): List = - invokeAction.input.map { it.evaluate(extent) } - - private fun raiseEvents(output: List) { - invokeAction.raises.forEach { eventTemplate -> - val event = eventTemplate.copy(data = output) - val handler = - when (event.channel) { - EventChannel.INTERNAL -> - commandExecutionContext.stateMachineEventHandler::propagateToParent - else -> commandExecutionContext.stateMachineEventHandler::sendEvent - } - handler(event) - } - } -} - -class ActionMatchCommand -internal constructor( - private val matchAction: MatchAction, - commandExecutionContext: CommandExecutionContext, - commandFactory: ActionCommandFactory, - meterRegistry: MeterRegistry, -) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { - - override fun execute(): List { - val extent = commandExecutionContext.scope.extent - val matchValue = matchAction.value.execute(extent) - - val selectedActions = - matchAction.cases.entries - .filter { (expression, _) -> expression.execute(extent) == matchValue } - .map { it.value } - .ifEmpty { listOfNotNull(matchAction.default) } - - return selectedActions.map { commandFactory.create(it, commandExecutionContext) } - } -} - -class ActionRaiseCommand -internal constructor( - private val raiseAction: RaiseAction, - commandExecutionContext: CommandExecutionContext, - commandFactory: ActionCommandFactory, - meterRegistry: MeterRegistry, -) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { - - override fun execute(): List { - val event = - raiseAction.event.evaluateData(commandExecutionContext.scope.extent).run { - val target = raiseAction.target?.execute(commandExecutionContext.scope.extent) as? String - if (target != null) copy(target = target) else this - } - - with(commandExecutionContext.stateMachineEventHandler) { - if (event.channel == EventChannel.INTERNAL) propagateToParent(event) else sendEvent(event) - } - - return emptyList() - } -} - -class ActionTimeoutCommand -internal constructor( - private val timeoutAction: TimeoutAction, - commandExecutionContext: CommandExecutionContext, - commandFactory: ActionCommandFactory, - meterRegistry: MeterRegistry, -) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { - - override fun execute(): List = - listOf(commandFactory.create(timeoutAction.`do`, commandExecutionContext)) -} - -class ActionTimeoutResetCommand -internal constructor( - val timeoutResetAction: TimeoutResetAction, - commandExecutionContext: CommandExecutionContext, - commandFactory: ActionCommandFactory, - meterRegistry: MeterRegistry, -) : ActionCommand(commandExecutionContext, commandFactory, meterRegistry) { - - override fun execute(): List = emptyList() -} diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/command/CommandExecutionContext.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/command/CommandExecutionContext.kt deleted file mode 100644 index 0b06a22a..00000000 --- a/src/main/java/at/ac/uibk/dps/cirrina/execution/command/CommandExecutionContext.kt +++ /dev/null @@ -1,15 +0,0 @@ -package at.ac.uibk.dps.cirrina.execution.command - -import at.ac.uibk.dps.cirrina.execution.`object`.event.Event -import at.ac.uibk.dps.cirrina.execution.`object`.statemachine.StateMachine -import at.ac.uibk.dps.cirrina.execution.service.ServiceImplementationSelector -import kotlinx.coroutines.CoroutineScope - -data class CommandExecutionContext( - val scope: Scope, - val serviceImplementationSelector: ServiceImplementationSelector, - val stateMachineEventHandler: StateMachine.StateMachineEventHandler, - val coroutineScope: CoroutineScope, - val raisingEvent: Event? = null, - val isWhile: Boolean = false, -) diff --git a/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/Action.kt b/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/Action.kt deleted file mode 100644 index bbfa628d..00000000 --- a/src/main/java/at/ac/uibk/dps/cirrina/execution/object/action/Action.kt +++ /dev/null @@ -1,109 +0,0 @@ -package at.ac.uibk.dps.cirrina.execution.`object`.action - -import at.ac.uibk.dps.cirrina.csm.Csml.* -import at.ac.uibk.dps.cirrina.execution.`object`.context.ContextVariable -import at.ac.uibk.dps.cirrina.execution.`object`.event.Event -import at.ac.uibk.dps.cirrina.execution.`object`.expression.Expression - -interface Action { - - companion object { - fun create(description: ActionDescription, name: String? = null): Result = runCatching { - when (description) { - is EvalDescription -> Expression.from(description.expression).map(::EvalAction).getOrThrow() - - is InvokeDescription -> - InvokeAction( - description.type, - description.mode, - buildVariables(description.input).getOrThrow(), - buildEvents(description.raises).getOrThrow(), - ) - - is MatchDescription -> - MatchAction( - Expression.from(description.value).getOrThrow(), - description.cases.associate { - Expression.from(it.of).getOrThrow() to create(it.then).getOrThrow() - }, - description.default?.let { create(it).getOrThrow() }, - ) - - is RaiseDescription -> - RaiseAction( - Event.from(description.event).getOrThrow(), - description.target?.let { Expression.from(it).getOrThrow() }, - ) - - is TimeoutDescription -> - TimeoutAction( - name ?: error("timeout action name required"), - Expression.from(description.delay).getOrThrow(), - create(description.`do`).getOrThrow(), - ) - - is ResetDescription -> TimeoutResetAction(description.name) - - else -> error("unknown type: ${description.javaClass.simpleName}") - } - } - - private fun buildVariables(context: Map) = runCatching { - context.map { (k, v) -> - val expression = Expression.from(v).getOrThrow() - ContextVariable.lazy(k, expression) - } - } - - private fun buildEvents(events: List) = runCatching { - events.map { Event.from(it).getOrThrow() } - } - } -} - -interface EventRaisingAction : Action { - - fun raises(): List -} - -class EvalAction internal constructor(val expression: Expression) : Action - -class InvokeAction -internal constructor( - val type: String, - val mode: InvocationMode, - val input: List, - val raises: List, -) : EventRaisingAction { - - override fun raises(): List = raises -} - -class MatchAction -internal constructor( - val value: Expression, - val cases: Map, - val default: Action? = null, -) : EventRaisingAction { - - override fun raises(): List = - (cases.values + listOfNotNull(default)).filterIsInstance().flatMap { - it.raises() - } -} - -class RaiseAction internal constructor(val event: Event, val target: Expression?) : - EventRaisingAction { - - override fun raises(): List = listOf(event) -} - -class TimeoutAction -internal constructor(val name: String, val delay: Expression, val `do`: Action) : - EventRaisingAction { - - override fun raises(): List = - (`do` as? RaiseAction)?.let { listOf(it.event) } ?: emptyList() -} - -class TimeoutResetAction internal constructor(val action: String) : Action From c01d9698278826486e58d14b1c67b7dea18fdab6 Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Mon, 9 Feb 2026 11:49:45 +0100 Subject: [PATCH 06/15] fix: correct dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 03ea4c44..cf9d43fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,8 +13,8 @@ RUN gradle distZip RUN unzip build/distributions/cirrina.zip -d /tmp \ && chmod +x /tmp/cirrina/bin/cirrina -FROM eclipse-temurin:25-jdk-jammy AS runtime +FROM gcr.io/distroless/java25-debian13 AS runtime COPY --from=build /tmp/cirrina /opt/cirrina -ENTRYPOINT ["java", "-cp", "/opt/cirrina/lib/*", "at.ac.uibk.dps.cirrina.cirrina.CirrinaKt"] \ No newline at end of file +ENTRYPOINT ["java", "-cp", "/opt/cirrina/lib/*", "at.ac.uibk.dps.cirrina.CirrinaKt"] \ No newline at end of file From 34429c6185b1414093741d0e7c2086524f211c70 Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Mon, 9 Feb 2026 11:53:15 +0100 Subject: [PATCH 07/15] chore: remove unnecessary import --- .../kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt index 114918d8..08eaa7f5 100644 --- a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt +++ b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt @@ -1,6 +1,5 @@ package at.ac.uibk.dps.cirrina.execution.`object` -import at.ac.uibk.dps.cirrina.csm.Csml import at.ac.uibk.dps.cirrina.csm.Csml.* import org.jgrapht.graph.DefaultEdge import org.jgrapht.graph.SimpleDirectedGraph @@ -90,7 +89,7 @@ internal constructor( val type: String, val mode: InvocationMode, val input: List, - val output: List, + val output: List, val raises: List, ) : EventRaisingAction { override fun raises(): List = raises From 11fc5e47999da011344dbe8e6f3a2f9a4b3fd98d Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Mon, 9 Feb 2026 16:32:43 +0100 Subject: [PATCH 08/15] style: match project logging style --- .../at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt index e1f982f7..d2048b1e 100644 --- a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt +++ b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt @@ -106,10 +106,10 @@ internal constructor( ?.let { runCatching { extent.set(variable.reference, it.value) } .onFailure { e -> - logger.warn("Failed to assign service output to variable '${variable.reference}'", e) + logger.warn(e) { "failed to assign service output to variable '${variable.reference}'" } } } - ?: logger.warn("Service output does not contain expected variable '${variable.reference}'") + ?: logger.warn { "service output does not contain expected variable '${variable.reference}'" } } private fun raiseEvents(output: List) { From dd5b76a822c1313f20abb242221391f579655d03 Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Mon, 9 Feb 2026 17:15:32 +0100 Subject: [PATCH 09/15] test: add service variable output test --- .../dps/cirrina/execution/object/ActionCommand.kt | 8 ++++++-- .../kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt | 6 +++++- .../resources/pkl/complete/completeStateMachine.pkl | 11 +++++++++-- src/test/resources/pkl/complete/main.pkl | 2 +- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt index d2048b1e..109b3c32 100644 --- a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt +++ b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt @@ -106,10 +106,14 @@ internal constructor( ?.let { runCatching { extent.set(variable.reference, it.value) } .onFailure { e -> - logger.warn(e) { "failed to assign service output to variable '${variable.reference}'" } + logger.warn(e) { + "failed to assign service output to variable '${variable.reference}'" + } } } - ?: logger.warn { "service output does not contain expected variable '${variable.reference}'" } + ?: logger.warn { + "service output does not contain expected variable '${variable.reference}'" + } } private fun raiseEvents(output: List) { diff --git a/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt b/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt index cea46fbe..67a59327 100644 --- a/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt +++ b/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt @@ -29,7 +29,10 @@ class CompleteTest { val context = ContextInMemory() val server = mockHttpServer { input -> val v = input.firstOrNull { it.name == "v" } ?: error("variable 'v' not found") - listOf(ContextVariable("v", (v.value as Int) + 1)) + listOf( + ContextVariable("v", (v.value as Int) + 1), + ContextVariable("s", (v.value + 1) * 10), + ) } try { @@ -45,6 +48,7 @@ class CompleteTest { assertEquals(100, context.get("v")) assertEquals(true, context.get("b")) assertEquals(true, context.get("e")) + assertEquals(1000, context.get("f")) } finally { server.stop(1) } diff --git a/src/test/resources/pkl/complete/completeStateMachine.pkl b/src/test/resources/pkl/complete/completeStateMachine.pkl index 9865716b..f18becba 100644 --- a/src/test/resources/pkl/complete/completeStateMachine.pkl +++ b/src/test/resources/pkl/complete/completeStateMachine.pkl @@ -18,6 +18,7 @@ sm = new csml.StateMachine { type = "increment" mode = "local" input { ["v"] = "x" } + output { new csml.ContextVariableReferenceDescription { reference = "s" } } raises { new csml.Internal { topic = "e2" } } } } @@ -50,7 +51,13 @@ sm = new csml.StateMachine { ["e4"] = new csml.Transition { to = "d" } } } - ["e"] = new csml.Terminal { entry { new csml.Eval { expression = "b = true" } } } + ["e"] = new csml.Terminal { entry { + new csml.Eval { expression = "b = true" } + new csml.Eval { expression = "f = s"} + } } + } + transient { + ["x"] = "0" + ["s"] = "0" } - transient { ["x"] = "0" } } \ No newline at end of file diff --git a/src/test/resources/pkl/complete/main.pkl b/src/test/resources/pkl/complete/main.pkl index 297cc3a3..32b71978 100644 --- a/src/test/resources/pkl/complete/main.pkl +++ b/src/test/resources/pkl/complete/main.pkl @@ -6,7 +6,7 @@ collaborativeStateMachine { stateMachines { ["completeStateMachine"] = completeStateMachine.sm } - persistent { ["b"] = "false"; ["v"] = "0"; ["e"] = "false" } + persistent { ["b"] = "false"; ["v"] = "0"; ["e"] = "false"; ["f"] = "0" } } instances { ["complete"] = "completeStateMachine" From d593d79efa99c52af55dfd15f22028b4765b488d Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Tue, 10 Feb 2026 09:14:35 +0100 Subject: [PATCH 10/15] test: add test for service variable output --- src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt | 2 +- src/test/resources/pkl/complete/completeStateMachine.pkl | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt b/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt index 67a59327..8365a561 100644 --- a/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt +++ b/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt @@ -48,7 +48,7 @@ class CompleteTest { assertEquals(100, context.get("v")) assertEquals(true, context.get("b")) assertEquals(true, context.get("e")) - assertEquals(1000, context.get("f")) + assertEquals(50500, context.get("f")) } finally { server.stop(1) } diff --git a/src/test/resources/pkl/complete/completeStateMachine.pkl b/src/test/resources/pkl/complete/completeStateMachine.pkl index f18becba..bec7a420 100644 --- a/src/test/resources/pkl/complete/completeStateMachine.pkl +++ b/src/test/resources/pkl/complete/completeStateMachine.pkl @@ -24,6 +24,7 @@ sm = new csml.StateMachine { } exit { new csml.Eval { expression = "e = true" } + new csml.Eval { expression = "f = f + s" } } on { ["e2"] = new csml.Transition { to = "c"; do { new csml.Eval { expression = "x = $v" } } } @@ -51,10 +52,7 @@ sm = new csml.StateMachine { ["e4"] = new csml.Transition { to = "d" } } } - ["e"] = new csml.Terminal { entry { - new csml.Eval { expression = "b = true" } - new csml.Eval { expression = "f = s"} - } } + ["e"] = new csml.Terminal { entry { new csml.Eval { expression = "b = true" } } } } transient { ["x"] = "0" From 6a2df6ae9509025d8284d9013f0d31998fe948f9 Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Tue, 10 Feb 2026 09:27:45 +0100 Subject: [PATCH 11/15] style: change function declaration to match project style --- .../at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt index 109b3c32..b3ca54fe 100644 --- a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt +++ b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt @@ -99,7 +99,7 @@ internal constructor( private fun prepareInput(extent: Extent): List = invokeAction.input.map { it.evaluate(extent) } - private fun assignServiceOutput(output: List, extent: Extent) = + private fun assignServiceOutput(output: List, extent: Extent) { invokeAction.output.forEach { variable -> output .firstOrNull { it.name == variable.reference } @@ -115,6 +115,7 @@ internal constructor( "service output does not contain expected variable '${variable.reference}'" } } + } private fun raiseEvents(output: List) { invokeAction.raises.forEach { eventTemplate -> From e859c9001f182f1f1c27bf97c6ab983f6fafc7b5 Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Tue, 3 Feb 2026 10:38:28 +0100 Subject: [PATCH 12/15] feat: add service invocation output variables --- src/main/resources/pkl/csm/csml.pkl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/pkl/csm/csml.pkl b/src/main/resources/pkl/csm/csml.pkl index e9f92d4d..95aca902 100644 --- a/src/main/resources/pkl/csm/csml.pkl +++ b/src/main/resources/pkl/csm/csml.pkl @@ -132,6 +132,10 @@ class MatchDescription extends ActionDescription { default: ActionDescription? } +class ContextVariableReferenceDescription { + reference: String +} + class LogDescription extends ActionDescription { message: Expression } From 99cbdb9e4f975a56f0b3f819c805ea5a45cd1df6 Mon Sep 17 00:00:00 2001 From: Marlon Etheredge <80909536+Frnd-me@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:37:25 +0100 Subject: [PATCH 13/15] feat: improving actions (#97) --- src/main/resources/pkl/csm/csml.pkl | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/resources/pkl/csm/csml.pkl b/src/main/resources/pkl/csm/csml.pkl index 95aca902..57275ad5 100644 --- a/src/main/resources/pkl/csm/csml.pkl +++ b/src/main/resources/pkl/csm/csml.pkl @@ -99,7 +99,6 @@ class InvokeDescription extends ActionDescription { type: InvocationType mode: InvocationMode = "remote" input: Context - output: Listing raises: Listing } @@ -127,23 +126,14 @@ class CaseDescription { } class MatchDescription extends ActionDescription { - value: Expression cases: Listing default: ActionDescription? } -class ContextVariableReferenceDescription { - reference: String -} - class LogDescription extends ActionDescription { message: Expression } -class ContextVariableReferenceDescription { - reference: String -} - typealias EventTopic = String(matches(Regex(#"^[a-zA-Z_]\w*$"#))) typealias EventChannel = "internal"|"external"|"global"|"peripheral" From d88d410df3c34d790c7aa9446772971e0e0eeac9 Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Tue, 3 Feb 2026 10:38:28 +0100 Subject: [PATCH 14/15] feat: add service invocation output variables --- src/main/resources/pkl/csm/csml.pkl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/resources/pkl/csm/csml.pkl b/src/main/resources/pkl/csm/csml.pkl index 57275ad5..d820e86c 100644 --- a/src/main/resources/pkl/csm/csml.pkl +++ b/src/main/resources/pkl/csm/csml.pkl @@ -99,6 +99,7 @@ class InvokeDescription extends ActionDescription { type: InvocationType mode: InvocationMode = "remote" input: Context + output: Listing raises: Listing } @@ -122,10 +123,11 @@ class ResetDescription extends ActionDescription { class CaseDescription { of: Expression - then: Listing + then: ActionDescription } class MatchDescription extends ActionDescription { + value: Expression cases: Listing default: ActionDescription? } @@ -134,6 +136,10 @@ class LogDescription extends ActionDescription { message: Expression } +class ContextVariableReferenceDescription { + reference: String +} + typealias EventTopic = String(matches(Regex(#"^[a-zA-Z_]\w*$"#))) typealias EventChannel = "internal"|"external"|"global"|"peripheral" From 9c2ad0e27a77772708e243106dc331093a01abfa Mon Sep 17 00:00:00 2001 From: Gregor Holzer Date: Tue, 10 Feb 2026 13:34:24 +0100 Subject: [PATCH 15/15] fix: fix csml language --- src/main/resources/pkl/csm/csml.pkl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/resources/pkl/csm/csml.pkl b/src/main/resources/pkl/csm/csml.pkl index d820e86c..f413203e 100644 --- a/src/main/resources/pkl/csm/csml.pkl +++ b/src/main/resources/pkl/csm/csml.pkl @@ -123,11 +123,10 @@ class ResetDescription extends ActionDescription { class CaseDescription { of: Expression - then: ActionDescription + then: Listing } class MatchDescription extends ActionDescription { - value: Expression cases: Listing default: ActionDescription? }