/** * Copyright 2015 Otto (GmbH & Co KG) * * Licensed 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 com.ottogroup.bi.spqr.operator.kafka.source; import java.util.concurrent.locks.LockSupport; import kafka.consumer.ConsumerIterator; import kafka.consumer.KafkaStream; import kafka.message.MessageAndMetadata; import uk.co.real_logic.queues.MessageWaitStrategy; import uk.co.real_logic.queues.OneToOneConcurrentArrayQueue3; /** * Reads content from an assigned {@link KafkaStream} and writes the data to a provided queue. * @author mnxfst * @since Apr 20, 2015 */ public class KafkaTopicStreamConsumer implements Runnable { /** stream instance to read messages from */ private final KafkaStream<byte[], byte[]> kafkaTopicPartitionStream; /** externally provided queue to use for exchanging messages with underlying kafka emitter */ private final OneToOneConcurrentArrayQueue3<byte[]> messages; /** indicates whether the consumer is running or not */ private boolean running = false; private static final int RETRIES = 200; private final MessageWaitStrategy<byte[]> messageWaitStrategy; /** * Initializes the partition consumer using the provided input * @param kafkaTopicStream * @param messages queue to write received content to * @param messageWaitStrategy optional wait strategy applied when consuming data from queue. if provided it may be used to signal new elements */ public KafkaTopicStreamConsumer(final KafkaStream<byte[], byte[]> kafkaTopicStream, final OneToOneConcurrentArrayQueue3<byte[]> messages, final MessageWaitStrategy<byte[]> messageWaitStrategy) { this.kafkaTopicPartitionStream = kafkaTopicStream; this.messages = messages; this.messageWaitStrategy = messageWaitStrategy; } /** * @see java.lang.Runnable#run() */ public void run() { // fetch message iterator from stream and mark the consumer as 'running' instance ConsumerIterator<byte[], byte[]> topicPartitionStreamIterator = this.kafkaTopicPartitionStream.iterator(); this.running = true; // keep on running ... until told to stop int counter = 0; while(running) { counter = RETRIES; while(!topicPartitionStreamIterator.hasNext()) { counter = waitFor(counter); // TODO provide a configurable wait strategy } // receive the next message from stream MessageAndMetadata<byte[], byte[]> message = topicPartitionStreamIterator.next(); if(message != null && message.message() != null && message.message().length > 0) { // if the message is neither null nor empty, insert it into the queue and signal the wait strategy to // release any existing locks -- if there is a wait strategy provided at all this.messages.add(message.message()); if(messageWaitStrategy != null) messageWaitStrategy.forceLockRelease(); } } } /** * Simple wait strategy to avoid CPU overload scenarios due to active waiting for nothing * @param counter * @return */ protected int waitFor(int counter) { if(counter > 100) --counter; else if(counter > 0) { --counter; Thread.yield(); } else { LockSupport.parkNanos(1l); } return counter; } /** * Shuts down the partition consumer */ public void shutdown() { this.running = false; } }