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

org.enodeframework.eventing.ProcessingEventMailBox.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.MailBoxProcessException
import org.enodeframework.common.extensions.SystemClock
import org.enodeframework.common.function.Action1
import org.enodeframework.common.io.Task
import org.slf4j.LoggerFactory
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicInteger

class ProcessingEventMailBox(
    val aggregateRootTypeName: String,
    val aggregateRootId: String,
    private val coroutineDispatcher: CoroutineDispatcher,
    private var handleProcessingEventAction: Action1
) {
    private val lockObj = Any()
    private val isUsing = AtomicInteger(0)
    private val isRemoved = AtomicInteger(0)
    private val isRunning = AtomicInteger(0)
    private var waitingProcessingEventDict = ConcurrentHashMap()
    private var processingEventQueue: ConcurrentLinkedQueue = ConcurrentLinkedQueue()
    private var lastActiveTime: Date
    private var nextExpectingEventVersion: Int? = null

    private fun tryRemovedInvalidWaitingMessages(version: Int) {
        waitingProcessingEventDict.keys.filter { x: Int -> x < version }.forEach { key: Int ->
            if (waitingProcessingEventDict.containsKey(key)) {
                val processingEvent = waitingProcessingEventDict.remove(key)
                processingEvent!!.complete()
                logger.warn(
                    "{} invalid waiting message removed, aggregateRootType: {}, aggregateRootId: {}, commandId: {}, eventVersion: {}, eventStreamId: {}, eventTypes: {}, eventIds: {}, nextExpectingEventVersion: {}",
                    javaClass.name,
                    processingEvent.message.aggregateRootTypeName,
                    processingEvent.message.aggregateRootId,
                    processingEvent.message.commandId,
                    processingEvent.message.version,
                    processingEvent.message.id,
                    processingEvent.message.events.joinToString("|") { x: DomainEventMessage -> x.javaClass.name },
                    processingEvent.message.events.joinToString("|") { obj: DomainEventMessage -> obj.id },
                    version
                )
            }
        }
    }

    private fun tryEnqueueValidWaitingMessage() {
        if (nextExpectingEventVersion == null) {
            return
        }
        while (waitingProcessingEventDict.containsKey(nextExpectingEventVersion)) {
            val nextProcessingEvent = waitingProcessingEventDict.remove(nextExpectingEventVersion)
            if (nextProcessingEvent != null) {
                enqueueEventStream(nextProcessingEvent)
                logger.info(
                    "{} enqueued waiting processingEvent, aggregateRootId: {}, aggregateRootTypeName: {}, eventVersion: {}",
                    javaClass.name,
                    aggregateRootId,
                    aggregateRootTypeName,
                    nextProcessingEvent.message.version
                )
            }
        }
    }

    fun getTotalUnHandledMessageCount(): Int {
        return processingEventQueue.count()
    }

    fun setNextExpectingEventVersion(version: Int) {
        synchronized(lockObj) {
            tryRemovedInvalidWaitingMessages(version)
            if (this.nextExpectingEventVersion == null || version > this.nextExpectingEventVersion!!) {
                nextExpectingEventVersion = version
                logger.info(
                    "{} refreshed nextExpectingEventVersion, aggregateRootId: {}, aggregateRootTypeName: {}, version: {}",
                    javaClass.name,
                    aggregateRootId,
                    aggregateRootTypeName,
                    nextExpectingEventVersion
                )
                tryEnqueueValidWaitingMessage()
                lastActiveTime = Date()
                tryRun()
            } else if (version == this.nextExpectingEventVersion) {
                logger.info(
                    "{} equals nextExpectingEventVersion ignored, aggregateRootId: {}, aggregateRootTypeName: {}, version: {}, current nextExpectingEventVersion: {}",
                    javaClass.name,
                    aggregateRootId,
                    aggregateRootTypeName,
                    version,
                    nextExpectingEventVersion
                )
            } else {
                logger.info(
                    "{} nextExpectingEventVersion ignored, aggregateRootId: {}, aggregateRootTypeName: {}, version: {}, current nextExpectingEventVersion: {}",
                    javaClass.name,
                    aggregateRootId,
                    aggregateRootTypeName,
                    version,
                    nextExpectingEventVersion
                )
            }
        }
    }

    private fun enqueueEventStream(processingEvent: ProcessingEvent) {
        synchronized(lockObj) {
            processingEvent.mailbox = this
            processingEventQueue.add(processingEvent)
            nextExpectingEventVersion = processingEvent.message.version + 1
            if (logger.isDebugEnabled) {
                logger.debug("{} enqueued new message, aggregateRootType: {}, aggregateRootId: {}, commandId: {}, eventVersion: {}, eventStreamId: {}, eventTypes: {}, eventIds: {}",
                    javaClass.name,
                    processingEvent.message.aggregateRootTypeName,
                    processingEvent.message.aggregateRootId,
                    processingEvent.message.commandId,
                    processingEvent.message.version,
                    processingEvent.message.id,
                    processingEvent.message.events.joinToString("|") { x: DomainEventMessage -> x.javaClass.name },
                    processingEvent.message.events.joinToString("|") { x: DomainEventMessage -> x.id })
            }
        }
    }

    fun enqueueMessage(processingEvent: ProcessingEvent): EnqueueMessageResult {
        synchronized(lockObj) {
            if (isRemoved()) {
                throw MailBoxProcessException(
                    String.format(
                        "ProcessingEventMailBox was removed, cannot allow to enqueue message, aggregateRootTypeName: %s, aggregateRootId: %s",
                        aggregateRootTypeName,
                        aggregateRootId
                    )
                )
            }
            val eventStream = processingEvent.message
            if (nextExpectingEventVersion == null || eventStream.version > this.nextExpectingEventVersion!!) {
                if (waitingProcessingEventDict.putIfAbsent(eventStream.version, processingEvent) == null) {
                    logger.warn(
                        "{} waiting message added, aggregateRootType: {}, aggregateRootId: {}, commandId: {}, eventVersion: {}, eventStreamId: {}, eventTypes: {}, eventIds: {}, nextExpectingEventVersion: {}",
                        javaClass.name,
                        eventStream.aggregateRootTypeName,
                        eventStream.aggregateRootId,
                        eventStream.commandId,
                        eventStream.version,
                        eventStream.id,
                        processingEvent.message.events.joinToString("|") { x: DomainEventMessage -> x.javaClass.name },
                        processingEvent.message.events.joinToString("|") { x: DomainEventMessage -> x.id },
                        nextExpectingEventVersion
                    )
                }
                return EnqueueMessageResult.AddToWaitingList
            } else if (eventStream.version == nextExpectingEventVersion) {
                enqueueEventStream(processingEvent)
                tryEnqueueValidWaitingMessage()
                lastActiveTime = Date()
                tryRun()
                return EnqueueMessageResult.Success
            }
            return EnqueueMessageResult.Ignored
        }
    }

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

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

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

    private fun processMessage() {
        val message = processingEventQueue.poll()
        if (message != null) {
            lastActiveTime = Date()
            try {
                handleProcessingEventAction.apply(message)
            } catch (ex: Exception) {
                logger.error(
                    "{} run has unknown exception, aggregateRootId: {}", javaClass.name, aggregateRootId, ex
                )
                Task.sleep(1)
                completeRun()
            }
        } else {
            completeRun()
        }
    }

    fun tryUsing(): Boolean {
        return isUsing.compareAndSet(0, 1)
    }

    fun exitUsing() {
        isUsing.set(0)
    }

    fun markAsRemoved() {
        isRemoved.set(1)
    }

    private fun setAsRunning() {
        isRunning.set(1)
    }

    fun isRunning(): Boolean {
        return isRunning.get() == 1
    }

    fun isRemoved(): Boolean {
        return isRemoved.get() == 1
    }

    private fun setAsNotRunning() {
        isRunning.set(0)
    }

    fun getWaitingMessageCount(): Int {
        return waitingProcessingEventDict.size
    }

    private val logger = LoggerFactory.getLogger(ProcessingEventMailBox::class.java)

    init {
        this.lastActiveTime = Date()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy