/**************************************************************************************
* 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.window;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventType;
import com.espertech.esper.collection.ViewUpdatedCollection;
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.event.EventBeanUtility;
import com.espertech.esper.view.CloneableView;
import com.espertech.esper.view.DataWindowView;
import com.espertech.esper.view.View;
import com.espertech.esper.view.ViewSupport;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Batch window based on timestamp of arriving events.
*/
public class ExternallyTimedBatchView extends ViewSupport implements DataWindowView, CloneableView
{
private final ExternallyTimedBatchViewFactory factory;
private final ExprNode timestampExpression;
private final ExprEvaluator timestampExpressionEval;
private final long millisecondsBeforeExpiry;
private final EventBean[] eventsPerStream = new EventBean[1];
protected EventBean[] lastBatch;
private Long oldestTimestampRoundedToRef;
protected final Set<EventBean> window = new LinkedHashSet<EventBean>();
protected Long referenceTimestamp;
protected ViewUpdatedCollection viewUpdatedCollection;
protected AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext;
/**
* Constructor.
* @param timestampExpression is the field name containing a long timestamp value
* that should be in ascending order for the natural order of events and is intended to reflect
* System.currentTimeInMillis but does not necessarily have to.
* @param msecBeforeExpiry is the number of milliseconds before events gets pushed
* out of the window as oldData in the update method. The view compares
* each events timestamp against the newest event timestamp and those with a delta
* greater then secondsBeforeExpiry are pushed out of the window.
* @param viewUpdatedCollection is a collection that the view must update when receiving events
* @param factory for copying this view in a group-by
* @param agentInstanceViewFactoryContext context for expression evalauation
*/
public ExternallyTimedBatchView(ExternallyTimedBatchViewFactory factory,
ExprNode timestampExpression,
ExprEvaluator timestampExpressionEval,
long msecBeforeExpiry,
Long optionalReferencePoint,
ViewUpdatedCollection viewUpdatedCollection,
AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext)
{
this.factory = factory;
this.timestampExpression = timestampExpression;
this.timestampExpressionEval = timestampExpressionEval;
this.millisecondsBeforeExpiry = msecBeforeExpiry;
this.viewUpdatedCollection = viewUpdatedCollection;
this.agentInstanceViewFactoryContext = agentInstanceViewFactoryContext;
this.referenceTimestamp = optionalReferencePoint;
}
public View cloneView()
{
return factory.makeView(agentInstanceViewFactoryContext);
}
/**
* Returns the field name to get timestamp values from.
* @return field name for timestamp values
*/
public final ExprNode getTimestampExpression()
{
return timestampExpression;
}
public final EventType getEventType()
{
// The schema is the parent view's schema
return parent.getEventType();
}
public final void update(EventBean[] newData, EventBean[] oldData)
{
// remove points from data window
if (oldData != null && oldData.length != 0) {
for (EventBean anOldData : oldData) {
window.remove(anOldData);
handleInternalRemovedEvent(anOldData);
}
determineOldestTimestamp();
}
// add data points to the window
EventBean[] batchNewData = null;
if (newData != null) {
for (EventBean newEvent : newData) {
long timestamp = getLongValue(newEvent);
if (referenceTimestamp == null) {
referenceTimestamp = timestamp;
}
if (oldestTimestampRoundedToRef == null) {
oldestTimestampRoundedToRef = roundDownTimestamp(timestamp);
}
else {
if (timestamp - oldestTimestampRoundedToRef >= millisecondsBeforeExpiry) {
if (batchNewData == null) {
batchNewData = window.toArray(new EventBean[window.size()]);
}
else {
batchNewData = EventBeanUtility.addToArray(batchNewData, window);
}
window.clear();
oldestTimestampRoundedToRef = null;
}
}
window.add(newEvent);
handleInternalAddEvent(newEvent, batchNewData != null);
}
}
if (batchNewData != null) {
handleInternalPostBatch(window, batchNewData);
if (viewUpdatedCollection != null) {
viewUpdatedCollection.update(batchNewData, lastBatch);
}
updateChildren(batchNewData, lastBatch);
lastBatch = batchNewData;
determineOldestTimestamp();
}
if (oldData != null && oldData.length > 0) {
if (viewUpdatedCollection != null) {
viewUpdatedCollection.update(null, oldData);
}
updateChildren(null, oldData);
}
}
public final Iterator<EventBean> iterator()
{
return window.iterator();
}
public final String toString()
{
return this.getClass().getName() +
" timestampExpression=" + timestampExpression +
" millisecondsBeforeExpiry=" + millisecondsBeforeExpiry;
}
/**
* Returns true to indicate the window is empty, or false if the view is not empty.
* @return true if empty
*/
public boolean isEmpty()
{
return window.isEmpty();
}
public long getMillisecondsBeforeExpiry() {
return millisecondsBeforeExpiry;
}
protected void determineOldestTimestamp() {
if (window.isEmpty()) {
oldestTimestampRoundedToRef = null;
}
else {
long ts = getLongValue(window.iterator().next());
oldestTimestampRoundedToRef = roundDownTimestamp(ts);
}
}
protected void handleInternalPostBatch(Set<EventBean> window, EventBean[] batchNewData) {
// no action require
}
protected void handleInternalRemovedEvent(EventBean anOldData) {
// no action require
}
protected void handleInternalAddEvent(EventBean anNewData, boolean isNextBatch) {
// no action require
}
private long roundDownTimestamp(long timestamp) {
if (timestamp <= referenceTimestamp) {
return referenceTimestamp;
}
long delta = Math.abs(timestamp - referenceTimestamp);
long factor = delta / millisecondsBeforeExpiry;
return referenceTimestamp + factor * millisecondsBeforeExpiry;
}
private long getLongValue(EventBean obj)
{
eventsPerStream[0] = obj;
Number num = (Number) timestampExpressionEval.evaluate(eventsPerStream, true, agentInstanceViewFactoryContext);
return num.longValue();
}
}