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.trident.state.DruidBatchStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import storm.trident.tuple.TridentTuple; 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<TridentTuple> BLOCKING_QUEUE; public static final DruidBatchStatus STATUS = new DruidBatchStatus(); private static Long TRANSACTION_ID = null; private static BlockingQueue<Long> LIMBO_TRANSACTIONS = new ArrayBlockingQueue<Long>(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 { TridentTuple tuple = null; tuple = BLOCKING_QUEUE.poll(); if (tuple != null) { String phrase = (String) tuple.getValue(0); String word = (String) tuple.getValue(2); Long baseline = (Long) tuple.getValue(3); theMap.put("searchphrase", phrase); theMap.put("word", word); theMap.put("baseline", baseline); } 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("searchphrase"); dimensions.add("word"); return new MapBasedInputRow(System.currentTimeMillis(), dimensions, theMap); } @Override public Runnable commit() { List<Long> limboTransactions = new ArrayList<Long>(); LIMBO_TRANSACTIONS.drainTo(limboTransactions); return new StormCommitRunnable(limboTransactions); } public synchronized void sendBatch(Long txId, List<TridentTuple> tuples) { BLOCKING_QUEUE = new ArrayBlockingQueue<TridentTuple>(tuples.size(), false, tuples); TRANSACTION_ID = txId; LOG.error("Beginning commit to Druid. [" + tuples.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 } }