All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.axoniq.console.framework.eventprocessor.DeadLetterManager.kt Maven / Gradle / Ivy
/*
* Copyright (c) 2022-2024. AxonIQ B.V.
*
* 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.console.framework.eventprocessor
import io.axoniq.console.framework.AxoniqConsoleDlqMode
import org.apache.commons.codec.digest.DigestUtils
import org.axonframework.config.EventProcessingConfiguration
import org.axonframework.eventhandling.EventMessage
import org.axonframework.messaging.MetaData
import org.axonframework.messaging.deadletter.DeadLetter
import org.axonframework.messaging.deadletter.SequencedDeadLetterQueue
import org.axonframework.serialization.Serializer
import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit
import io.axoniq.console.framework.api.DeadLetter as ApiDeadLetter
private const val LETTER_PAYLOAD_SIZE_LIMIT = 1024
private const val MASKED = ""
private const val LIMITED = ""
class DeadLetterManager(
private val eventProcessingConfig: EventProcessingConfiguration,
private val eventSerializer: Serializer,
private val dlqMode: AxoniqConsoleDlqMode,
private val dlqDiagnosticsWhitelist: List,
private val executor: ExecutorService,
) {
fun deadLetters(
processingGroup: String,
offset: Int = 0,
size: Int = 25,
maxSequenceLetters: Int = 10
): List> {
if (dlqMode == AxoniqConsoleDlqMode.NONE) {
return emptyList()
}
return 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): io.axoniq.console.framework.api.DeadLetter {
if (dlqMode == AxoniqConsoleDlqMode.MASKED) {
return ApiDeadLetter(
this.message().identifier,
MASKED,
this.message().payloadType.simpleName,
this.cause().map { it.type() }.orElse(null),
this.cause().map { MASKED }.orElse(null),
this.enqueuedAt(),
this.lastTouched(),
MetaData.emptyInstance(),
sequenceIdentifier.hashIfNeeded()
)
} else if (dlqMode == AxoniqConsoleDlqMode.LIMITED) {
return ApiDeadLetter(
this.message().identifier,
LIMITED,
this.message().payloadType.simpleName,
this.cause().map { it.type() }.orElse(null),
this.cause().map { LIMITED }.orElse(null),
this.enqueuedAt(),
this.lastTouched(),
this.diagnostics().filtered(),
sequenceIdentifier
)
}
return 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 executor.submit(Callable {
letterProcessorFor(processingGroup).process {
it.message().identifier.hashIfNeeded() == messageIdentifier
}
}).get(60, TimeUnit.SECONDS)
}
private fun String.hashIfNeeded(): String {
return if (dlqMode == AxoniqConsoleDlqMode.MASKED) {
DigestUtils.sha256Hex(this)
} else {
this
}
}
private fun letterProcessorFor(processingGroup: String) =
eventProcessingConfig
.sequencedDeadLetterProcessor(processingGroup)
.orElseThrow {
IllegalArgumentException(
"There's no dead-letter queue configured for Processing Group [$processingGroup]!"
)
}
private fun MetaData.filtered() = this.subset(*dlqDiagnosticsWhitelist.toTypedArray())
}