
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