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.kestra.plugin.pulsar.Produce Maven / Gradle / Ivy
package io.kestra.plugin.pulsar;
import io.kestra.core.models.annotations.Example;
import io.kestra.core.models.annotations.Plugin;
import io.kestra.core.models.annotations.PluginProperty;
import io.kestra.core.models.executions.metrics.Counter;
import io.kestra.core.models.tasks.RunnableTask;
import io.kestra.core.runners.RunContext;
import io.kestra.core.serializers.FileSerde;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.apache.pulsar.client.api.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URI;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import static io.kestra.core.utils.Rethrow.throwFunction;
@SuperBuilder
@ToString
@EqualsAndHashCode
@Getter
@NoArgsConstructor
@io.swagger.v3.oas.annotations.media.Schema(
title = "Produce message in a Pulsar topic"
)
@Plugin(
examples = {
@Example(
title = "Read a csv, transform it to right format & produce it to Pulsar",
full = true,
code = {
"id: produce",
"namespace: io.kestra.tests",
"inputs:",
" - type: FILE",
" name: file",
"",
"tasks:",
" - id: csvReader",
" type: io.kestra.plugin.serdes.csv.CsvReader",
" from: \"{{ inputs.file }}\"",
" - id: fileTransform",
" type: io.kestra.plugin.scripts.nashorn.FileTransform",
" from: \"{{ outputs.csvReader.uri }}\"",
" script: |",
" var result = {",
" \"key\": row.id,",
" \"value\": {",
" \"username\": row.username,",
" \"tweet\": row.tweet",
" },",
" \"eventTime\": row.timestamp,",
" \"properties\": {",
" \"key\": \"value\"",
" }",
" };",
" row = result",
" - id: produce",
" type: io.kestra.plugin.pulsar.Produce",
" from: \"{{ outputs.fileTransform.uri }}\"",
" uri: pulsar://localhost:26650",
" serializer: JSON",
" topic: test_kestra",
}
)
}
)
public class Produce extends AbstractPulsarConnection implements RunnableTask {
@io.swagger.v3.oas.annotations.media.Schema(
title = "Pulsar topic where to send message"
)
@NotNull
@PluginProperty(dynamic = true)
private String topic;
@io.swagger.v3.oas.annotations.media.Schema(
title = "Source of message send",
description = "Can be an internal storage uri, a map or a list." +
"with the following format: key, value, partition, timestamp, headers"
)
@NotNull
@PluginProperty(dynamic = true)
private Object from;
@io.swagger.v3.oas.annotations.media.Schema(
title = "Serializer used for the value"
)
@NotNull
@PluginProperty(dynamic = true)
@Builder.Default
private SerdeType serializer = SerdeType.STRING;
@io.swagger.v3.oas.annotations.media.Schema(
title = "Specify a name for the producer."
)
@PluginProperty(dynamic = true)
private String producerName;
@io.swagger.v3.oas.annotations.media.Schema(
title = "Add all the properties in the provided map to the producer."
)
@PluginProperty(dynamic = true, additionalProperties = String.class)
private Map producerProperties;
@io.swagger.v3.oas.annotations.media.Schema(
title = "Configure the type of access mode that the producer requires on the topic",
description = "Possible values are:\n" +
"* `Shared`: By default multiple producers can publish on a topic\n" +
"* `Exclusive`: Require exclusive access for producer. Fail immediately if there's already a producer connected.\n" +
"* `WaitForExclusive`: Producer creation is pending until it can acquire exclusive access"
)
@PluginProperty
private ProducerAccessMode accessMode;
@io.swagger.v3.oas.annotations.media.Schema(
title = "Add public encryption key, used by producer to encrypt the data key."
)
@PluginProperty(dynamic = true)
private String encryptionKey;
@io.swagger.v3.oas.annotations.media.Schema(
title = "Set the compression type for the producer.",
description = "By default, message payloads are not compressed. Supported compression types are:\n" +
"* `NONE`: No compression (Default)\n" +
"* `LZ4`: Compress with LZ4 algorithm. Faster but lower compression than ZLib\n" +
"* `ZLIB`: Standard ZLib compression\n" +
"* `ZSTD` Compress with Zstandard codec. Since Pulsar 2.3.\n" +
"* `SNAPPY` Compress with Snappy codec. Since Pulsar 2.4."
)
@PluginProperty
private CompressionType compressionType;
@SuppressWarnings("unchecked")
@Override
public Output run(RunContext runContext) throws Exception {
try (PulsarClient client = PulsarService.client(this, runContext)) {
ProducerBuilder producerBuilder = client.newProducer()
.topic(runContext.render(this.topic))
.enableBatching(true);
if (this.producerName != null) {
producerBuilder.producerName(runContext.render(this.producerName));
}
if (this.accessMode != null) {
producerBuilder.accessMode(this.accessMode);
}
if (this.encryptionKey != null) {
producerBuilder.addEncryptionKey(runContext.render(this.encryptionKey));
}
if (this.compressionType != null) {
producerBuilder.compressionType(this.compressionType);
}
if (this.producerProperties != null) {
producerBuilder.properties(this.producerProperties
.entrySet()
.stream()
.map(throwFunction(e -> new AbstractMap.SimpleEntry<>(
runContext.render(e.getKey()),
runContext.render(e.getValue())
)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
);
}
try (Producer producer = producerBuilder.create()) {
Integer count = 1;
if (this.from instanceof String || this.from instanceof List) {
Flowable flowable;
Flowable resultFlowable;
if (this.from instanceof String) {
URI from = new URI(runContext.render((String) this.from));
try (BufferedReader inputStream = new BufferedReader(new InputStreamReader(runContext.uriToInputStream(from)))) {
flowable = Flowable.create(FileSerde.reader(inputStream), BackpressureStrategy.BUFFER);
resultFlowable = this.buildFlowable(flowable, runContext, producer);
count = resultFlowable
.reduce(Integer::sum)
.blockingGet();
}
} else {
flowable = Flowable.fromArray(((List) this.from).toArray());
resultFlowable = this.buildFlowable(flowable, runContext, producer);
count = resultFlowable
.reduce(Integer::sum)
.blockingGet();
}
} else {
this.produceMessage(producer, runContext, (Map) this.from);
}
runContext.metric(Counter.of("records", count));
producer.flush();
return Output.builder()
.messagesCount(count)
.build();
}
}
}
@SuppressWarnings("unchecked")
private Flowable buildFlowable(Flowable flowable, RunContext runContext, Producer producer) {
return flowable
.map(row -> {
this.produceMessage(producer, runContext, (Map) row);
return 1;
});
}
@SuppressWarnings("unchecked")
private CompletableFuture produceMessage(Producer producer, RunContext runContext, Map map) throws Exception {
TypedMessageBuilder message = producer.newMessage();
Map renderedMap = runContext.render(map);
if (renderedMap.containsKey("key")) {
message.key((String) renderedMap.get("key"));
}
if (renderedMap.containsKey("properties")) {
message.properties((Map) renderedMap.get("properties"));
}
if (renderedMap.containsKey("value")) {
message.value(this.serializer.serialize(renderedMap.get("value")));
}
if (renderedMap.containsKey("eventTime")) {
message.eventTime(processTimestamp(renderedMap.get("eventTime")));
}
if (renderedMap.containsKey("deliverAfter")) {
message.deliverAfter(processTimestamp(renderedMap.get("deliverAfter")), TimeUnit.MILLISECONDS);
}
if (renderedMap.containsKey("deliverAt")) {
message.deliverAt(processTimestamp(renderedMap.get("deliverAt")));
}
if (renderedMap.containsKey("sequenceId")) {
message.sequenceId((long) renderedMap.get("sequenceId"));
}
return message.sendAsync();
}
private Long processTimestamp(Object timestamp) {
if (timestamp == null) {
return null;
}
if (timestamp instanceof Long) {
return (Long) timestamp;
}
if (timestamp instanceof ZonedDateTime) {
return ((ZonedDateTime) timestamp).toInstant().toEpochMilli();
}
if (timestamp instanceof Instant) {
return ((Instant) timestamp).toEpochMilli();
}
if (timestamp instanceof LocalDateTime) {
return ((LocalDateTime) timestamp).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
if (timestamp instanceof String) {
try {
return ZonedDateTime.parse((String) timestamp).toInstant().toEpochMilli();
} catch (Exception ignored) {
return Instant.parse((String) timestamp).toEpochMilli();
}
}
throw new IllegalArgumentException("Invalid type of timestamp with type '" + timestamp.getClass() + "'");
}
@Builder
@Getter
public static class Output implements io.kestra.core.models.tasks.Output {
@io.swagger.v3.oas.annotations.media.Schema(
title = "Number of message produced"
)
private final Integer messagesCount;
}
}