/*
***************************************************************************************
* 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.filter;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventType;
import com.espertech.esper.metrics.instrumentation.InstrumentationHelper;
import com.espertech.esper.metrics.jmx.JmxGetter;
import com.espertech.esper.metrics.jmx.JmxOperation;
import com.espertech.esper.util.AuditPath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;
/**
* Implementation of the filter service interface.
* Does not allow the same filter callback to be added more then once.
*/
public abstract class FilterServiceBase implements FilterServiceSPI {
private final FilterServiceGranularLockFactory lockFactory;
private static final Logger log = LoggerFactory.getLogger(FilterServiceBase.class);
private final EventTypeIndexBuilder indexBuilder;
private final EventTypeIndex eventTypeIndex;
private final AtomicLong numEventsEvaluated = new AtomicLong();
private volatile long filtersVersion = 1;
private final CopyOnWriteArraySet<FilterServiceListener> filterServiceListeners;
protected FilterServiceBase(FilterServiceGranularLockFactory lockFactory, boolean allowIsolation) {
this.lockFactory = lockFactory;
eventTypeIndex = new EventTypeIndex(lockFactory);
indexBuilder = new EventTypeIndexBuilder(eventTypeIndex, allowIsolation);
filterServiceListeners = new CopyOnWriteArraySet<FilterServiceListener>();
}
public boolean isSupportsTakeApply() {
return indexBuilder.isSupportsTakeApply();
}
public long getFiltersVersion() {
return filtersVersion;
}
public void destroy() {
log.debug("Destroying filter service");
eventTypeIndex.destroy();
indexBuilder.destroy();
}
protected FilterServiceEntry addInternal(FilterValueSet filterValueSet, FilterHandle filterCallback) {
FilterServiceEntry entry = indexBuilder.add(filterValueSet, filterCallback, lockFactory);
filtersVersion++;
return entry;
}
protected void removeInternal(FilterHandle filterCallback, FilterServiceEntry filterServiceEntry) {
indexBuilder.remove(filterCallback, filterServiceEntry);
filtersVersion++;
}
protected long evaluateInternal(EventBean theEvent, Collection<FilterHandle> matches) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qFilter(theEvent);
}
long version = filtersVersion;
numEventsEvaluated.incrementAndGet();
// Finds all matching filters and return their callbacks.
retryableMatchEvent(theEvent, matches);
if ((AuditPath.isAuditEnabled) && (!filterServiceListeners.isEmpty())) {
for (FilterServiceListener listener : filterServiceListeners) {
listener.filtering(theEvent, matches, null);
}
}
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aFilter(matches);
}
return version;
}
protected long evaluateInternal(EventBean theEvent, Collection<FilterHandle> matches, int statementId) {
long version = filtersVersion;
numEventsEvaluated.incrementAndGet();
ArrayDeque<FilterHandle> allMatches = new ArrayDeque<FilterHandle>();
// Finds all matching filters
retryableMatchEvent(theEvent, allMatches);
// Add statement matches to collection passed
for (FilterHandle match : allMatches) {
if (match.getStatementId() == statementId) {
matches.add(match);
}
}
if ((AuditPath.isAuditEnabled) && (!filterServiceListeners.isEmpty())) {
for (FilterServiceListener listener : filterServiceListeners) {
listener.filtering(theEvent, matches, statementId);
}
}
return version;
}
@JmxGetter(name = "NumEventsEvaluated", description = "Number of events evaluated (main)")
public final long getNumEventsEvaluated() {
return numEventsEvaluated.get();
}
@JmxOperation(description = "Reset number of events evaluated")
public void resetStats() {
numEventsEvaluated.set(0);
}
public void addFilterServiceListener(FilterServiceListener filterServiceListener) {
filterServiceListeners.add(filterServiceListener);
}
public void removeFilterServiceListener(FilterServiceListener filterServiceListener) {
filterServiceListeners.remove(filterServiceListener);
}
protected FilterSet takeInternal(Set<Integer> statementIds) {
filtersVersion++;
return indexBuilder.take(statementIds);
}
protected void applyInternal(FilterSet filterSet) {
filtersVersion++;
indexBuilder.apply(filterSet, lockFactory);
}
@JmxGetter(name = "NumFiltersApprox", description = "Number of filters managed (approximately)")
public int getFilterCountApprox() {
return eventTypeIndex.getFilterCountApprox();
}
@JmxGetter(name = "NumEventTypes", description = "Number of event types considered")
public int getCountTypes() {
return eventTypeIndex.size();
}
public void init() {
// no initialization required
}
protected void removeTypeInternal(EventType type) {
eventTypeIndex.removeType(type);
}
private void retryableMatchEvent(EventBean theEvent, Collection<FilterHandle> matches) {
// Install lock backoff exception handler that retries the evaluation.
try {
eventTypeIndex.matchEvent(theEvent, matches);
} catch (FilterLockBackoffException ex) {
// retry on lock back-off
// lock-backoff may occur when stateful evaluations take place such as boolean expressions that are subqueries
// statements that contain subqueries in pattern filter expression can themselves modify filters, leading to a theoretically possible deadlock
long delayNs = 10;
while (true) {
try {
// yield
try {
Thread.sleep(0);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// delay
LockSupport.parkNanos(delayNs);
if (delayNs < 1000000000) {
delayNs = delayNs * 2;
}
// evaluate
matches.clear();
eventTypeIndex.matchEvent(theEvent, matches);
break;
} catch (FilterLockBackoffException ex2) {
// retried
}
}
}
}
}