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

.messaging_rabbitmq.2.0.1.source-code.RabbitMqClient.kt Maven / Gradle / Ivy

There is a newer version: 2.1.2
Show newest version
package com.hexagonkt.messaging.rabbitmq

import com.hexagonkt.core.logging.Logger
import com.codahale.metrics.MetricRegistry
import com.hexagonkt.http.parseQueryString
import com.hexagonkt.core.*
import com.rabbitmq.client.*
import com.rabbitmq.client.AMQP.BasicProperties
import com.rabbitmq.client.impl.StandardMetricsCollector

import java.io.Closeable
import java.lang.Runtime.getRuntime
import java.net.URI
import java.nio.charset.Charset.defaultCharset
import java.util.UUID.randomUUID
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.Executors.newFixedThreadPool
import kotlin.reflect.KClass

/**
 * Rabbit client.
 *
 * * TODO Review if channel handling is still needed in Java 4.1.x version
 * * TODO Add metrics
 * * TODO Ordered shutdown
 */
class RabbitMqClient(
    private val connectionFactory: ConnectionFactory,
    private val poolSize: Int = getRuntime().availableProcessors()) : Closeable {

    internal companion object {

        private fun  setVar(value: T?, setter: (T) -> Unit) {
            if (value != null)
                setter(value)
        }

        internal fun createConnectionFactory(uri: URI): ConnectionFactory {
            require(uri.toString().isNotBlank())

            val cf = ConnectionFactory()
            cf.setUri(uri)

            val params = parseQueryString(uri.query ?: "").filterNot { it.value.isBlank() }
            fun value(name: String): String? = params[name]
            val automaticRecovery = value("automaticRecovery")?.toBoolean()
            val recoveryInterval = value("recoveryInterval")?.toLong()
            val shutdownTimeout = value("shutdownTimeout")?.toInt()
            val heartbeat = value("heartbeat")?.toInt()
            val metricsCollector = StandardMetricsCollector(MetricRegistry())

            setVar(automaticRecovery) { cf.isAutomaticRecoveryEnabled = it }
            setVar(recoveryInterval) { cf.networkRecoveryInterval = it }
            setVar(shutdownTimeout) { cf.shutdownTimeout = it }
            setVar(heartbeat) { cf.requestedHeartbeat = it }
            setVar(metricsCollector) { cf.metricsCollector = it }

            return cf
        }
    }

    private val log: Logger = Logger(this::class)
    private val args = hashMapOf()

    @Volatile private var count: Int = 0
    private val threadPool = newFixedThreadPool(poolSize) { Thread(it, "rabbitmq-" + count++) }
    private var connection: Connection? = connectionFactory.newConnection()
    private val metrics: Metrics = Metrics(connectionFactory.metricsCollector as StandardMetricsCollector)
    private val listener = ConnectionListener()

    /** . */
    constructor (uri: URI) : this(createConnectionFactory(uri))

    /** . */
    val connected: Boolean get() = connection?.isOpen ?: false

    /** @see Closeable.close */
    override fun close() {
        connection?.removeShutdownListener(listener)
        (connection as? Recoverable)?.removeRecoveryListener(listener)
        connection?.close()
        connection = null
        metrics.report()
        log.info { "RabbitMQ client closed" }
    }

    /** . */
    fun declareQueue(name: String) {
        args["x-max-length-bytes"] = 1048576  // max queue length
        withChannel { it.queueDeclare(name, false, false, false, args) }
    }

    /** . */
    fun deleteQueue(name: String) {
        withChannel { it.queueDelete(name) }
    }

    /** . */
    fun bindExchange(exchange: String, exchangeType: String, routingKey: String, queue: String) {
        withChannel {
            it.queueDeclare(queue, false, false, false, null)
            it.queuePurge(queue)
            it.exchangeDeclare(exchange, exchangeType, false, false, false, null)
            it.queueBind(queue, exchange, routingKey)
        }
    }

    /** . */
    fun  consume(
        exchange: String, routingKey: String, type: KClass, handler: (T) -> Unit) {

        withChannel {
            it.queueDeclare(routingKey, false, false, false, null)
            it.queuePurge(routingKey)
            it.queueBind(routingKey, exchange, routingKey)
        }
        consume(routingKey, type, handler)
    }

    fun  consume(queueName: String, type: KClass, handler: (T) -> R) {
        val channel = createChannel()
        val callback = Handler(connectionFactory, channel, threadPool, type, handler)
        channel.basicConsume(queueName, false, callback)
        log.info { "Consuming messages in $queueName" }
    }

    /**
     * Tries to get a channel for five times. If it does not succeed it throws an
     * IllegalStateException.
     *
     * @return A new channel.
     */
    private fun createChannel(): Channel =
        retry(times = 3, delay = 50) {
            if (connection?.isOpen != true) {
                connection = connectionFactory.newConnection()
                connection?.addShutdownListener(listener)
                (connection as Recoverable).addRecoveryListener(listener)
                log.warn { "Rabbit connection RESTORED" }
            }
            val channel = connection?.createChannel() ?: fail
            channel.basicQos(poolSize)
            channel.addShutdownListener(listener)
            (channel as Recoverable).addRecoveryListener(listener)
            channel
        }

    private fun  withChannel(callback: (Channel) -> T): T {
        var channel: Channel? = null
        try {
            channel = createChannel()
            return callback(channel)
        }
        finally {
            if (channel != null && channel.isOpen)
                channel.close()
        }
    }

    fun publish(queue: String, message: String, correlationId: String? = null) =
        publish("", queue, message, correlationId)

    fun publish(
        exchange: String,
        routingKey: String,
        message: String,
        correlationId: String? = null) {

        withChannel { channel ->
            publish(channel, exchange, routingKey, null, message, correlationId, null)
        }
    }

    private fun publish(
        channel: Channel,
        exchange: String,
        routingKey: String,
        encoding: String?,
        message: String,
        correlationId: String?,
        replyQueueName: String?) {

        val builder = BasicProperties.Builder()

        if (!correlationId.isNullOrBlank())
            builder.correlationId(correlationId)

        if (!replyQueueName.isNullOrBlank())
            builder.replyTo(replyQueueName)

        if (!encoding.isNullOrBlank())
            builder.contentEncoding(encoding)

        val props = builder.build()

        val charset = if (encoding == null) defaultCharset() else charset(encoding)
        channel.basicPublish(exchange, routingKey, props, message.toByteArray(charset))

        log.debug {
            """
            EXCHANGE: $exchange ROUTING KEY: $routingKey
            REPLY TO: $replyQueueName CORRELATION ID: $correlationId
            BODY:
            $message""".trimIndent()
        }
    }

    fun call(requestQueue: String, message: String): String =
        withChannel {
            val correlationId = randomUUID().toString()
            val replyQueueName = it.queueDeclare().queue
            val charset = defaultCharset().name()

            publish(it, "", requestQueue, charset, message, correlationId, replyQueueName)

            val response = ArrayBlockingQueue(1)
            val consumer = object : DefaultConsumer(it) {
                override fun handleDelivery(
                    consumerTag: String?,
                    envelope: Envelope?,
                    properties: BasicProperties?,
                    body: ByteArray?) {

                    if (properties?.correlationId == correlationId)
                        response.offer(String(body ?: byteArrayOf()))
                }

                override fun handleCancelOk(consumerTag: String) {
                    log.debug { "Explicit cancel for the consumer $consumerTag" }
                }
            }

            val consumerTag = it.basicConsume(replyQueueName, true, consumer)

            val result: String = response.take() // Wait until there is an element in the array blocking queue
            it.basicCancel(consumerTag)
            result
        }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy