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

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

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

import com.hexagonkt.core.converters.convert
import com.hexagonkt.core.retry
import com.hexagonkt.core.logging.Logger
import com.hexagonkt.core.media.MediaType
import com.hexagonkt.serialization.SerializationManager.formatOfOrNull
import com.hexagonkt.serialization.SerializationManager.requireDefaultFormat
import com.hexagonkt.serialization.parse
import com.hexagonkt.serialization.serialize
import com.rabbitmq.client.AMQP.BasicProperties
import com.rabbitmq.client.Channel
import com.rabbitmq.client.ConnectionFactory
import com.rabbitmq.client.DefaultConsumer
import com.rabbitmq.client.Envelope
import com.rabbitmq.client.ShutdownSignalException
import java.nio.charset.Charset
import java.nio.charset.Charset.defaultCharset
import java.util.concurrent.ExecutorService
import kotlin.reflect.KClass

/**
 * Message handler that can reply messages to a reply queue.
 *
 * TODO Test content type support.
 */
internal class Handler internal constructor (
    connectionFactory: ConnectionFactory,
    channel: Channel,
    private val executor: ExecutorService,
    private val type: KClass,
    private val handler: (T) -> R) : DefaultConsumer(channel) {

    private companion object {

        private const val RETRIES = 5
        private const val DELAY = 50L
    }

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

    private val client: RabbitMqClient by lazy { RabbitMqClient(connectionFactory) }

    /** @see DefaultConsumer.handleDelivery */
    override fun handleDelivery(
        consumerTag: String, envelope: Envelope, properties: BasicProperties, body: ByteArray) {

        executor.execute {
            val charset = properties.contentEncoding ?: defaultCharset().name()
            val correlationId = properties.correlationId
            val replyTo = properties.replyTo
            val contentType = properties.contentType
                ?.let { MediaType(it) }
                ?.let { formatOfOrNull(it) }
                ?: requireDefaultFormat()

            try {
                log.trace { "Received message ($correlationId) in $charset" }
                val request = String(body, Charset.forName(charset))
                log.trace { "Message body:\n$request" }
                val input = request.parse(contentType).convert(type)

                val response = handler(input)

                if (replyTo != null)
                    handleResponse(response, replyTo, correlationId)
            }
            catch (ex: Exception) {
                log.warn(ex) { "Error processing message ($correlationId) in $charset" }

                if (replyTo != null)
                    handleError(ex, replyTo, correlationId)
            }
            finally {
                retry(RETRIES, DELAY) {
                    channel.basicAck(envelope.deliveryTag, false)
                }
            }
        }
    }

    /** @see DefaultConsumer.handleCancel */
    override fun handleCancel(consumerTag: String?) {
        log.error { "Unexpected cancel for the consumer $consumerTag" }
    }

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

    /** @see DefaultConsumer.handleShutdownSignal */
    override fun handleShutdownSignal(consumerTag: String, sig: ShutdownSignalException) {
        if (sig.isInitiatedByApplication) {
            log.debug { "Consumer $consumerTag: shutdown is initiated by application. Ignoring it" }
        }
        else {
            val msg = sig.localizedMessage ?: ""
            log.debug { "Consumer $consumerTag shutdown error $msg" }
        }
    }

    /** @see DefaultConsumer.handleConsumeOk */
    override fun handleConsumeOk(consumerTag: String) {
        log.debug { "Consumer $consumerTag has been registered" }
    }

    private fun handleResponse(response: R, replyTo: String, correlationId: String?) {
        val output = when (response) {
            is String -> response
            is Int -> response.toString()
            is Long -> response.toString()
            else -> response.serialize()
        }

        client.publish(replyTo, output, correlationId)
    }

    private fun handleError(exception: Exception, replyTo: String, correlationId: String?) {
        val message = exception.message ?: ""
        val errorMessage = message.ifBlank { exception.javaClass.name }
        client.publish(replyTo, errorMessage, correlationId)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy