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

io.axoniq.inspector.eventprocessor.DeadLetterManager.kt Maven / Gradle / Ivy

/*
 * Copyright (c) 2022-2023. Inspector Axon
 *
 * 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 io.axoniq.inspector.eventprocessor

import org.axonframework.config.EventProcessingConfiguration
import org.axonframework.eventhandling.EventMessage
import org.axonframework.messaging.deadletter.DeadLetter
import org.axonframework.messaging.deadletter.SequencedDeadLetterQueue
import org.axonframework.serialization.Serializer
import io.axoniq.inspector.api.DeadLetter as ApiDeadLetter

private const val LETTER_PAYLOAD_SIZE_LIMIT = 1024

class DeadLetterManager(
    private val eventProcessingConfig: EventProcessingConfiguration,
    private val eventSerializer: Serializer
) {

    fun deadLetters(
        processingGroup: String,
        offset: Int = 0,
        size: Int = 25,
        maxSequenceLetters: Int = 10
    ): List> = dlqFor(processingGroup)
        .deadLetters()
        .drop(offset)
        .take(size)
        .map { sequence ->
            sequence
                .asIterable()
                .take(maxSequenceLetters)
                .map { toDeadLetter(it, processingGroup) }
        }

    private fun toDeadLetter(letter: DeadLetter>, processingGroup: String) =
        letter.toApiLetter(sequenceIdentifierFor(processingGroup, letter))

    private fun DeadLetter>.toApiLetter(sequenceIdentifier: String) = ApiDeadLetter(
        this.message().identifier,
        serializePayload(),
        this.message().payloadType.simpleName,
        this.cause().map { it.type() }.orElse(null),
        this.cause().map { it.message() }.orElse(null),
        this.enqueuedAt(),
        this.lastTouched(),
        this.diagnostics(),
        sequenceIdentifier
    )

    private fun DeadLetter>.serializePayload() =
        try {
            eventSerializer
                .serialize(this.message().payload, String::class.java)
                .data
        } catch (_: Exception) {
            this.message().payload.toString()
        }.take(LETTER_PAYLOAD_SIZE_LIMIT)

    private fun sequenceIdentifierFor(
        processingGroup: String,
        letter: DeadLetter>
    ): String = eventProcessingConfig
        .sequencingPolicy(processingGroup)
        .getSequenceIdentifierFor(letter.message())
        ?.let {
            if (it is String) it else it.hashCode().toString()
        }
        ?: letter.message().identifier

    fun sequenceSize(
        processingGroup: String,
        sequenceIdentifier: String
    ) = dlqFor(processingGroup).sequenceSize(sequenceIdentifier)

    fun delete(
        processingGroup: String,
        sequenceIdentifier: String,
    ) {
        val dlq = dlqFor(processingGroup)
        dlq.deadLetterSequence(sequenceIdentifier)
            .forEach { dlq.evict(it) }
    }

    fun delete(
        processingGroup: String,
        sequenceIdentifier: String,
        messageIdentifier: String
    ) {
        val dlq = dlqFor(processingGroup)
        dlq.deadLetterSequence(sequenceIdentifier)
            .first { it.message().identifier == messageIdentifier }
            .let { dlq.evict(it) }
    }

    private fun dlqFor(processingGroup: String): SequencedDeadLetterQueue> =
        eventProcessingConfig
            .deadLetterQueue(processingGroup)
            .orElseThrow {
                IllegalArgumentException(
                    "There's no dead-letter queue configured for Processing Group [$processingGroup]!"
                )
            }

    fun process(
        processingGroup: String,
        messageIdentifier: String
    ): Boolean {
        return letterProcessorFor(processingGroup).process { it.message().identifier == messageIdentifier }
    }

    private fun letterProcessorFor(processingGroup: String) =
        eventProcessingConfig
            .sequencedDeadLetterProcessor(processingGroup)
            .orElseThrow {
                IllegalArgumentException(
                    "There's no dead-letter queue configured for Processing Group [$processingGroup]!"
                )
            }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy