/* * Copyright (c) 2016 Red Hat, Inc. and/or its affiliates. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cheng Fang - Initial API and implementation */ package org.jberet.support.io; import java.io.Serializable; import java.util.List; import javax.batch.api.BatchProperty; import javax.batch.api.chunk.ItemWriter; import javax.enterprise.context.Dependent; import javax.inject.Inject; import javax.inject.Named; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import org.jberet.support._private.SupportMessages; /** * An implementation of {@code ItemWriter} that sends data items to Kafka {@code TopicPartition} as specified in batch * property {@link #topicPartition}. * * @see KafkaItemReader * @see KafkaItemReaderWriterBase * * @since 1.3.0 */ @Named @Dependent public class KafkaItemWriter extends KafkaItemReaderWriterBase implements ItemWriter { /** * A topic partition in the form of {@code <topicName>:<partitionNumber>}. For example, "orders:0". * Unlike {@link KafkaItemReader}, which accepts multiple {@code TopicPartition} as source, this writer class only * accepts 1 {@code TopicPartition} as destination. * * @see KafkaItemReaderWriterBase#topicPartitionDelimiter * @see "org.apache.kafka.common.TopicPartition" */ @Inject @BatchProperty protected String topicPartition; /** * The key used when sending {@code ProducerRecord}. * * @see "org.apache.kafka.clients.producer.ProducerRecord" */ @Inject @BatchProperty protected String recordKey; /** * The Kafka producer responsible for sending the records. */ protected KafkaProducer producer; /** * The topic name extracted from {@link #topicPartition}. This field is used as the default destination topic name. * Subclass may override method {@link #getTopic(Object)} to provide the topic name differently. */ private String topic; /** * The partition number extracted from {@link #topicPartition}. This field is used as the default destination * partition number. Subclass may override method {@link #getPartition(Object)} to provide the partition number * differently. */ private Integer partition; /** * During the writer opening, the Kafka producer is instantiated, based on the configuration properties as specified * in the batch property {@link #configFile}. * * @param checkpoint item writer checkpoint data, currently not used * @throws Exception if error occurs */ @Override public void open(final Serializable checkpoint) throws Exception { producer = new KafkaProducer(createConfigProperties()); if (topicPartition == null) { throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, null, "topicPartition"); } final int colonPos = topicPartition.lastIndexOf(topicPartitionDelimiter); if (colonPos > 0) { topic = topicPartition.substring(0, colonPos); partition = Integer.valueOf(topicPartition.substring(colonPos + 1)); } else if (colonPos < 0) { topic = topicPartition; } else { throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, topicPartition, "topicPartition"); } } /** * Creates Kafka {@code ProducerRecord} and sends it to Kafka topic partition for each item in data {@code items}. * * @param items data items to be sent to Kafka server * * @throws Exception if error occurs */ @Override @SuppressWarnings("unchecked") public void writeItems(final List<Object> items) throws Exception { for (final Object item : items) { producer.send(new ProducerRecord(getTopic(item), getPartition(item), getRecordKey(item), item)); } } /** * Returns null checkpoint info for this writer. * * @return null checkpoint info */ @Override public Serializable checkpointInfo() { return null; } /** * Closes the Kafka producer. */ @Override public void close() { if (producer != null) { producer.close(); producer = null; } } /** * Gets the destination topic used when sending {@code ProducerRecord}. * Subclass may override this method to provide a suitable topic. * The default implementation returns a value based on the injected field {@link #topicPartition}. * * @param item the item currently being sent * * @return topic used for sending the current {@code ProducerRecord} */ @SuppressWarnings("unused") protected String getTopic(final Object item) { return topic; } /** * Gets the destination topic partition used when sending {@code ProducerRecord}. * Subclass may override this method to provide a suitable topic partition number. * The default implementation returns a value based on the injected field {@link #topicPartition}. * * @param item the item currently being sent * * @return topic partition used for sending the current {@code ProducerRecord} */ @SuppressWarnings("unused") protected Integer getPartition(final Object item) { return partition; } /** * Gets the key used when sending {@code ProducerRecord}. * Subclass may override this method to provide a suitable key. * The default implementation returns the injected value of field {@link #recordKey}. * * @param item the item currently being sent * * @return a key used for sending the current {@code ProducerRecord} */ @SuppressWarnings("unused") protected String getRecordKey(final Object item) { return recordKey; } }