/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * *************************************************************************************** */ package com.espertech.esper.view.ext; import com.espertech.esper.client.EventBean; import com.espertech.esper.client.EventType; import com.espertech.esper.collection.MultiKeyUntyped; import com.espertech.esper.collection.OneEventCollection; import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext; import com.espertech.esper.epl.expression.core.ExprEvaluator; import com.espertech.esper.epl.expression.core.ExprEvaluatorContext; import com.espertech.esper.epl.expression.core.ExprNode; import com.espertech.esper.metrics.instrumentation.InstrumentationHelper; import com.espertech.esper.util.CollectionUtil; import com.espertech.esper.view.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** * Window sorting by values in the specified field extending a specified number of elements * from the lowest value up or the highest value down and retaining only the last unique value per key. * <p> * The type of the field to be sorted in the event must implement the Comparable interface. * <p> * The natural order in which events arrived is used as the second sorting criteria. Thus should events arrive * with equal sort values the oldest event leaves the sort window first. * <p> * Old values removed from a another view are removed from the sort view. */ public class RankWindowView extends ViewSupport implements DataWindowView, CloneableView { private final RankWindowViewFactory rankWindowViewFactory; protected final ExprEvaluator[] sortCriteriaEvaluators; private final ExprNode[] sortCriteriaExpressions; protected final ExprEvaluator[] uniqueCriteriaEvaluators; private final ExprNode[] uniqueCriteriaExpressions; private final EventBean[] eventsPerStream = new EventBean[1]; private final boolean[] isDescendingValues; private final int sortWindowSize; private final IStreamSortRankRandomAccess optionalRankedRandomAccess; protected final AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext; private final Comparator<Object> comparator; protected TreeMap<Object, Object> sortedEvents; // key is computed sort-key, value is either List<EventBean> or EventBean protected Map<Object, Object> uniqueKeySortKeys; // key is computed unique-key, value is computed sort-key protected int numberOfEvents; public RankWindowView(RankWindowViewFactory rankWindowViewFactory, ExprNode[] uniqueCriteriaExpressions, ExprEvaluator[] uniqueCriteriaEvaluators, ExprNode[] sortCriteriaExpressions, ExprEvaluator[] sortCriteriaEvaluators, boolean[] descendingValues, int sortWindowSize, IStreamSortRankRandomAccess optionalRankedRandomAccess, boolean isSortUsingCollator, AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext) { this.rankWindowViewFactory = rankWindowViewFactory; this.uniqueCriteriaExpressions = uniqueCriteriaExpressions; this.uniqueCriteriaEvaluators = uniqueCriteriaEvaluators; this.sortCriteriaExpressions = sortCriteriaExpressions; this.sortCriteriaEvaluators = sortCriteriaEvaluators; this.isDescendingValues = descendingValues; this.sortWindowSize = sortWindowSize; this.optionalRankedRandomAccess = optionalRankedRandomAccess; this.agentInstanceViewFactoryContext = agentInstanceViewFactoryContext; comparator = CollectionUtil.getComparator(sortCriteriaEvaluators, isSortUsingCollator, isDescendingValues); sortedEvents = new TreeMap<Object, Object>(comparator); uniqueKeySortKeys = new HashMap<Object, Object>(); } public View cloneView() { return rankWindowViewFactory.makeView(agentInstanceViewFactoryContext); } public final EventType getEventType() { // The schema is the parent view's schema return parent.getEventType(); } public final void update(EventBean[] newData, EventBean[] oldData) { if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().qViewProcessIRStream(this, rankWindowViewFactory.getViewName(), newData, oldData); } OneEventCollection removedEvents = new OneEventCollection(); // Remove old data if (oldData != null) { for (int i = 0; i < oldData.length; i++) { Object uniqueKey = getUniqueValues(oldData[i]); Object existingSortKey = uniqueKeySortKeys.get(uniqueKey); if (existingSortKey == null) { continue; } EventBean event = removeFromSortedEvents(existingSortKey, uniqueKey); if (event != null) { numberOfEvents--; uniqueKeySortKeys.remove(uniqueKey); removedEvents.add(event); internalHandleRemovedKey(existingSortKey, oldData[i]); } } } // Add new data if (newData != null) { for (int i = 0; i < newData.length; i++) { Object uniqueKey = getUniqueValues(newData[i]); Object newSortKey = getSortValues(newData[i]); Object existingSortKey = uniqueKeySortKeys.get(uniqueKey); // not currently found: its a new entry if (existingSortKey == null) { compareAndAddOrPassthru(newData[i], uniqueKey, newSortKey, removedEvents); } else { // same unique-key event found already, remove and add again // key did not change, perform in-place substitute of event if (existingSortKey.equals(newSortKey)) { EventBean replaced = inplaceReplaceSortedEvents(existingSortKey, uniqueKey, newData[i]); if (replaced != null) { removedEvents.add(replaced); } internalHandleReplacedKey(newSortKey, newData[i], replaced); } else { EventBean removed = removeFromSortedEvents(existingSortKey, uniqueKey); if (removed != null) { numberOfEvents--; removedEvents.add(removed); internalHandleRemovedKey(existingSortKey, removed); } compareAndAddOrPassthru(newData[i], uniqueKey, newSortKey, removedEvents); } } } } // Remove data that sorts to the bottom of the window if (numberOfEvents > sortWindowSize) { while (numberOfEvents > sortWindowSize) { Object lastKey = sortedEvents.lastKey(); Object existing = sortedEvents.get(lastKey); if (existing instanceof List) { List<EventBean> existingList = (List<EventBean>) existing; while (numberOfEvents > sortWindowSize && !existingList.isEmpty()) { EventBean newestEvent = existingList.remove(0); Object uniqueKey = getUniqueValues(newestEvent); uniqueKeySortKeys.remove(uniqueKey); numberOfEvents--; removedEvents.add(newestEvent); internalHandleRemovedKey(existing, newestEvent); } if (existingList.isEmpty()) { sortedEvents.remove(lastKey); } } else { EventBean lastSortedEvent = (EventBean) existing; Object uniqueKey = getUniqueValues(lastSortedEvent); uniqueKeySortKeys.remove(uniqueKey); numberOfEvents--; removedEvents.add(lastSortedEvent); sortedEvents.remove(lastKey); internalHandleRemovedKey(lastKey, lastSortedEvent); } } } // If there are child views, fireStatementStopped update method if (optionalRankedRandomAccess != null) { optionalRankedRandomAccess.refresh(sortedEvents, numberOfEvents, sortWindowSize); } if (this.hasViews()) { EventBean[] expiredArr = null; if (!removedEvents.isEmpty()) { expiredArr = removedEvents.toArray(); } if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().qViewIndicate(this, rankWindowViewFactory.getViewName(), newData, expiredArr); } updateChildren(newData, expiredArr); if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aViewIndicate(); } } if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aViewProcessIRStream(); } } public void internalHandleReplacedKey(Object newSortKey, EventBean newEvent, EventBean oldEvent) { // no action } public void internalHandleRemovedKey(Object sortKey, EventBean eventBean) { // no action } public void internalHandleAddedKey(Object sortKey, EventBean eventBean) { // no action } private void compareAndAddOrPassthru(EventBean eventBean, Object uniqueKey, Object newSortKey, OneEventCollection removedEvents) { // determine full or not if (numberOfEvents >= sortWindowSize) { int compared = comparator.compare(sortedEvents.lastKey(), newSortKey); // this new event will fall outside of the ranks or coincides with the last entry, so its an old event already if (compared < 0) { removedEvents.add(eventBean); } else { // this new event is higher in sort key then the last entry so we are interested uniqueKeySortKeys.put(uniqueKey, newSortKey); numberOfEvents++; CollectionUtil.addEventByKeyLazyListMapBack(newSortKey, eventBean, sortedEvents); internalHandleAddedKey(newSortKey, eventBean); } } else { // not yet filled, need to add uniqueKeySortKeys.put(uniqueKey, newSortKey); numberOfEvents++; CollectionUtil.addEventByKeyLazyListMapBack(newSortKey, eventBean, sortedEvents); internalHandleAddedKey(newSortKey, eventBean); } } private EventBean removeFromSortedEvents(Object sortKey, Object uniqueKeyToRemove) { Object existing = sortedEvents.get(sortKey); EventBean removedOldEvent = null; if (existing != null) { if (existing instanceof List) { List<EventBean> existingList = (List<EventBean>) existing; Iterator<EventBean> it = existingList.iterator(); for (; it.hasNext(); ) { EventBean eventForRank = it.next(); if (getUniqueValues(eventForRank).equals(uniqueKeyToRemove)) { it.remove(); removedOldEvent = eventForRank; break; } } if (existingList.isEmpty()) { sortedEvents.remove(sortKey); } } else { removedOldEvent = (EventBean) existing; sortedEvents.remove(sortKey); } } return removedOldEvent; } private EventBean inplaceReplaceSortedEvents(Object sortKey, Object uniqueKeyToReplace, EventBean newData) { Object existing = sortedEvents.get(sortKey); EventBean replaced = null; if (existing != null) { if (existing instanceof List) { List<EventBean> existingList = (List<EventBean>) existing; Iterator<EventBean> it = existingList.iterator(); for (; it.hasNext(); ) { EventBean eventForRank = it.next(); if (getUniqueValues(eventForRank).equals(uniqueKeyToReplace)) { it.remove(); replaced = eventForRank; break; } } existingList.add(newData); // add to back as this is now the newest event } else { replaced = (EventBean) existing; sortedEvents.put(sortKey, newData); } } return replaced; } public final Iterator<EventBean> iterator() { return new RankWindowIterator(sortedEvents); } public final String toString() { return this.getClass().getName() + " uniqueFieldName=" + Arrays.toString(uniqueCriteriaExpressions) + " sortFieldName=" + Arrays.toString(sortCriteriaExpressions) + " isDescending=" + Arrays.toString(isDescendingValues) + " sortWindowSize=" + sortWindowSize; } public Object getUniqueValues(EventBean theEvent) { return getCriteriaKey(eventsPerStream, uniqueCriteriaEvaluators, theEvent, agentInstanceViewFactoryContext); } public Object getSortValues(EventBean theEvent) { return getCriteriaKey(eventsPerStream, sortCriteriaEvaluators, theEvent, agentInstanceViewFactoryContext); } public static Object getCriteriaKey(EventBean[] eventsPerStream, ExprEvaluator[] evaluators, EventBean theEvent, ExprEvaluatorContext evalContext) { eventsPerStream[0] = theEvent; if (evaluators.length > 1) { return getCriteriaMultiKey(eventsPerStream, evaluators, evalContext); } else { return evaluators[0].evaluate(eventsPerStream, true, evalContext); } } public static MultiKeyUntyped getCriteriaMultiKey(EventBean[] eventsPerStream, ExprEvaluator[] evaluators, ExprEvaluatorContext evalContext) { Object[] result = new Object[evaluators.length]; int count = 0; for (ExprEvaluator expr : evaluators) { result[count++] = expr.evaluate(eventsPerStream, true, evalContext); } return new MultiKeyUntyped(result); } /** * True to indicate the sort window is empty, or false if not empty. * * @return true if empty sort window */ public boolean isEmpty() { if (sortedEvents == null) { return true; } return sortedEvents.isEmpty(); } public void visitView(ViewDataVisitor viewDataVisitor) { viewDataVisitor.visitPrimary(sortedEvents, false, rankWindowViewFactory.getViewName(), numberOfEvents, sortedEvents.size()); } public ViewFactory getViewFactory() { return rankWindowViewFactory; } private static final Logger log = LoggerFactory.getLogger(RankWindowView.class); }