CommandHandlerBus.kt

package de.pflugradts.passbird.application.commandhandling

import de.pflugradts.passbird.application.InactivityTerminationRequestedException
import de.pflugradts.passbird.application.StdinTerminationRequestedException
import de.pflugradts.passbird.application.commandhandling.command.base.Command
import de.pflugradts.passbird.application.commandhandling.handler.CommandHandler
import java.util.logging.Level
import java.util.logging.Logger

class CommandHandlerBus constructor(commandHandlers: Set<CommandHandler>) : CommandBus {
    private val commandHandlersByType = commandHandlers.groupBy { it.commandType }

    override fun post(command: Command) {
        var subscriberException: RuntimeException? = null
        commandHandlersByType[command.javaClass].orEmpty().forEach { commandHandler ->
            subscriberException = commandHandler.handleCatching(command, subscriberException)
        }
        subscriberException?.let { throw it }
    }

    private fun CommandHandler.handleCatching(command: Command, previousException: RuntimeException?) = runCatching { handle(command) }
        .fold(
            onSuccess = { previousException },
            onFailure = { exception ->
                exception.throwIfFatal()
                when (exception) {
                    is InactivityTerminationRequestedException,
                    is StdinTerminationRequestedException,
                    -> previousException ?: exception

                    else -> {
                        LOGGER.log(Level.SEVERE, "Exception thrown by command handler", exception)
                        previousException ?: exception.asRuntimeException()
                    }
                }
            },
        )

    private companion object {
        val LOGGER: Logger = Logger.getLogger(CommandHandlerBus::class.java.name)
    }
}

private fun Throwable.asRuntimeException() = this as? RuntimeException ?: RuntimeException(this)

private fun Throwable.throwIfFatal() {
    if (this is VirtualMachineError || this is ThreadDeath || this is LinkageError) throw this
}