/* *************************************************************************************** * 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.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.core.ResultSetProcessorHelperFactory; import com.espertech.esper.epl.expression.core.ExprEvaluatorContext; import com.espertech.esper.epl.spec.OutputLimitLimitType; import com.espertech.esper.epl.spec.SelectClauseStreamSelectorEnum; import com.espertech.esper.event.EventBeanUtility; import com.espertech.esper.util.AuditPath; import com.espertech.esper.util.ExecutionPathDebugLog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 Logger log = LoggerFactory.getLogger(OutputProcessViewConditionFirst.class); public OutputProcessViewConditionFirst(ResultSetProcessorHelperFactory resultSetProcessorHelperFactory, ResultSetProcessor resultSetProcessor, Long afterConditionTime, Integer afterConditionNumberOfEvents, boolean afterConditionSatisfied, OutputProcessViewConditionFactory parent, AgentInstanceContext agentInstanceContext) { super(resultSetProcessorHelperFactory, agentInstanceContext, resultSetProcessor, afterConditionTime, afterConditionNumberOfEvents, afterConditionSatisfied); this.parent = parent; OutputCallback outputCallback = getCallbackToLocal(parent.getStreamCount()); this.outputCondition = parent.getOutputConditionFactory().make(agentInstanceContext, outputCallback); } public int getNumChangesetRows() { return Math.max(viewEventsList.size(), joinEventsSet.size()); } public OutputCondition getOptionalOutputCondition() { return outputCondition; } public OutputProcessViewConditionDeltaSet getOptionalDeltaSet() { return null; } public OutputProcessViewAfterState getOptionalAfterConditionState() { return null; } /** * 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 (!hasRelevantResults(newOldEvents)) { return; } witnessedFirst = true; if (parent.isDistinct()) { 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) { addToChangeSet(joinEventsSet, newEvents, oldEvents); boolean isGenerateSynthetic = parent.getStatementResultService().isMakeSynthetic(); UniformPair<EventBean[]> newOldEvents = resultSetProcessor.processOutputLimitedJoin(joinEventsSet, isGenerateSynthetic, OutputLimitLimitType.FIRST); joinEventsSet.clear(); if (!hasRelevantResults(newOldEvents)) { return; } witnessedFirst = true; if (parent.isDistinct()) { 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 { addToChangeSet(joinEventsSet, newEvents, oldEvents); // 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(); } } private boolean hasRelevantResults(UniformPair<EventBean[]> newOldEvents) { if (newOldEvents == null) { return false; } if (parent.getSelectClauseStreamSelectorEnum() == SelectClauseStreamSelectorEnum.ISTREAM_ONLY) { if (newOldEvents.getFirst() == null) { return false; // nothing to indicate } } else if (parent.getSelectClauseStreamSelectorEnum() == SelectClauseStreamSelectorEnum.RSTREAM_ISTREAM_BOTH) { if (newOldEvents.getFirst() == null && newOldEvents.getSecond() == null) { return false; // nothing to indicate } } else { if (newOldEvents.getSecond() == null) { return false; // nothing to indicate } } return true; } private static void addToChangeSet(List<UniformPair<Set<MultiKey<EventBean>>>> joinEventsSet, Set<MultiKey<EventBean>> newEvents, Set<MultiKey<EventBean>> oldEvents) { 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)); } }