dlg99 commented on a change in pull request #9927: URL: https://github.com/apache/pulsar/pull/9927#discussion_r597323899
########## File path: pulsar-io/kafka-connect-adaptor/src/main/java/org/apache/pulsar/io/kafka/connect/KafkaConnectSink.java ########## @@ -0,0 +1,277 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.pulsar.io.kafka.connect; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.OffsetAndMetadata; +import org.apache.kafka.common.TopicPartition; +import org.apache.kafka.common.record.TimestampType; +import org.apache.kafka.connect.connector.Task; +import org.apache.kafka.connect.data.Schema; +import org.apache.kafka.connect.sink.SinkConnector; +import org.apache.kafka.connect.sink.SinkRecord; +import org.apache.kafka.connect.sink.SinkTask; +import org.apache.pulsar.functions.api.Record; +import org.apache.pulsar.io.core.KeyValue; +import org.apache.pulsar.io.core.Sink; +import org.apache.pulsar.io.core.SinkContext; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.pulsar.io.kafka.connect.PulsarKafkaWorkerConfig.OFFSET_STORAGE_TOPIC_CONFIG; +import static org.apache.pulsar.io.kafka.connect.PulsarKafkaWorkerConfig.PULSAR_SERVICE_URL_CONFIG; + +@Slf4j +public class KafkaConnectSink implements Sink<Object> { + + private boolean unwrapKeyValueIfAvailable; + + private final static ImmutableMap<Class<?>, Schema> primitiveTypeToSchema; + static { + primitiveTypeToSchema = ImmutableMap.<Class<?>, Schema>builder() + .put(Boolean.class, Schema.BOOLEAN_SCHEMA) + .put(Byte.class, Schema.INT8_SCHEMA) + .put(Short.class, Schema.INT16_SCHEMA) + .put(Integer.class, Schema.INT32_SCHEMA) + .put(Long.class, Schema.INT64_SCHEMA) + .put(Float.class, Schema.FLOAT32_SCHEMA) + .put(Double.class, Schema.FLOAT64_SCHEMA) + .put(String.class, Schema.STRING_SCHEMA) + .put(byte[].class, Schema.BYTES_SCHEMA) + .build(); + } + + private PulsarKafkaSinkContext sinkContext; + private PulsarKafkaSinkTaskContext taskContext; + private SinkConnector connector; + private SinkTask task; + + private Schema defaultKeySchema; + private Schema defaultValueSchema; + + + private int batchSize; + private long lingerMs; + private final ScheduledExecutorService scheduledExecutor = + Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder() + .setNameFormat("pulsar-io-kafka-adaptor-sink-flush-%d") + .build()); + private final AtomicInteger numPendingRecords = new AtomicInteger(0); + + private volatile CompletableFuture<Void> pendingFlush = new CompletableFuture<>(); + private volatile boolean isRunning = false; + + private Properties props = new Properties(); + private PulsarKafkaConnectSinkConfig kafkaSinkConfig; + + protected String topicName; + + @Override + public void write(Record<Object> sourceRecord) { + if (log.isDebugEnabled()) { + log.debug("Record sending to kafka, record={}.", sourceRecord); + } + + if (!isRunning) { + log.error("Sink is stopped. Cannot send the record {}", sourceRecord); + sourceRecord.fail(); + return; + } + + try { + SinkRecord record = toSinkRecord(sourceRecord); + task.put(Lists.newArrayList(record)); + } catch (Exception ex) { + log.error("Error sending the record {}", sourceRecord, ex); + sourceRecord.fail(); + return; + } + pendingFlush.whenComplete((ignore, ex) -> { + if (ex == null) { + sourceRecord.ack(); + } else { + log.error("Error sending the record {}", sourceRecord, ex); + sourceRecord.fail(); + } + throw new IllegalArgumentException(); + }); + numPendingRecords.incrementAndGet(); + flushIfNeeded(false); + } + + @Override + public void close() throws Exception { + isRunning = false; + flushIfNeeded(true); + scheduledExecutor.shutdown(); + if (!scheduledExecutor.awaitTermination(10 * lingerMs, TimeUnit.MILLISECONDS)) { + log.error("scheduledExecutor did not terminate in {} ms", 10 * lingerMs); + } + + task.stop(); + connector.stop(); + taskContext.close(); + + log.info("Kafka sink stopped."); + } + + @Override + public void open(Map<String, Object> config, SinkContext ctx) throws Exception { Review comment: Maybe I misunderstood something but: SubscriptionType is not passed as part of the context. Existing Pulsar Kafka Sink (KafkaBytesSink) does not enforce specific subscription model so I'll skip dealing with wiring this info through the context and enforcing it (in both sinks) for after this PR is done. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org