package org.calrissian.flowbox.support; import org.apache.commons.lang.StringUtils; import org.calrissian.flowbox.model.Event; import org.calrissian.flowbox.model.Tuple; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.concurrent.LinkedBlockingDeque; import static java.lang.System.currentTimeMillis; public class Window { private String groupedIndex; // a unique key given to the groupBy field/value combinations in the window buffer private Deque<WindowItem> events; // using standard array list for proof of concept. // Circular buffer needs to be used after concept is proven private int triggerTicks = 0; /** * A sliding window buffer which automatically evicts by count */ public Window(String groupedIndex, long size) { events = new LimitingDeque<WindowItem>(size); this.groupedIndex = groupedIndex; } public Window(String groupedIndex) { events = new LinkedBlockingDeque<WindowItem>(); this.groupedIndex = groupedIndex; } public WindowItem add(Event event, String previousStream) { WindowItem item = new WindowItem(event, currentTimeMillis(), previousStream); events.add(item); return item; } /** * Used for age-based expiration */ public void timeEvict(long thresholdInSeconds) { while(events != null && events.peek() != null && (System.currentTimeMillis() - events.peek().getTimestamp()) >= (thresholdInSeconds * 1000)) events.poll(); } public void resetTriggerTicks() { triggerTicks = 0; } public int getTriggerTicks() { return triggerTicks; } public void incrTriggerTicks() { triggerTicks += 1; } public String getGroupedIndex() { return groupedIndex; } /** * Returns the difference(in millis) between the HEAD & TAIL timestamps. */ public long timeRange() { if(events.size() <= 1) return -1; return events.getLast().getTimestamp() - events.getFirst().getTimestamp(); } public Iterable<WindowItem> getEvents() { return events; } public int size() { return events.size(); } /** * Used for count-based expiration */ public WindowItem expire() { WindowItem item = events.removeLast(); return item; } public static String buildKeyIndexForEvent(Event event, List<String> groupBy) { StringBuffer stringBuffer = new StringBuffer(); if(groupBy == null || groupBy.size() == 0) { return ""; // default partition when no groupBy fields are specified. } for(String groupField : groupBy) { Set<Tuple> tuples = event.getAll(groupField); SortedSet<String> values = new TreeSet<String>(); if(tuples == null) { values.add(""); } else { for(Tuple tuple : tuples) values.add(tuple.getValue().toString()); // toString() for now until we have something better } stringBuffer.append(groupBy + StringUtils.join(values, "")); } try { return hashString(stringBuffer.toString()); } catch (Exception e) { return null; } } public static String hashString(String string) throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] hash = md.digest(string.getBytes("UTF-8")); //converting byte array to Hexadecimal String StringBuilder sb = new StringBuilder(2*hash.length); for(byte b : hash) sb.append(String.format("%02x", b&0xff)); return sb.toString(); } public void clear() { events.clear(); } @Override public String toString() { return "Window{" + "groupedIndex='" + groupedIndex + '\'' + ", size=" + events.size() + ", events=" + events + ", triggertTicks=" + triggerTicks + '}'; } }