1919 */
2020package io .quarkiverse .kafkastreamsprocessor .impl .decorator .processor ;
2121
22+ import static org .apache .kafka .common .record .TimestampType .CREATE_TIME ;
23+
2224import java .util .Optional ;
2325import java .util .Set ;
2426
2729import jakarta .enterprise .context .Dependent ;
2830import jakarta .inject .Inject ;
2931
32+ import org .apache .kafka .clients .consumer .ConsumerRecord ;
3033import org .apache .kafka .common .KafkaException ;
34+ import org .apache .kafka .common .serialization .Serializer ;
3135import org .apache .kafka .streams .processor .To ;
3236import org .apache .kafka .streams .processor .api .ContextualProcessor ;
3337import org .apache .kafka .streams .processor .api .FixedKeyRecord ;
3741import org .apache .kafka .streams .processor .api .RecordMetadata ;
3842import org .apache .kafka .streams .processor .internals .InternalProcessorContext ;
3943
44+ import io .cloudevents .kafka .CloudEventSerializer ;
45+ import io .quarkiverse .kafkastreamsprocessor .api .configuration .Configuration ;
4046import io .quarkiverse .kafkastreamsprocessor .api .decorator .processor .AbstractProcessorDecorator ;
4147import io .quarkiverse .kafkastreamsprocessor .api .decorator .processor .ProcessorDecoratorPriorities ;
42- import io .quarkiverse .kafkastreamsprocessor .impl .TopologyProducer ;
43- import io .quarkiverse .kafkastreamsprocessor .impl .errors .DlqMetadataHandler ;
48+ import io .quarkiverse .kafkastreamsprocessor .impl .errors .DlqProducerService ;
4449import io .quarkiverse .kafkastreamsprocessor .impl .errors .ErrorHandlingStrategy ;
45- import io .quarkiverse .kafkastreamsprocessor .impl .metrics .KafkaStreamsProcessorMetrics ;
4650import io .quarkiverse .kafkastreamsprocessor .spi .SinkToTopicMappingBuilder ;
4751import io .quarkiverse .kafkastreamsprocessor .spi .properties .KStreamsProcessorConfig ;
4852import lombok .AccessLevel ;
@@ -68,56 +72,66 @@ public class DlqDecorator extends AbstractProcessorDecorator {
6872 private final Set <String > functionalSinks ;
6973
7074 /**
71- * Tool to enrich a message metadata before its storage in the dead letter queue
75+ * Whether the dead-letter queue mechanism is activated for this microservice
7276 */
73- private final DlqMetadataHandler dlqMetadataHandler ;
77+ private final boolean activated ;
7478
7579 /**
76- * container of all metrics of the framework
80+ * Delegate that handles DLQ message production
7781 */
78- private final KafkaStreamsProcessorMetrics metrics ;
82+ private final DlqProducerService dlqDelegate ;
7983
8084 /**
81- * Whether the dead-letter queue mechanism is activated for this microservice
85+ * The configuration of the Kafka Streams processor
8286 */
83- private final boolean activated ;
87+ private final Configuration configuration ;
88+
89+ /**
90+ * Class containing the configuration related to kafka streams processor
91+ */
92+ private final KStreamsProcessorConfig kStreamsProcessorConfig ;
8493
8594 /**
8695 * Keeping a reference to the ProcessorContext to be able to use it in the {@link Processor#process(Record)} method
8796 * whilst not implementing the little too narrowing {@link ContextualProcessor}.
8897 */
8998 private ProcessorContext context ;
9099
91- DlqDecorator (Set <String > functionalSinks , DlqMetadataHandler dlqMetadataHandler ,
92- KafkaStreamsProcessorMetrics metrics , boolean activated ) {
100+ DlqDecorator (Set <String > functionalSinks , boolean activated ,
101+ DlqProducerService dlqDelegate ,
102+ Configuration configuration ,
103+ KStreamsProcessorConfig kStreamsProcessorConfig ) {
93104 this .functionalSinks = functionalSinks ;
94- this .dlqMetadataHandler = dlqMetadataHandler ;
95- this .metrics = metrics ;
96105 this .activated = activated ;
106+ this .dlqDelegate = dlqDelegate ;
107+ this .configuration = configuration ;
108+ this .kStreamsProcessorConfig = kStreamsProcessorConfig ;
97109 }
98110
99111 /**
100112 * Injection constructor
101113 *
102114 * @param sinkToTopicMappingBuilder
103115 * utility to get access to the mapping between sinks and Kafka topics
104- * @param dlqMetadataHandler
105- * the enricher of metadata before sending message to the dead letter queue
106- * @param metrics
107- * container of all metrics of the framework
108116 * @param kStreamsProcessorConfig
109117 * It contains the configuration for the error strategy configuration property value (default
110118 * {@link ErrorHandlingStrategy#CONTINUE})
111119 * and the configuration Kafka topic to use for dead letter queue (optional)
120+ * @param dlqDelegate
121+ * delegate that handles DLQ production with properly configured producer
122+ * @param configuration
123+ * the configuration of the kafka streams processor
112124 */
113125 @ Inject
114126 public DlqDecorator (
115- SinkToTopicMappingBuilder sinkToTopicMappingBuilder , DlqMetadataHandler dlqMetadataHandler ,
116- KafkaStreamsProcessorMetrics metrics ,
117- KStreamsProcessorConfig kStreamsProcessorConfig ) { // NOSONAR Optional with microprofile-config
118- this (sinkToTopicMappingBuilder .sinkToTopicMapping ().keySet (), dlqMetadataHandler , metrics ,
127+ SinkToTopicMappingBuilder sinkToTopicMappingBuilder ,
128+ KStreamsProcessorConfig kStreamsProcessorConfig ,
129+ DlqProducerService dlqDelegate ,
130+ Configuration configuration ) { // NOSONAR Optional with microprofile-config
131+ this (sinkToTopicMappingBuilder .sinkToTopicMapping ().keySet (),
119132 ErrorHandlingStrategy .shouldSendToDlq (kStreamsProcessorConfig .errorStrategy (),
120- kStreamsProcessorConfig .dlq ().topic ()));
133+ kStreamsProcessorConfig .dlq ().topic ()),
134+ dlqDelegate , configuration , kStreamsProcessorConfig );
121135 }
122136
123137 /**
@@ -158,11 +172,30 @@ public void process(Record record) {
158172 } catch (RuntimeException e ) { // NOSONAR
159173 Optional <RecordMetadata > recordMetadata = context .recordMetadata ();
160174 if (recordMetadata .isPresent ()) {
161- dlqMetadataHandler .addMetadata (record .headers (), recordMetadata .get ().topic (),
162- recordMetadata .get ().partition (), e );
163- context .forward (record , TopologyProducer .DLQ_SINK_NAME );
164- // Re-throw so the exception gets logged
165- metrics .microserviceDlqSentCounter ().increment ();
175+ Serializer <Object > keySerializer = (Serializer <Object >) configuration .getSourceKeySerde ().serializer ();
176+ Serializer <Object > valueSerializer = (Serializer <Object >) getSourceValueSerializer (configuration ,
177+ kStreamsProcessorConfig );
178+ byte [] serializedKey = keySerializer .serialize (recordMetadata .get ().topic (), record .headers (),
179+ record .key ());
180+ byte [] serializedValue = valueSerializer .serialize (recordMetadata .get ().topic (), record .headers (),
181+ record .value ());
182+
183+ ConsumerRecord <byte [], byte []> consumerRecord = new ConsumerRecord <>(
184+ recordMetadata .get ().topic (),
185+ recordMetadata .get ().partition (),
186+ recordMetadata .get ().offset (),
187+ record .timestamp (),
188+ CREATE_TIME ,
189+ serializedKey != null ? serializedKey .length : 0 ,
190+ serializedValue != null ? serializedValue .length : 0 ,
191+ serializedKey ,
192+ serializedValue ,
193+ record .headers (),
194+ Optional .empty ());
195+
196+ dlqDelegate .sendToDlq (consumerRecord , e , context .taskId (), false );
197+
198+ // throw exception to fail OpenTelemetry span
166199 throw e ;
167200 }
168201 }
@@ -171,6 +204,17 @@ public void process(Record record) {
171204 }
172205 }
173206
207+ Serializer <?> getSourceValueSerializer (Configuration configuration ,
208+ KStreamsProcessorConfig kStreamsProcessorConfig ) {
209+ // defaulting to CloudEventSerializer if kafkastreamsprocessor.input.is-cloud-event is true
210+ if (kStreamsProcessorConfig .input ().isCloudEvent ()) {
211+ CloudEventSerializer serializer = new CloudEventSerializer ();
212+ serializer .configure (kStreamsProcessorConfig .dlq ().cloudEventSerializerConfig (), false );
213+ return serializer ;
214+ }
215+ return configuration .getSourceValueSerde ().serializer ();
216+ }
217+
174218 @ RequiredArgsConstructor (access = AccessLevel .MODULE )
175219 static final class DlqProcessorContextDecorator <KOut , VOut > implements InternalProcessorContext <KOut , VOut > {
176220
0 commit comments