/************************************************************************************** * 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.epl.view; import com.espertech.esper.client.EventBean; import com.espertech.esper.collection.MultiKey; import com.espertech.esper.collection.UniformPair; import com.espertech.esper.core.context.util.AgentInstanceContext; import com.espertech.esper.epl.core.ResultSetProcessor; import com.espertech.esper.epl.expression.ExprEvaluatorContext; import com.espertech.esper.epl.spec.OutputLimitLimitType; import com.espertech.esper.event.EventBeanUtility; import com.espertech.esper.util.AuditPath; import com.espertech.esper.util.ExecutionPathDebugLog; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.util.*; /** * Handles output rate limiting for FIRST, only applicable with a having-clause and no group-by clause. * <p> * Without having-clause the order of processing won't matter therefore its handled by the * {@link OutputProcessViewConditionDefault}. With group-by the {@link ResultSetProcessor} handles the per-group first criteria. */ public class OutputProcessViewConditionFirst extends OutputProcessViewBaseWAfter { private final OutputProcessViewConditionFactory parent; private final OutputCondition outputCondition; // Posted events in ordered form (for applying to aggregates) and summarized per type // Using ArrayList as random access is a requirement. private List<UniformPair<EventBean[]>> viewEventsList = new ArrayList<UniformPair<EventBean[]>>(); private List<UniformPair<Set<MultiKey<EventBean>>>> joinEventsSet = new ArrayList<UniformPair<Set<MultiKey<EventBean>>>>(); private boolean witnessedFirst; private static final Log log = LogFactory.getLog(OutputProcessViewConditionFirst.class); public OutputProcessViewConditionFirst(ResultSetProcessor resultSetProcessor, Long afterConditionTime, Integer afterConditionNumberOfEvents, boolean afterConditionSatisfied, OutputProcessViewConditionFactory parent, AgentInstanceContext agentInstanceContext) { super(resultSetProcessor, afterConditionTime, afterConditionNumberOfEvents, afterConditionSatisfied); this.parent = parent; OutputCallback outputCallback = getCallbackToLocal(parent.getStreamCount()); this.outputCondition = parent.getOutputConditionFactory().make(agentInstanceContext, outputCallback); } /** * The update method is called if the view does not participate in a join. * @param newData - new events * @param oldData - old events */ public void update(EventBean[] newData, EventBean[] oldData) { if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled())) { log.debug(".update Received update, " + " newData.length==" + ((newData == null) ? 0 : newData.length) + " oldData.length==" + ((oldData == null) ? 0 : oldData.length)); } if (!super.checkAfterCondition(newData, parent.getStatementContext())) { return; } if (!witnessedFirst) { boolean isGenerateSynthetic = parent.getStatementResultService().isMakeSynthetic(); // Process the events and get the result viewEventsList.add(new UniformPair<EventBean[]>(newData, oldData)); UniformPair<EventBean[]> newOldEvents = resultSetProcessor.processOutputLimitedView(viewEventsList, isGenerateSynthetic, OutputLimitLimitType.FIRST); viewEventsList.clear(); if (newOldEvents == null || (newOldEvents.getFirst() == null && newOldEvents.getSecond() == null)) { return; // nothing to indicate } witnessedFirst = true; if (parent.isDistinct() && newOldEvents != null) { newOldEvents.setFirst(EventBeanUtility.getDistinctByProp(newOldEvents.getFirst(), parent.getEventBeanReader())); newOldEvents.setSecond(EventBeanUtility.getDistinctByProp(newOldEvents.getSecond(), parent.getEventBeanReader())); } boolean isGenerateNatural = parent.getStatementResultService().isMakeNatural(); if ((!isGenerateSynthetic) && (!isGenerateNatural)) { if (AuditPath.isAuditEnabled) { OutputStrategyUtil.indicateEarlyReturn(parent.getStatementContext(), newOldEvents); } return; } output(true, newOldEvents); } else { viewEventsList.add(new UniformPair<EventBean[]>(newData, oldData)); resultSetProcessor.processOutputLimitedView(viewEventsList, false, OutputLimitLimitType.FIRST); viewEventsList.clear(); } int newDataLength = 0; int oldDataLength = 0; if(newData != null) { newDataLength = newData.length; } if(oldData != null) { oldDataLength = oldData.length; } outputCondition.updateOutputCondition(newDataLength, oldDataLength); } /** * This process (update) method is for participation in a join. * @param newEvents - new events * @param oldEvents - old events */ public void process(Set<MultiKey<EventBean>> newEvents, Set<MultiKey<EventBean>> oldEvents, ExprEvaluatorContext exprEvaluatorContext) { if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled())) { log.debug(".process Received update, " + " newData.length==" + ((newEvents == null) ? 0 : newEvents.size()) + " oldData.length==" + ((oldEvents == null) ? 0 : oldEvents.size())); } if (!super.checkAfterCondition(newEvents, parent.getStatementContext())) { return; } // add the incoming events to the event batches if (!witnessedFirst) { Set<MultiKey<EventBean>> copyNew; if (newEvents != null) { copyNew = new LinkedHashSet<MultiKey<EventBean>>(newEvents); } else { copyNew = new LinkedHashSet<MultiKey<EventBean>>(); } Set<MultiKey<EventBean>> copyOld; if (oldEvents != null) { copyOld = new LinkedHashSet<MultiKey<EventBean>>(oldEvents); } else { copyOld = new LinkedHashSet<MultiKey<EventBean>>(); } joinEventsSet.add(new UniformPair<Set<MultiKey<EventBean>>>(copyNew, copyOld)); boolean isGenerateSynthetic = parent.getStatementResultService().isMakeSynthetic(); // Process the events and get the result UniformPair<EventBean[]> newOldEvents = resultSetProcessor.processOutputLimitedJoin(joinEventsSet, isGenerateSynthetic, OutputLimitLimitType.FIRST); joinEventsSet.clear(); if (newOldEvents == null || (newOldEvents.getFirst() == null && newOldEvents.getSecond() == null)) { return; // nothing to indicate } witnessedFirst = true; if (parent.isDistinct() && newOldEvents != null) { newOldEvents.setFirst(EventBeanUtility.getDistinctByProp(newOldEvents.getFirst(), parent.getEventBeanReader())); newOldEvents.setSecond(EventBeanUtility.getDistinctByProp(newOldEvents.getSecond(), parent.getEventBeanReader())); } boolean isGenerateNatural = parent.getStatementResultService().isMakeNatural(); if ((!isGenerateSynthetic) && (!isGenerateNatural)) { if (AuditPath.isAuditEnabled) { OutputStrategyUtil.indicateEarlyReturn(parent.getStatementContext(), newOldEvents); } return; } output(true, newOldEvents); } else { Set<MultiKey<EventBean>> copyNew; if (newEvents != null) { copyNew = new LinkedHashSet<MultiKey<EventBean>>(newEvents); } else { copyNew = new LinkedHashSet<MultiKey<EventBean>>(); } Set<MultiKey<EventBean>> copyOld; if (oldEvents != null) { copyOld = new LinkedHashSet<MultiKey<EventBean>>(oldEvents); } else { copyOld = new LinkedHashSet<MultiKey<EventBean>>(); } joinEventsSet.add(new UniformPair<Set<MultiKey<EventBean>>>(copyNew, copyOld)); // Process the events and get the result resultSetProcessor.processOutputLimitedJoin(joinEventsSet, false, OutputLimitLimitType.FIRST); joinEventsSet.clear(); } int newEventsSize = 0; if (newEvents != null) { newEventsSize = newEvents.size(); } int oldEventsSize = 0; if (oldEvents != null) { oldEventsSize = oldEvents.size(); } outputCondition.updateOutputCondition(newEventsSize, oldEventsSize); } /** * Called once the output condition has been met. * Invokes the result set processor. * Used for non-join event data. * @param doOutput - true if the batched events should actually be output as well as processed, false if they should just be processed * @param forceUpdate - true if output should be made even when no updating events have arrived * */ protected void continueOutputProcessingView(boolean doOutput, boolean forceUpdate) { if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled())) { log.debug(".continueOutputProcessingView"); } witnessedFirst = false; } private void output(boolean forceUpdate, UniformPair<EventBean[]> results) { // Child view can be null in replay from named window if (childView != null) { OutputStrategyUtil.output(forceUpdate, results, childView); } } /** * Called once the output condition has been met. * Invokes the result set processor. * Used for join event data. * @param doOutput - true if the batched events should actually be output as well as processed, false if they should just be processed * @param forceUpdate - true if output should be made even when no updating events have arrived */ protected void continueOutputProcessingJoin(boolean doOutput, boolean forceUpdate) { if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled())) { log.debug(".continueOutputProcessingJoin"); } witnessedFirst = false; } private OutputCallback getCallbackToLocal(int streamCount) { // single stream means no join // multiple streams means a join if(streamCount == 1) { return new OutputCallback() { public void continueOutputProcessing(boolean doOutput, boolean forceUpdate) { OutputProcessViewConditionFirst.this.continueOutputProcessingView(doOutput, forceUpdate); } }; } else { return new OutputCallback() { public void continueOutputProcessing(boolean doOutput, boolean forceUpdate) { OutputProcessViewConditionFirst.this.continueOutputProcessingJoin(doOutput, forceUpdate); } }; } } public Iterator<EventBean> iterator() { return OutputStrategyUtil.getIterator(joinExecutionStrategy, resultSetProcessor, parentView, parent.isDistinct()); } public void terminated() { if (parent.isTerminable()) { outputCondition.terminated(); } } }