/* *************************************************************************************** * 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.EPException; import com.espertech.esper.client.EventType; import com.espertech.esper.metrics.instrumentation.InstrumentationHelper; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * This class is responsible for changes to {@link EventTypeIndex} for addition and removal of filters. * It delegates the work to make modifications to the filter parameter tree to an {@link IndexTreeBuilder}. * It enforces a policy that a filter callback can only be added once. */ public class EventTypeIndexBuilder { private final Map<FilterHandle, EventTypeIndexBuilderValueIndexesPair> isolatableCallbacks; private final Lock callbacksLock; private final EventTypeIndex eventTypeIndex; /** * Constructor - takes the event type index to manipulate as its parameter. * * @param eventTypeIndex - index to manipulate * @param allowIsolation - indicator whether isolated service provider is allowed for the engine */ public EventTypeIndexBuilder(EventTypeIndex eventTypeIndex, boolean allowIsolation) { this.eventTypeIndex = eventTypeIndex; this.callbacksLock = new ReentrantLock(); if (allowIsolation) { this.isolatableCallbacks = new HashMap<FilterHandle, EventTypeIndexBuilderValueIndexesPair>(); } else { this.isolatableCallbacks = null; } } /** * Destroy the service. */ public void destroy() { eventTypeIndex.destroy(); if (isolatableCallbacks != null) { isolatableCallbacks.clear(); } } /** * Add a filter to the event type index structure, and to the filter subtree. * Throws an IllegalStateException exception if the callback is already registered. * * @param filterValueSet is the filter information * @param filterCallback is the callback * @param lockFactory lock factory * @return filter service enrty */ public final FilterServiceEntry add(FilterValueSet filterValueSet, FilterHandle filterCallback, FilterServiceGranularLockFactory lockFactory) { if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().qFilterAdd(filterValueSet, filterCallback); } EventType eventType = filterValueSet.getEventType(); // Check if a filter tree exists for this event type FilterHandleSetNode rootNode = eventTypeIndex.get(eventType); // Make sure we have a root node if (rootNode == null) { callbacksLock.lock(); try { rootNode = eventTypeIndex.get(eventType); if (rootNode == null) { rootNode = new FilterHandleSetNode(lockFactory.obtainNew()); eventTypeIndex.add(eventType, rootNode); } } finally { callbacksLock.unlock(); } } // Now add to tree ArrayDeque<EventTypeIndexBuilderIndexLookupablePair>[] path = IndexTreeBuilder.add(filterValueSet, filterCallback, rootNode, lockFactory); EventTypeIndexBuilderIndexLookupablePair[][] pathArray = new EventTypeIndexBuilderIndexLookupablePair[path.length][]; for (int i = 0; i < path.length; i++) { pathArray[i] = path[i].toArray(new EventTypeIndexBuilderIndexLookupablePair[path[i].size()]); } EventTypeIndexBuilderValueIndexesPair pair = new EventTypeIndexBuilderValueIndexesPair(filterValueSet, pathArray); // for non-isolatable callbacks the consumer keeps track of tree location if (isolatableCallbacks == null) { if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aFilterAdd(); } return pair; } // for isolatable callbacks this class is keeping track of tree location callbacksLock.lock(); try { isolatableCallbacks.put(filterCallback, pair); } finally { callbacksLock.unlock(); } if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aFilterAdd(); } return null; } /** * Remove a filter callback from the given index node. * * @param filterCallback is the callback to remove * @param filterServiceEntry entry */ public final void remove(FilterHandle filterCallback, FilterServiceEntry filterServiceEntry) { EventTypeIndexBuilderValueIndexesPair pair; if (isolatableCallbacks != null) { callbacksLock.lock(); try { pair = isolatableCallbacks.remove(filterCallback); } finally { callbacksLock.unlock(); } if (pair == null) { return; } } else { pair = (EventTypeIndexBuilderValueIndexesPair) filterServiceEntry; } if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().qFilterRemove(filterCallback, pair); } EventType eventType = pair.getFilterValueSet().getEventType(); FilterHandleSetNode rootNode = eventTypeIndex.get(eventType); // Now remove from tree if (rootNode != null) { for (int i = 0; i < pair.getIndexPairs().length; i++) { IndexTreeBuilder.remove(eventType, filterCallback, pair.getIndexPairs()[i], rootNode); } } if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aFilterRemove(); } } /** * Returns filters for the statement ids. * * @param statementIds ids to take * @return set of filters for taken statements */ public final FilterSet take(Set<Integer> statementIds) { if (isolatableCallbacks == null) { throw new EPException("Operation not supported, please enable isolation in the engine configuration"); } List<FilterSetEntry> list = new ArrayList<FilterSetEntry>(); callbacksLock.lock(); try { for (Map.Entry<FilterHandle, EventTypeIndexBuilderValueIndexesPair> entry : isolatableCallbacks.entrySet()) { EventTypeIndexBuilderValueIndexesPair pair = entry.getValue(); if (statementIds.contains(entry.getKey().getStatementId())) { list.add(new FilterSetEntry(entry.getKey(), pair.getFilterValueSet())); EventType eventType = pair.getFilterValueSet().getEventType(); FilterHandleSetNode rootNode = eventTypeIndex.get(eventType); // Now remove from tree for (int i = 0; i < pair.getIndexPairs().length; i++) { IndexTreeBuilder.remove(eventType, entry.getKey(), pair.getIndexPairs()[i], rootNode); } } } for (FilterSetEntry removed : list) { isolatableCallbacks.remove(removed.getHandle()); } } finally { callbacksLock.unlock(); } return new FilterSet(list); } /** * Add the filters, from previously-taken filters. * * @param filterSet to add * @param lockFactory lock factory */ public void apply(FilterSet filterSet, FilterServiceGranularLockFactory lockFactory) { for (FilterSetEntry entry : filterSet.getFilters()) { add(entry.getFilterValueSet(), entry.getHandle(), lockFactory); } } public boolean isSupportsTakeApply() { return isolatableCallbacks != null; } }