package com.packtpub.druid.firehose; import com.google.common.collect.Maps; import com.metamx.druid.input.InputRow; import com.metamx.druid.input.MapBasedInputRow; import com.metamx.druid.realtime.firehose.Firehose; import com.packtpub.storm.model.FixMessageDto; import com.packtpub.storm.trident.state.DruidPartitionStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class StormFirehose implements Firehose { private static final Logger LOG = LoggerFactory.getLogger(StormFirehose.class); private static final Object START = new Object(); private static final Object FINISHED = new Object(); private static BlockingQueue<FixMessageDto> BLOCKING_QUEUE; public static final DruidPartitionStatus STATUS = new DruidPartitionStatus(); private static String TRANSACTION_ID = null; private static BlockingQueue<String> LIMBO_TRANSACTIONS = new ArrayBlockingQueue<String>(99999); @Override public boolean hasMore() { if (BLOCKING_QUEUE != null && !BLOCKING_QUEUE.isEmpty()) return true; try { synchronized (START) { START.wait(); } } catch (InterruptedException e) { LOG.error("hasMore() blocking was interrupted.", e); } return true; } @Override public InputRow nextRow() { final Map<String, Object> theMap = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); try { FixMessageDto message; message = BLOCKING_QUEUE.poll(); if (message != null) { // LOG.info("[" + message.symbol + "] @ [" + message.price + "] for [" + message.uid + "]"); theMap.put("symbol", message.symbol); theMap.put("price", message.price); } if (BLOCKING_QUEUE.isEmpty()) { STATUS.putInLimbo(TRANSACTION_ID); LIMBO_TRANSACTIONS.add(TRANSACTION_ID); LOG.info("Batch is fully consumed by Druid. Unlocking [FINISH]"); synchronized (FINISHED) { FINISHED.notify(); } } } catch (Exception e) { LOG.error("Error occurred in nextRow.", e); } final LinkedList<String> dimensions = new LinkedList<String>(); dimensions.add("symbol"); dimensions.add("price"); return new MapBasedInputRow(System.currentTimeMillis(), dimensions, theMap); } @Override public Runnable commit() { List<String> limboTransactions = new ArrayList<String>(); LIMBO_TRANSACTIONS.drainTo(limboTransactions); return new StormCommitRunnable(limboTransactions); } public synchronized void sendMessages(String partitionId, List<FixMessageDto> messages) { BLOCKING_QUEUE = new ArrayBlockingQueue<FixMessageDto>(messages.size(), false, messages); TRANSACTION_ID = partitionId; LOG.info("Beginning commit to Druid. [" + messages.size() + "] messages, unlocking [START]"); synchronized (START) { START.notify(); } try { synchronized (FINISHED) { FINISHED.wait(); } } catch (InterruptedException e) { LOG.error("Commit to Druid interrupted."); } LOG.info("Returning control to Storm."); } @Override public void close() throws IOException { // do nothing } }