/*
***************************************************************************************
* 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.core.service;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.StatementAwareUpdateListener;
import com.espertech.esper.client.UpdateListener;
import com.espertech.esper.collection.MultiKeyUntyped;
import com.espertech.esper.collection.UniformPair;
import com.espertech.esper.core.thread.OutboundUnitRunnable;
import com.espertech.esper.core.thread.ThreadingOption;
import com.espertech.esper.core.thread.ThreadingService;
import com.espertech.esper.epl.expression.core.ExprEvaluator;
import com.espertech.esper.epl.expression.core.ExprEvaluatorContext;
import com.espertech.esper.epl.metric.MetricReportingPath;
import com.espertech.esper.epl.metric.MetricReportingService;
import com.espertech.esper.epl.metric.MetricReportingServiceSPI;
import com.espertech.esper.epl.metric.StatementMetricHandle;
import com.espertech.esper.event.EventBeanUtility;
import com.espertech.esper.event.NaturalEventBean;
import com.espertech.esper.metrics.instrumentation.InstrumentationHelper;
import com.espertech.esper.util.AuditPath;
import com.espertech.esper.util.ExecutionPathDebugLog;
import com.espertech.esper.view.ViewSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* Implements tracking of statement listeners and subscribers for a given statement
* such as to efficiently dispatch in situations where 0, 1 or more listeners
* are attached and/or 0 or 1 subscriber (such as iteration-only statement).
*/
public class StatementResultServiceImpl implements StatementResultService {
private static Logger log = LoggerFactory.getLogger(StatementResultServiceImpl.class);
private final String statementName;
private final StatementLifecycleSvc statementLifecycleSvc;
private final MetricReportingService metricReportingService;
private final ThreadingService threadingService;
// Part of the statement context
private EPStatementSPI epStatement;
private EPServiceProviderSPI epServiceProvider;
private boolean isInsertInto;
private boolean isPattern;
private boolean isDistinct;
private boolean isForClause;
private StatementMetricHandle statementMetricHandle;
private boolean forClauseDelivery = false;
private ExprEvaluator[] groupDeliveryExpressions;
private ExprEvaluatorContext exprEvaluatorContext;
// For natural delivery derived out of select-clause expressions
private Class[] selectClauseTypes;
private String[] selectClauseColumnNames;
// Listeners and subscribers and derived information
private EPStatementListenerSet statementListenerSet;
private boolean isMakeNatural;
private boolean isMakeSynthetic;
private ResultDeliveryStrategy statementResultNaturalStrategy;
private Set<StatementResultListener> statementOutputHooks;
/**
* Buffer for holding dispatchable events.
*/
protected ThreadLocal<ArrayDeque<UniformPair<EventBean[]>>> lastResults = new ThreadLocal<ArrayDeque<UniformPair<EventBean[]>>>() {
protected synchronized ArrayDeque<UniformPair<EventBean[]>> initialValue() {
return new ArrayDeque<UniformPair<EventBean[]>>();
}
};
/**
* Ctor.
*
* @param statementLifecycleSvc handles persistence for statements
* @param metricReportingService for metrics reporting
* @param threadingService for outbound threading
* @param statementName statement name
*/
public StatementResultServiceImpl(String statementName,
StatementLifecycleSvc statementLifecycleSvc,
MetricReportingServiceSPI metricReportingService,
ThreadingService threadingService) {
log.debug(".ctor");
this.statementName = statementName;
this.statementLifecycleSvc = statementLifecycleSvc;
this.metricReportingService = metricReportingService;
if (metricReportingService != null) {
this.statementOutputHooks = metricReportingService.getStatementOutputHooks();
} else {
this.statementOutputHooks = Collections.EMPTY_SET;
}
this.threadingService = threadingService;
}
public void setContext(EPStatementSPI epStatement, EPServiceProviderSPI epServiceProvider,
boolean isInsertInto,
boolean isPattern,
boolean isDistinct,
boolean isForClause,
StatementMetricHandle statementMetricHandle) {
this.epStatement = epStatement;
this.epServiceProvider = epServiceProvider;
this.isInsertInto = isInsertInto;
this.isPattern = isPattern;
this.isDistinct = isDistinct;
this.isForClause = isForClause;
isMakeSynthetic = isInsertInto || isPattern || isDistinct || isForClause;
this.statementMetricHandle = statementMetricHandle;
}
public void setSelectClause(Class[] selectClauseTypes, String[] selectClauseColumnNames,
boolean forClauseDelivery, ExprEvaluator[] groupDeliveryExpressions, ExprEvaluatorContext exprEvaluatorContext) {
if ((selectClauseTypes == null) || (selectClauseTypes.length == 0)) {
throw new IllegalArgumentException("Invalid null or zero-element list of select clause expression types");
}
if ((selectClauseColumnNames == null) || (selectClauseColumnNames.length == 0)) {
throw new IllegalArgumentException("Invalid null or zero-element list of select clause column names");
}
this.selectClauseTypes = selectClauseTypes;
this.selectClauseColumnNames = selectClauseColumnNames;
this.forClauseDelivery = forClauseDelivery;
this.exprEvaluatorContext = exprEvaluatorContext;
this.groupDeliveryExpressions = groupDeliveryExpressions;
}
public int getStatementId() {
return epStatement.getStatementId();
}
public boolean isMakeSynthetic() {
return isMakeSynthetic;
}
public boolean isMakeNatural() {
return isMakeNatural;
}
public String getStatementName() {
return statementName;
}
public EPStatementListenerSet getStatementListenerSet() {
return statementListenerSet;
}
public void setUpdateListeners(EPStatementListenerSet updateListeners, boolean isRecovery) {
// indicate that listeners were updated for potential persistence of listener set, once the statement context is known
if (epStatement != null) {
this.statementLifecycleSvc.updatedListeners(epStatement, updateListeners, isRecovery);
}
this.statementListenerSet = updateListeners;
isMakeNatural = statementListenerSet.getSubscriber() != null;
isMakeSynthetic = !(statementListenerSet.getListeners().length == 0 && statementListenerSet.getStmtAwareListeners().length == 0)
|| isPattern || isInsertInto || isDistinct | isForClause;
if (statementListenerSet.getSubscriber() == null) {
statementResultNaturalStrategy = null;
isMakeNatural = false;
return;
}
statementResultNaturalStrategy = ResultDeliveryStrategyFactory.create(epStatement, statementListenerSet.getSubscriber(), statementListenerSet.getSubscriberMethodName(),
selectClauseTypes, selectClauseColumnNames, epServiceProvider.getURI(), epServiceProvider.getEngineImportService());
isMakeNatural = true;
}
// Called by OutputProcessView
public void indicate(UniformPair<EventBean[]> results) {
if (results != null) {
if ((MetricReportingPath.isMetricsEnabled) && (statementMetricHandle.isEnabled())) {
int numIStream = (results.getFirst() != null) ? results.getFirst().length : 0;
int numRStream = (results.getSecond() != null) ? results.getSecond().length : 0;
this.metricReportingService.accountOutput(statementMetricHandle, numIStream, numRStream);
}
if ((results.getFirst() != null) && (results.getFirst().length != 0)) {
lastResults.get().add(results);
} else if ((results.getSecond() != null) && (results.getSecond().length != 0)) {
lastResults.get().add(results);
}
}
}
public void execute() {
ArrayDeque<UniformPair<EventBean[]>> dispatches = lastResults.get();
UniformPair<EventBean[]> events = EventBeanUtility.flattenList(dispatches);
if (ExecutionPathDebugLog.isDebugEnabled && log.isDebugEnabled()) {
ViewSupport.dumpUpdateParams(".execute", events);
}
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qaStatementResultExecute(events, epStatement.getStatementId(), epStatement.getName(), exprEvaluatorContext.getAgentInstanceId(), Thread.currentThread().getId());
}
if ((ThreadingOption.isThreadingEnabled) && (threadingService.isOutboundThreading())) {
threadingService.submitOutbound(new OutboundUnitRunnable(events, this));
} else {
processDispatch(events);
}
dispatches.clear();
}
/**
* Indicate an outbound result.
*
* @param events to indicate
*/
public void processDispatch(UniformPair<EventBean[]> events) {
// Plain all-events delivery
if (!forClauseDelivery) {
dispatchInternal(events);
return;
}
// Discrete delivery
if ((groupDeliveryExpressions == null) || (groupDeliveryExpressions.length == 0)) {
UniformPair<EventBean[]> todeliver = new UniformPair<EventBean[]>(null, null);
if (events != null) {
if (events.getFirst() != null) {
for (EventBean theEvent : events.getFirst()) {
todeliver.setFirst(new EventBean[]{theEvent});
dispatchInternal(todeliver);
}
todeliver.setFirst(null);
}
if (events.getSecond() != null) {
for (EventBean theEvent : events.getSecond()) {
todeliver.setSecond(new EventBean[]{theEvent});
dispatchInternal(todeliver);
}
todeliver.setSecond(null);
}
}
return;
}
// Grouped delivery
Map<Object, UniformPair<EventBean[]>> groups;
try {
groups = getGroupedResults(events);
} catch (RuntimeException ex) {
log.error("Unexpected exception evaluating grouped-delivery expressions: " + ex.getMessage() + ", delivering ungrouped", ex);
dispatchInternal(events);
return;
}
// Deliver each group separately
for (Map.Entry<Object, UniformPair<EventBean[]>> group : groups.entrySet()) {
dispatchInternal(group.getValue());
}
}
private Map<Object, UniformPair<EventBean[]>> getGroupedResults(UniformPair<EventBean[]> events) {
if (events == null) {
return Collections.emptyMap();
}
Map<Object, UniformPair<EventBean[]>> groups = new LinkedHashMap<Object, UniformPair<EventBean[]>>();
EventBean[] eventsPerStream = new EventBean[1];
getGroupedResults(groups, events.getFirst(), true, eventsPerStream);
getGroupedResults(groups, events.getSecond(), false, eventsPerStream);
return groups;
}
private void getGroupedResults(Map<Object, UniformPair<EventBean[]>> groups, EventBean[] events, boolean insertStream, EventBean[] eventsPerStream) {
if (events == null) {
return;
}
for (EventBean theEvent : events) {
EventBean evalEvent = theEvent;
if (evalEvent instanceof NaturalEventBean) {
evalEvent = ((NaturalEventBean) evalEvent).getOptionalSynthetic();
}
Object key;
eventsPerStream[0] = evalEvent;
if (groupDeliveryExpressions.length == 1) {
key = groupDeliveryExpressions[0].evaluate(eventsPerStream, true, exprEvaluatorContext);
} else {
Object[] keys = new Object[groupDeliveryExpressions.length];
for (int i = 0; i < groupDeliveryExpressions.length; i++) {
keys[i] = groupDeliveryExpressions[i].evaluate(eventsPerStream, true, exprEvaluatorContext);
}
key = new MultiKeyUntyped(keys);
}
UniformPair<EventBean[]> groupEntry = groups.get(key);
if (groupEntry == null) {
if (insertStream) {
groupEntry = new UniformPair<EventBean[]>(new EventBean[]{theEvent}, null);
} else {
groupEntry = new UniformPair<EventBean[]>(null, new EventBean[]{theEvent});
}
groups.put(key, groupEntry);
} else {
if (insertStream) {
if (groupEntry.getFirst() == null) {
groupEntry.setFirst(new EventBean[]{theEvent});
} else {
groupEntry.setFirst(EventBeanUtility.addToArray(groupEntry.getFirst(), theEvent));
}
} else {
if (groupEntry.getSecond() == null) {
groupEntry.setSecond(new EventBean[]{theEvent});
} else {
groupEntry.setSecond(EventBeanUtility.addToArray(groupEntry.getSecond(), theEvent));
}
}
}
}
}
private void dispatchInternal(UniformPair<EventBean[]> events) {
if (statementResultNaturalStrategy != null) {
statementResultNaturalStrategy.execute(events);
}
EventBean[] newEventArr = events != null ? events.getFirst() : null;
EventBean[] oldEventArr = events != null ? events.getSecond() : null;
for (UpdateListener listener : statementListenerSet.getListeners()) {
try {
listener.update(newEventArr, oldEventArr);
} catch (Throwable t) {
String message = "Unexpected exception invoking listener update method on listener class '" + listener.getClass().getSimpleName() +
"' : " + t.getClass().getSimpleName() + " : " + t.getMessage();
log.error(message, t);
}
}
if (statementListenerSet.getStmtAwareListeners().length > 0) {
for (StatementAwareUpdateListener listener : statementListenerSet.getStmtAwareListeners()) {
try {
listener.update(newEventArr, oldEventArr, epStatement, epServiceProvider);
} catch (Throwable t) {
String message = "Unexpected exception invoking listener update method on listener class '" + listener.getClass().getSimpleName() +
"' : " + t.getClass().getSimpleName() + " : " + t.getMessage();
log.error(message, t);
}
}
}
if ((AuditPath.isAuditEnabled) && (!statementOutputHooks.isEmpty())) {
for (StatementResultListener listener : statementOutputHooks) {
listener.update(newEventArr, oldEventArr, epStatement.getName(), epStatement, epServiceProvider);
}
}
}
/**
* Dispatches when the statement is stopped any remaining results.
*/
public void dispatchOnStop() {
ArrayDeque<UniformPair<EventBean[]>> dispatches = lastResults.get();
if (dispatches.isEmpty()) {
return;
}
execute();
lastResults = new ThreadLocal<ArrayDeque<UniformPair<EventBean[]>>>() {
protected synchronized ArrayDeque<UniformPair<EventBean[]>> initialValue() {
return new ArrayDeque<UniformPair<EventBean[]>>();
}
};
}
}