io.kestra.plugin.kafka.RealtimeTrigger Maven / Gradle / Ivy
package io.kestra.plugin.kafka;
import io.kestra.core.models.annotations.Example;
import io.kestra.core.models.annotations.Plugin;
import io.kestra.core.models.conditions.ConditionContext;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.models.triggers.AbstractTrigger;
import io.kestra.core.models.triggers.RealtimeTriggerInterface;
import io.kestra.core.models.triggers.TriggerContext;
import io.kestra.core.models.triggers.TriggerOutput;
import io.kestra.core.models.triggers.TriggerService;
import io.kestra.core.runners.RunContext;
import io.kestra.plugin.kafka.serdes.SerdeType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.errors.WakeupException;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@SuperBuilder
@ToString
@EqualsAndHashCode
@Getter
@NoArgsConstructor
@Schema(
title = "Consume a message in real-time from a Kafka topic and create one execution per message.",
description = "If you would like to consume multiple messages processed within a given time frame and process them in batch, you can use the [io.kestra.plugin.kafka.Trigger](https://kestra.io/plugins/plugin-kafka/triggers/io.kestra.plugin.kafka.trigger) instead."
)
@Plugin(
examples = {
@Example(
title = "Consume a message from a Kafka topic in real time.",
full = true,
code = """
id: kafka_realtime_trigger
namespace: company.team
tasks:
- id: log
type: io.kestra.plugin.core.log.Log
message: "{{ trigger.value }}"
triggers:
- id: realtime_trigger
type: io.kestra.plugin.kafka.RealtimeTrigger
topic: test_kestra
properties:
bootstrap.servers: localhost:9092
serdeProperties:
schema.registry.url: http://localhost:8085
keyDeserializer: STRING
valueDeserializer: AVRO
groupId: kafkaConsumerGroupId"""
)
}
)
public class RealtimeTrigger extends AbstractTrigger implements RealtimeTriggerInterface, TriggerOutput, KafkaConnectionInterface, KafkaConsumerInterface {
private Map properties;
@Builder.Default
private Map serdeProperties = Collections.emptyMap();
private Object topic;
private List partitions;
private String topicPattern;
@NotNull
private String groupId;
@Builder.Default
private SerdeType keyDeserializer = SerdeType.STRING;
@Builder.Default
private SerdeType valueDeserializer = SerdeType.STRING;
private String since;
@Builder.Default
@Getter(AccessLevel.NONE)
private final AtomicBoolean isActive = new AtomicBoolean(true);
@Builder.Default
@Getter(AccessLevel.NONE)
private final CountDownLatch waitForTermination = new CountDownLatch(1);
@Builder.Default
@Getter(AccessLevel.NONE)
private final AtomicReference> consumer = new AtomicReference<>();
@Override
public Publisher evaluate(ConditionContext conditionContext, TriggerContext context) {
RunContext runContext = conditionContext.getRunContext();
Consume task = Consume.builder()
.id(this.id)
.type(Consume.class.getName())
.properties(this.properties)
.serdeProperties(this.serdeProperties)
.topic(this.topic)
.topicPattern(this.topicPattern)
.partitions(this.partitions)
.groupId(this.groupId)
.keyDeserializer(this.keyDeserializer)
.valueDeserializer(this.valueDeserializer)
.since(this.since)
.build();
return Flux.from(publisher(task, runContext))
.map((record) -> TriggerService.generateRealtimeExecution(this, conditionContext, context, task.recordToMessage(record)));
}
public Publisher> publisher(final Consume task,
final RunContext runContext) {
return Flux.create(fluxSink -> {
try (KafkaConsumer