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

com.exactpro.th2.common.utils.message.transport.MessageBatcher.kt Maven / Gradle / Ivy

/*
 * Copyright 2023 Exactpro (Exactpro Systems Limited)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.exactpro.th2.common.utils.message.transport

import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.GroupBatch
import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.Message
import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.MessageGroup
import java.time.Instant
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Future
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock

class MessageBatcher(
    private val maxBatchSize: Int = 1000,
    private val maxFlushTime: Long = 1000,
    private val book: String,
    private val batchSelector: (Message.Builder<*>, String) -> Any = GROUP_SELECTOR,
    private val executor: ScheduledExecutorService,
    private val onError: (Throwable) -> Unit = {},
    private val onBatch: (GroupBatch) -> Unit
) : AutoCloseable {
    private val batches = ConcurrentHashMap()

    fun onMessage(message: Message.Builder<*>, sessionGroup: String) {
        message.idBuilder().setTimestamp(Instant.now())
        batches.getOrPut(batchSelector(message, sessionGroup)) { Batch(book, sessionGroup) }
            .add(message.build().toGroup())
    }

    override fun close() {
        batches.values.forEach {
            it.close()
        }
    }

    inner class Batch(
        book: String,
        group: String,
    ) : AutoCloseable {
        private val newBatch: () -> GroupBatch.Builder = {
            GroupBatch.builder().apply {
                setBook(book)
                setSessionGroup(group)
            }
        }

        private val lock = ReentrantLock()
        private var batch = newBatch()
        private var future: Future<*> = CompletableFuture.completedFuture(null)

        fun add(group: MessageGroup) = lock.withLock {
            batch.addGroup(group)

            when (batch.groupsBuilder().size) {
                1 -> future = executor.schedule(::send, maxFlushTime, MILLISECONDS)
                maxBatchSize -> send()
            }
        }

        private fun send() = lock.withLock {
            if (batch.groupsBuilder().isEmpty()) return
            runCatching { onBatch(batch.build()) }.onFailure(onError)
            batch = newBatch()
            future.cancel(false)
        }

        override fun close() = send()
    }

    companion object {
        @JvmField
        val GROUP_SELECTOR: (Message.Builder<*>, String) -> Any = { _, group -> group }

        @JvmField
        val ALIAS_SELECTOR: (Message.Builder<*>, String) -> Any = { message, _ ->
            message.idBuilder().sessionAlias to message.idBuilder().direction
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy