content: /**
   * 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 io.jafka.producer.async;
  
  import io.jafka.common.IllegalQueueStateException;
  import io.jafka.producer.SyncProducer;
  import io.jafka.producer.serializer.Encoder;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.BlockingQueue;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.TimeUnit;
  
  import static java.lang.String.format;
  
  /**
   * @author adyliu (imxylz@gmail.com)
   * @since 1.0
   */
  public class ProducerSendThread<T> extends Thread {
  
      final String threadName;
  
      final BlockingQueue<QueueItem<T>> queue;
  
      final Encoder<T> serializer;
  
      final SyncProducer underlyingProducer;
  
      final EventHandler<T> eventHandler;
  
      final CallbackHandler<T> callbackHandler;
  
      final long queueTime;
  
      final int batchSize;
  
      private final Logger logger = LoggerFactory.getLogger(ProducerSendThread.class);
  
      /////////////////////////////////////////////////////////////////////
      private final CountDownLatch shutdownLatch = new CountDownLatch(1);
  
      private volatile boolean shutdown = false;
  
      public ProducerSendThread(String threadName, //
                                BlockingQueue<QueueItem<T>> queue, //
                                Encoder<T> serializer, //
                                SyncProducer underlyingProducer,//
                                EventHandler<T> eventHandler, //
                                CallbackHandler<T> callbackHandler, //
                                long queueTime, //
                                int batchSize) {
          super();
          this.threadName = threadName;
          this.queue = queue;
          this.serializer = serializer;
          this.underlyingProducer = underlyingProducer;
          this.eventHandler = eventHandler;
          this.callbackHandler = callbackHandler;
          this.queueTime = queueTime;
          this.batchSize = batchSize;
      }
  
      @Override
      public void run() {
          try {
              List<QueueItem<T>> remainingEvents = processEvents();
              //handle remaining events
              if (remainingEvents.size() > 0) {
                  logger.debug(format("Dispatching last batch of %d events to the event handler", remainingEvents.size()));
                  tryToHandle(remainingEvents);
              }
          } catch (Exception e) {
              logger.error("Error in sending events: ", e);
          } finally {
              shutdownLatch.countDown();
          }
      }
  
      public void awaitShutdown() {
          try {
              shutdownLatch.await();
          } catch (InterruptedException e) {
              logger.warn(e.getMessage());
          }
      }
  
      public void shutdown() {
          shutdown = true;
          eventHandler.close();
          logger.info("Shutdown thread complete");
      }
  
      private List<QueueItem<T>> processEvents() {
          long lastSend = System.currentTimeMillis();
          final List<QueueItem<T>> events = new ArrayList<QueueItem<T>>();
          boolean full = false;
          while (!shutdown) {
              try {
                  QueueItem<T> item = queue.poll(Math.max(0, (lastSend + queueTime) - System.currentTimeMillis()), TimeUnit.MILLISECONDS);
                  long elapsed = System.currentTimeMillis() - lastSend;
                  boolean expired = item == null;
                  if (item != null) {
                      if (callbackHandler != null) {
                          List<QueueItem<T>> items = callbackHandler.afterDequeuingExistingData(item);
                          if (items != null) {
                              events.addAll(items);
                          }
                      } else {
                          events.add(item);
                      }
                      //
                      full = events.size() >= batchSize;
                  }
                  if (full || expired) {
                      if (logger.isDebugEnabled()) {
                          if (expired) {
                              logger.debug(elapsed + " ms elapsed. Queue time reached. Sending..");
                          } else {
                              logger.debug(format("Batch(%d) full. Sending..", batchSize));
                          }
                      }
                      tryToHandle(events);
                      lastSend = System.currentTimeMillis();
                      events.clear();
                  }
              } catch (InterruptedException e) {
                  logger.warn(e.getMessage(), e);
              }
          }
          if (queue.size() > 0) {
              throw new IllegalQueueStateException("Invalid queue state! After queue shutdown, " + queue.size() + " remaining items in the queue");
          }
          if (this.callbackHandler != null) {
              List<QueueItem<T>> remainEvents = this.callbackHandler.lastBatchBeforeClose();
              if (remainEvents != null) {
                  events.addAll(remainEvents);
              }
          }
          return events;
      }
  
      private void tryToHandle(List<QueueItem<T>> events) {
          if (logger.isDebugEnabled()) {
              logger.debug("handling " + events.size() + " events");
          }
          if (events.size() > 0) {
              try {
                  this.eventHandler.handle(events, underlyingProducer, serializer);
              } catch (RuntimeException e) {
                  logger.error("Error in handling batch of " + events.size() + " events", e);
              }
          }
      }
  }
  