NestingGroundService.kt
package de.pflugradts.passbird.domain.service.nest
import de.pflugradts.kotlinextensions.MutableOption
import de.pflugradts.kotlinextensions.MutableOption.Companion.mutableOptionOf
import de.pflugradts.kotlinextensions.MutableOption.Companion.optionOf
import de.pflugradts.kotlinextensions.Option
import de.pflugradts.kotlinextensions.TryResult
import de.pflugradts.kotlinextensions.TryResult.Companion.success
import de.pflugradts.passbird.domain.model.nest.Nest
import de.pflugradts.passbird.domain.model.nest.Nest.Companion.createNest
import de.pflugradts.passbird.domain.model.shell.Shell
import de.pflugradts.passbird.domain.model.shell.Shell.Companion.emptyShell
import de.pflugradts.passbird.domain.model.slot.Slot
import de.pflugradts.passbird.domain.model.slot.Slot.Companion.CAPACITY
import de.pflugradts.passbird.domain.model.slot.Slot.Companion.FIRST_SLOT
import de.pflugradts.passbird.domain.model.slot.Slot.Companion.LAST_SLOT
import de.pflugradts.passbird.domain.model.slot.Slot.Companion.slotAt
import de.pflugradts.passbird.domain.service.eventhandling.EventRegistry
import de.pflugradts.passbird.domain.service.password.tree.PasswordTreeAdapterPort
import de.pflugradts.passbird.domain.service.password.tree.PasswordTreeSyncService
import java.util.function.Supplier
class NestingGroundService constructor(
private val passwordTreeAdapterPort: PasswordTreeAdapterPort,
private val passwordTreeSyncService: PasswordTreeSyncService,
private val eventRegistry: EventRegistry,
) : NestService, NestStateView {
private val lazyNests = mutableListOf<MutableOption<Nest>>()
private val nests: MutableList<MutableOption<Nest>> get() = lazyNests.also { restoreIfNeeded() }
private var currentNest = Slot.DEFAULT
override fun populate(nestShells: List<Shell>) {
initializeSlotsIfNeeded()
restoreFromShells(nestShells)
}
override fun place(nestShell: Shell, slot: Slot) = place(nestShell, slot, true)
fun place(nestShell: Shell, slot: Slot, publish: Boolean): TryResult<Unit> {
restoreIfNeeded()
val snapshot = nestSnapshot()
val currentNestSnapshot = currentNest
createNest(nestShell, slot).let {
lazyNests[slot.nestIndex()].set(it)
eventRegistry.register(it)
if (publish) {
return passwordTreeSyncService.sync()
.onSuccess { eventRegistry.processEvents() }
.onFailure { restore(snapshot, currentNestSnapshot) }
}
it.clearDomainEvents()
return success(Unit)
}
}
override fun discardNestAt(slot: Slot): TryResult<Unit> {
val snapshot = nestSnapshot()
val currentNestSnapshot = currentNest
atNestSlot(slot).ifPresent {
it.discard()
it.getDomainEvents().forEach(eventRegistry::register)
it.clearDomainEvents()
eventRegistry.deregister(it)
}
lazyNests[slot.nestIndex()] = EMPTY_NEST_SUPPLIER.get()
return passwordTreeSyncService.sync()
.onSuccess { eventRegistry.processEvents() }
.onFailure { restore(snapshot, currentNestSnapshot) }
}
override fun atNestSlot(slot: Slot): Option<Nest> = if (slot === Slot.DEFAULT) Nest.DEFAULT.option() else nests[slot.nestIndex()]
override fun all(includeDefault: Boolean) = nests.let { if (includeDefault) Nest.DEFAULT.asOptionInList() + it else it }.stream()
override fun currentNest(): Nest = atNestSlot(currentNest).orElse(Nest.DEFAULT)
override fun moveToNestAt(slot: Slot) {
if (atNestSlot(slot).isPresent) currentNest = slot
}
override fun currentNestSlot() = currentNest().slot
override fun snapshot() = nestSnapshot()
private fun nestSnapshot() = (FIRST_SLOT..LAST_SLOT).map { slot ->
atNestSlot(slotAt(slot)).map { it.viewNestId() }.orElse(emptyShell())
}
private fun initializeSlotsIfNeeded() {
if (lazyNests.isEmpty()) {
repeat(CAPACITY) { lazyNests.add(EMPTY_NEST_SUPPLIER.get()) }
}
}
private fun restoreIfNeeded() {
if (lazyNests.isEmpty()) {
initializeSlotsIfNeeded()
restoreFromShells(passwordTreeAdapterPort.restore().nests())
}
}
private fun restore(nestShells: List<Shell>, currentNestSnapshot: Slot) {
lazyNests.filter { it.isPresent }.forEach { eventRegistry.deregister(it.get()) }
eventRegistry.clearEvents()
lazyNests.clear()
currentNest = currentNestSnapshot
initializeSlotsIfNeeded()
restoreFromShells(nestShells)
}
private fun restoreFromShells(nestShells: List<Shell>) {
if (nestShells.size == Slot.CAPACITY) {
nestShells.forEachIndexed { index, shell ->
if (shell.isNotEmpty) {
place(shell, Slot.slotAt(index + 1), publish = false)
}
}
}
if (currentNest != Slot.DEFAULT && atNestSlot(currentNest).isEmpty) {
currentNest = Slot.DEFAULT
}
}
companion object {
private val EMPTY_NEST_SUPPLIER = Supplier { mutableOptionOf<Nest>() }
}
}
private fun Nest.asOptionInList() = listOf(optionOf(this))
private fun Nest.option() = optionOf(this)
private fun Slot.nestIndex() = index() - 1