/**************************************************************************************
* Copyright (C) 2008 EsperTech, Inc. All rights reserved. *
* http://esper.codehaus.org *
* 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.std;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventType;
import com.espertech.esper.collection.MultiKeyUntyped;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
import com.espertech.esper.epl.expression.ExprEvaluator;
import com.espertech.esper.epl.expression.ExprNode;
import com.espertech.esper.util.ExecutionPathDebugLog;
import com.espertech.esper.view.CloneableView;
import com.espertech.esper.view.StoppableView;
import com.espertech.esper.view.View;
import com.espertech.esper.view.ViewSupport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.*;
public class GroupByViewReclaimAged extends ViewSupport implements CloneableView, GroupByView
{
private final ExprNode[] criteriaExpressions;
private final ExprEvaluator[] criteriaEvaluators;
protected final AgentInstanceViewFactoryChainContext agentInstanceContext;
private final long reclaimMaxAge;
private final long reclaimFrequency;
private EventBean[] eventsPerStream = new EventBean[1];
protected String[] propertyNames;
protected final Map<Object, GroupByViewAgedEntry> subViewsPerKey = new HashMap<Object, GroupByViewAgedEntry>();
private final HashMap<GroupByViewAgedEntry, Pair<Object, Object>> groupedEvents = new HashMap<GroupByViewAgedEntry, Pair<Object, Object>>();
private Long nextSweepTime = null;
/**
* Constructor.
* @param agentInstanceContext contains required view services
* @param criteriaExpressions is the fields from which to pull the values to group by
* @param reclaimMaxAge age after which to reclaim group
* @param reclaimFrequency frequency in which to check for groups to reclaim
*/
public GroupByViewReclaimAged(AgentInstanceViewFactoryChainContext agentInstanceContext,
ExprNode[] criteriaExpressions,
ExprEvaluator[] criteriaEvaluators,
double reclaimMaxAge, double reclaimFrequency)
{
this.agentInstanceContext = agentInstanceContext;
this.criteriaExpressions = criteriaExpressions;
this.criteriaEvaluators = criteriaEvaluators;
this.reclaimMaxAge = (long) (reclaimMaxAge * 1000d);
this.reclaimFrequency = (long) (reclaimFrequency * 1000d);
propertyNames = new String[criteriaExpressions.length];
for (int i = 0; i < criteriaExpressions.length; i++)
{
propertyNames[i] = criteriaExpressions[i].toExpressionString();
}
}
public View cloneView()
{
return new GroupByViewReclaimAged(agentInstanceContext, criteriaExpressions, criteriaEvaluators, reclaimMaxAge, reclaimFrequency);
}
/**
* Returns the field name that provides the key valie by which to group by.
* @return field name providing group-by key.
*/
public ExprNode[] getCriteriaExpressions()
{
return criteriaExpressions;
}
public final EventType getEventType()
{
// The schema is the parent view's schema
return parent.getEventType();
}
public final void update(EventBean[] newData, EventBean[] oldData)
{
long currentTime = agentInstanceContext.getTimeProvider().getTime();
if ((nextSweepTime == null) || (nextSweepTime <= currentTime))
{
if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled()))
{
log.debug("Reclaiming groups older then " + reclaimMaxAge + " msec and every " + reclaimFrequency + "msec in frequency");
}
nextSweepTime = currentTime + reclaimFrequency;
sweep(currentTime);
}
// Algorithm for single new event
if ((newData != null) && (oldData == null) && (newData.length == 1))
{
EventBean theEvent = newData[0];
EventBean[] newDataToPost = new EventBean[] {theEvent};
Object groupByValuesKey = getGroupKey(theEvent);
// Get child views that belong to this group-by value combination
GroupByViewAgedEntry subViews = this.subViewsPerKey.get(groupByValuesKey);
// If this is a new group-by value, the list of subviews is null and we need to make clone sub-views
if (subViews == null)
{
Object subviewsList = GroupByViewImpl.makeSubViews(this, propertyNames, groupByValuesKey, agentInstanceContext);
subViews = new GroupByViewAgedEntry(subviewsList, currentTime);
subViewsPerKey.put(groupByValuesKey, subViews);
}
else {
subViews.setLastUpdateTime(currentTime);
}
GroupByViewImpl.updateChildViews(subViews.getSubviewHolder(), newDataToPost, null);
}
else
{
// Algorithm for dispatching multiple events
if (newData != null)
{
for (EventBean newValue : newData)
{
handleEvent(newValue, true);
}
}
if (oldData != null)
{
for (EventBean oldValue : oldData)
{
handleEvent(oldValue, false);
}
}
// Update child views
for (Map.Entry<GroupByViewAgedEntry, Pair<Object, Object>> entry : groupedEvents.entrySet())
{
EventBean[] newEvents = GroupByViewImpl.convertToArray(entry.getValue().getFirst());
EventBean[] oldEvents = GroupByViewImpl.convertToArray(entry.getValue().getSecond());
GroupByViewImpl.updateChildViews(entry.getKey(), newEvents, oldEvents);
}
groupedEvents.clear();
}
}
private void handleEvent(EventBean theEvent, boolean isNew)
{
Object groupByValuesKey = getGroupKey(theEvent);
// Get child views that belong to this group-by value combination
GroupByViewAgedEntry subViews = this.subViewsPerKey.get(groupByValuesKey);
// If this is a new group-by value, the list of subviews is null and we need to make clone sub-views
if (subViews == null)
{
Object subviewsList = GroupByViewImpl.makeSubViews(this, propertyNames, groupByValuesKey, agentInstanceContext);
long currentTime = agentInstanceContext.getStatementContext().getTimeProvider().getTime();
subViews = new GroupByViewAgedEntry(subviewsList, currentTime);
subViewsPerKey.put(groupByValuesKey, subViews);
}
else {
subViews.setLastUpdateTime(agentInstanceContext.getStatementContext().getTimeProvider().getTime());
}
// Construct a pair of lists to hold the events for the grouped value if not already there
Pair<Object, Object> pair = groupedEvents.get(subViews);
if (pair == null) {
pair = new Pair<Object, Object>(null, null);
groupedEvents.put(subViews, pair);
}
// Add event to a child view event list for later child update that includes new and old events
if (isNew) {
pair.setFirst(GroupByViewImpl.addUpgradeToDequeIfPopulated(pair.getFirst(), theEvent));
}
else {
pair.setSecond(GroupByViewImpl.addUpgradeToDequeIfPopulated(pair.getSecond(), theEvent));
}
}
public final Iterator<EventBean> iterator()
{
throw new UnsupportedOperationException("Cannot iterate over group view, this operation is not supported");
}
public final String toString()
{
return this.getClass().getName() + " groupFieldNames=" + Arrays.toString(criteriaExpressions);
}
private void sweep(long currentTime)
{
ArrayDeque<Object> removed = new ArrayDeque<Object>();
for (Map.Entry<Object, GroupByViewAgedEntry> entry : subViewsPerKey.entrySet())
{
long age = currentTime - entry.getValue().getLastUpdateTime();
if (age > reclaimMaxAge)
{
removed.add(entry.getKey());
}
}
for (Object key : removed)
{
GroupByViewAgedEntry entry = subViewsPerKey.remove(key);
Object subviewHolder = entry.getSubviewHolder();
if (subviewHolder instanceof List) {
List<View> subviews = (List<View>) subviewHolder;
for (View view : subviews) {
removeSubview(view);
}
}
else if (subviewHolder instanceof View) {
removeSubview((View) subviewHolder);
}
}
}
private void removeSubview(View view) {
view.setParent(null);
recursiveMergeViewRemove(view);
if (view instanceof StoppableView) {
((StoppableView) view).stopView();
}
}
private void recursiveMergeViewRemove(View view)
{
for (View child : view.getViews()) {
if (child instanceof StoppableView) {
((StoppableView) child).stopView();
}
if (child instanceof MergeView) {
MergeView mergeView = (MergeView) child;
mergeView.removeParentView(view);
}
else {
recursiveMergeViewRemove(child);
}
}
}
private Object getGroupKey(EventBean theEvent)
{
eventsPerStream[0] = theEvent;
if (criteriaEvaluators.length == 1) {
return criteriaEvaluators[0].evaluate(eventsPerStream, true, agentInstanceContext);
}
Object[] values = new Object[criteriaEvaluators.length];
for (int i = 0; i < criteriaEvaluators.length; i++)
{
values[i] = criteriaEvaluators[i].evaluate(eventsPerStream, true, agentInstanceContext);
}
return new MultiKeyUntyped(values);
}
private static final Log log = LogFactory.getLog(GroupByViewReclaimAged.class);
}