All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.enodeframework.eventing.EventCommittingContextMailBox.kt Maven / Gradle / Ivy

package org.enodeframework.eventing

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import org.enodeframework.common.exception.DuplicateEventStreamException
import org.enodeframework.common.extensions.SystemClock
import org.enodeframework.common.function.Action1
import org.enodeframework.common.io.Task
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue

class EventCommittingContextMailBox(
    private val number: Int,
    private val batchSize: Int,
    private val coroutineDispatcher: CoroutineDispatcher,
    handleEventAction: Action1>
) {
    private val logger: Logger = LoggerFactory.getLogger(EventCommittingContextMailBox::class.java)
    private val lockObj = Any()
    private val asyncLockObj = Any()
    private val aggregateDictDict: ConcurrentHashMap> = ConcurrentHashMap()
    private val messageQueue: ConcurrentLinkedQueue = ConcurrentLinkedQueue()
    private val handleMessageAction: Action1> = handleEventAction
    private var lastActiveTime: Date = Date()

    private var isRunning = false

    private fun totalUnHandledMessageCount(): Long {
        return messageQueue.count().toLong()
    }

    fun getNumber() {
        number
    }

    /**
     * 放入一个消息到MailBox,并自动尝试运行MailBox
     */
    fun enqueueMessage(message: EventCommittingContext) {
        synchronized(lockObj) {
            val eventDict =
                aggregateDictDict.computeIfAbsent(message.eventStream.aggregateRootId) { ConcurrentHashMap() }
            // If the specified key is not already associated with a value (or is mapped to null) associates it with the given value and returns null, else returns the current value.
            if (eventDict.putIfAbsent(message.eventStream.id, ONE_BYTE) == null) {
                message.mailBox = this
                messageQueue.add(message)
                if (logger.isDebugEnabled) {
                    logger.debug("{} enqueued new message, mailboxNumber: {}, aggregateRootId: {}, commandId: {}, eventVersion: {}, eventStreamId: {}, eventIds: {}",
                        javaClass.name,
                        number,
                        message.eventStream.aggregateRootId,
                        message.processingCommand.message.id,
                        message.eventStream.version,
                        message.eventStream.id,
                        message.eventStream.events.joinToString("|") { obj: DomainEventMessage -> obj.id })
                }
                lastActiveTime = Date()
                tryRun()
            } else {
                throw DuplicateEventStreamException(message.eventStream)
            }
        }
    }

    /**
     * 尝试运行一次MailBox,一次运行会处理一个消息或者一批消息,当前MailBox不能是运行中或者暂停中或者已暂停
     */
    private fun tryRun() {
        synchronized(lockObj) {
            if (isRunning) {
                return
            }
            setAsRunning()
            if (logger.isDebugEnabled) {
                logger.debug("{} start run, mailboxNumber: {}", javaClass.name, number)
            }
            CoroutineScope(coroutineDispatcher).async { processMessages() }
            return
        }
    }

    /**
     * 请求完成MailBox的单次运行,如果MailBox中还有剩余消息,则继续尝试运行下一次
     */
    fun completeRun() {
        lastActiveTime = Date()
        if (logger.isDebugEnabled) {
            logger.debug("{} complete run, mailboxNumber: {}", javaClass.name, number)
        }
        setAsNotRunning()
        if (totalUnHandledMessageCount() > 0) {
            tryRun()
        }
    }

    fun removeAggregateAllEventCommittingContexts(aggregateRootId: String) {
        aggregateDictDict.remove(aggregateRootId)
    }

    fun isInactive(timeoutSeconds: Int): Boolean {
        return SystemClock.now() - lastActiveTime.time >= timeoutSeconds
    }

    private fun processMessages() {
        synchronized(asyncLockObj) {
            lastActiveTime = Date()
            val messageList: MutableList = ArrayList()
            while (messageList.size < batchSize) {
                val message = messageQueue.poll()
                if (message != null) {
                    val eventDict = aggregateDictDict[message.eventStream.aggregateRootId]
                    if (eventDict != null) {
                        if (eventDict.remove(message.eventStream.id) != null) {
                            messageList.add(message)
                        }
                    }
                } else {
                    break
                }
            }
            if (messageList.isEmpty()) {
                completeRun()
                return
            }
            try {
                handleMessageAction.apply(messageList)
            } catch (ex: Exception) {
                logger.error("{} run has unknown exception, mailboxNumber: {}", javaClass.name, number, ex)
                Task.sleep(1)
                completeRun()
            }
        }
    }

    private fun setAsRunning() {
        isRunning = true
    }

    private fun setAsNotRunning() {
        isRunning = false
    }

    companion object {
        private const val ONE_BYTE: Byte = 1
    }

    init {
        lastActiveTime = Date()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy