package org.infinispan.query.dsl.embedded.impl; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.infinispan.factories.annotations.Inject; import org.infinispan.interceptors.locking.ClusteringDependentLogic; import org.infinispan.notifications.Listener; import org.infinispan.notifications.cachelistener.CacheEntryListenerInvocation; import org.infinispan.notifications.cachelistener.CacheNotifier; import org.infinispan.notifications.cachelistener.CacheNotifierImpl; import org.infinispan.notifications.cachelistener.EventWrapper; import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted; import org.infinispan.notifications.cachelistener.annotation.CacheEntryActivated; import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated; import org.infinispan.notifications.cachelistener.annotation.CacheEntryExpired; import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated; import org.infinispan.notifications.cachelistener.annotation.CacheEntryLoaded; import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified; import org.infinispan.notifications.cachelistener.annotation.CacheEntryPassivated; import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved; import org.infinispan.notifications.cachelistener.annotation.CacheEntryVisited; import org.infinispan.notifications.cachelistener.event.CacheEntryEvent; import org.infinispan.notifications.cachelistener.event.Event; import org.infinispan.notifications.cachelistener.event.impl.EventImpl; import org.infinispan.notifications.cachelistener.filter.CacheEventConverter; import org.infinispan.notifications.cachelistener.filter.CacheEventFilter; import org.infinispan.notifications.cachelistener.filter.DelegatingCacheEntryListenerInvocation; import org.infinispan.notifications.cachelistener.filter.FilterIndexingServiceProvider; import org.infinispan.notifications.cachelistener.filter.IndexedFilter; import org.infinispan.objectfilter.FilterCallback; import org.infinispan.objectfilter.FilterSubscription; import org.infinispan.objectfilter.Matcher; /** * @author anistor@redhat.com * @since 8.1 */ public abstract class BaseJPAFilterIndexingServiceProvider implements FilterIndexingServiceProvider { private final ConcurrentMap<Matcher, FilteringListenerInvocation<?, ?>> filteringInvocations = new ConcurrentHashMap<>(4); private CacheNotifierImpl cacheNotifier; private ClusteringDependentLogic clusteringDependentLogic; @Inject protected void injectDependencies(CacheNotifier cacheNotifier, ClusteringDependentLogic clusteringDependentLogic) { this.cacheNotifier = (CacheNotifierImpl) cacheNotifier; this.clusteringDependentLogic = clusteringDependentLogic; } @Override public void start() { } @Override public void stop() { Collection<FilteringListenerInvocation<?, ?>> invocations = filteringInvocations.values(); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryActivated.class).removeAll(invocations); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryCreated.class).removeAll(invocations); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryInvalidated.class).removeAll(invocations); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryLoaded.class).removeAll(invocations); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryModified.class).removeAll(invocations); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryPassivated.class).removeAll(invocations); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryRemoved.class).removeAll(invocations); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryVisited.class).removeAll(invocations); cacheNotifier.getListenerCollectionForAnnotation(CacheEntriesEvicted.class).removeAll(invocations); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryExpired.class).removeAll(invocations); filteringInvocations.clear(); } @Override public <K, V> DelegatingCacheEntryListenerInvocation<K, V> interceptListenerInvocation(CacheEntryListenerInvocation<K, V> invocation) { return new DelegatingCacheEntryListenerInvocationImpl<>(invocation); } @Override public <K, V> void registerListenerInvocations(boolean isClustered, boolean isPrimaryOnly, boolean filterAndConvert, IndexedFilter<?, ?, ?> indexedFilter, Map<Class<? extends Annotation>, List<DelegatingCacheEntryListenerInvocation<K, V>>> listeners) { final Matcher matcher = getMatcher(indexedFilter); final String queryString = getQueryString(indexedFilter); final Map<String, Object> namedParameters = getNamedParameters(indexedFilter); final boolean isDeltaFilter = isDelta(indexedFilter); addFilteringInvocationForMatcher(matcher); Event.Type[] eventTypes = new Event.Type[listeners.keySet().size()]; int i = 0; for (Class<? extends Annotation> annotation : listeners.keySet()) { eventTypes[i++] = getEventTypeFromAnnotation(annotation); } Callback<K, V> callback = new Callback<>(matcher, isClustered, isPrimaryOnly, filterAndConvert, listeners); callback.subscription = matcher.registerFilter(queryString, namedParameters, callback, isDeltaFilter, eventTypes); } /** * Obtains the event type that corresponds to the given event annotation. * * @param annotation a CacheEntryXXX annotation * @return the event type or {@code null} if the given annotation is not supported */ private Event.Type getEventTypeFromAnnotation(Class<? extends Annotation> annotation) { if (annotation == CacheEntryCreated.class) return Event.Type.CACHE_ENTRY_CREATED; if (annotation == CacheEntryModified.class) return Event.Type.CACHE_ENTRY_MODIFIED; if (annotation == CacheEntryRemoved.class) return Event.Type.CACHE_ENTRY_REMOVED; if (annotation == CacheEntryActivated.class) return Event.Type.CACHE_ENTRY_ACTIVATED; if (annotation == CacheEntryInvalidated.class) return Event.Type.CACHE_ENTRY_INVALIDATED; if (annotation == CacheEntryLoaded.class) return Event.Type.CACHE_ENTRY_LOADED; if (annotation == CacheEntryPassivated.class) return Event.Type.CACHE_ENTRY_PASSIVATED; if (annotation == CacheEntryVisited.class) return Event.Type.CACHE_ENTRY_VISITED; if (annotation == CacheEntriesEvicted.class) return Event.Type.CACHE_ENTRY_EVICTED; if (annotation == CacheEntryExpired.class) return Event.Type.CACHE_ENTRY_EXPIRED; return null; } private void addFilteringInvocationForMatcher(Matcher matcher) { if (!filteringInvocations.containsKey(matcher)) { FilteringListenerInvocation filteringInvocation = new FilteringListenerInvocation(matcher); if (filteringInvocations.putIfAbsent(matcher, filteringInvocation) == null) { // todo these are added but never removed until stop is called cacheNotifier.getListenerCollectionForAnnotation(CacheEntryActivated.class).add(filteringInvocation); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryCreated.class).add(filteringInvocation); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryInvalidated.class).add(filteringInvocation); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryLoaded.class).add(filteringInvocation); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryModified.class).add(filteringInvocation); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryPassivated.class).add(filteringInvocation); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryRemoved.class).add(filteringInvocation); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryVisited.class).add(filteringInvocation); cacheNotifier.getListenerCollectionForAnnotation(CacheEntriesEvicted.class).add(filteringInvocation); cacheNotifier.getListenerCollectionForAnnotation(CacheEntryExpired.class).add(filteringInvocation); } } } private class Callback<K, V> implements FilterCallback { private final boolean isClustered; private final boolean isPrimaryOnly; private final boolean filterAndConvert; private final DelegatingCacheEntryListenerInvocation<K, V>[] activated_invocations; private final DelegatingCacheEntryListenerInvocation<K, V>[] created_invocations; private final DelegatingCacheEntryListenerInvocation<K, V>[] invalidated_invocations; private final DelegatingCacheEntryListenerInvocation<K, V>[] loaded_invocations; private final DelegatingCacheEntryListenerInvocation<K, V>[] modified_invocations; private final DelegatingCacheEntryListenerInvocation<K, V>[] passivated_invocations; private final DelegatingCacheEntryListenerInvocation<K, V>[] removed_invocations; private final DelegatingCacheEntryListenerInvocation<K, V>[] visited_invocations; private final DelegatingCacheEntryListenerInvocation<K, V>[] evicted_invocations; private final DelegatingCacheEntryListenerInvocation<K, V>[] expired_invocations; private final Matcher matcher; protected volatile FilterSubscription subscription; Callback(Matcher matcher, boolean isClustered, boolean isPrimaryOnly, boolean filterAndConvert, Map<Class<? extends Annotation>, List<DelegatingCacheEntryListenerInvocation<K, V>>> listeners) { this.matcher = matcher; this.isClustered = isClustered; this.isPrimaryOnly = isPrimaryOnly; this.filterAndConvert = filterAndConvert; activated_invocations = makeArray(listeners, CacheEntryActivated.class); created_invocations = makeArray(listeners, CacheEntryCreated.class); invalidated_invocations = makeArray(listeners, CacheEntryInvalidated.class); loaded_invocations = makeArray(listeners, CacheEntryLoaded.class); modified_invocations = makeArray(listeners, CacheEntryModified.class); passivated_invocations = makeArray(listeners, CacheEntryPassivated.class); removed_invocations = makeArray(listeners, CacheEntryRemoved.class); visited_invocations = makeArray(listeners, CacheEntryVisited.class); evicted_invocations = makeArray(listeners, CacheEntriesEvicted.class); expired_invocations = makeArray(listeners, CacheEntryExpired.class); } private DelegatingCacheEntryListenerInvocation<K, V>[] makeArray(Map<Class<? extends Annotation>, List<DelegatingCacheEntryListenerInvocation<K, V>>> listeners, Class<? extends Annotation> eventType) { List<DelegatingCacheEntryListenerInvocation<K, V>> invocations = listeners.get(eventType); if (invocations == null) { return null; } DelegatingCacheEntryListenerInvocation<K, V>[] invocationsArray = invocations.toArray(new DelegatingCacheEntryListenerInvocation[invocations.size()]); for (DelegatingCacheEntryListenerInvocation di : invocationsArray) { ((DelegatingCacheEntryListenerInvocationImpl) di).callback = this; } return invocationsArray; } void unregister() { FilterSubscription s = subscription; if (s != null) { // unregister only once matcher.unregisterFilter(s); subscription = null; } } @Override public void onFilterResult(Object userContext, Object eventType, Object instance, Object[] projection, Comparable[] sortProjection) { CacheEntryEvent<K, V> event = (CacheEntryEvent<K, V>) userContext; if (event.isPre() && isClustered || isPrimaryOnly && !clusteringDependentLogic.getCacheTopology().getDistribution(event.getKey()).isPrimary()) { return; } DelegatingCacheEntryListenerInvocation<K, V>[] invocations; switch (event.getType()) { case CACHE_ENTRY_ACTIVATED: invocations = activated_invocations; break; case CACHE_ENTRY_CREATED: invocations = created_invocations; break; case CACHE_ENTRY_INVALIDATED: invocations = invalidated_invocations; break; case CACHE_ENTRY_LOADED: invocations = loaded_invocations; break; case CACHE_ENTRY_MODIFIED: invocations = modified_invocations; break; case CACHE_ENTRY_PASSIVATED: invocations = passivated_invocations; break; case CACHE_ENTRY_REMOVED: invocations = removed_invocations; break; case CACHE_ENTRY_VISITED: invocations = visited_invocations; break; case CACHE_ENTRY_EVICTED: invocations = evicted_invocations; break; case CACHE_ENTRY_EXPIRED: invocations = expired_invocations; break; default: return; } boolean conversionDone = false; for (DelegatingCacheEntryListenerInvocation<K, V> invocation : invocations) { if (invocation.getObservation().shouldInvoke(event.isPre())) { if (!conversionDone) { if (filterAndConvert && event instanceof EventImpl) { //todo [anistor] can it not be an EventImpl? can it not be filterAndConvert? EventImpl<K, V> eventImpl = (EventImpl<K, V>) event; EventImpl<K, V> clone = eventImpl.clone(); clone.setValue((V) makeFilterResult(userContext, eventType, event.getKey(), projection == null ? instance : null, projection, sortProjection)); event = clone; } conversionDone = true; } invocation.invokeNoChecks(new EventWrapper<>(event.getKey(), event), false, filterAndConvert); } } } } private class DelegatingCacheEntryListenerInvocationImpl<K, V> extends DelegatingCacheEntryListenerInvocation<K, V> { protected Callback<K, V> callback; DelegatingCacheEntryListenerInvocationImpl(CacheEntryListenerInvocation<K, V> invocation) { super(invocation); } @Override public void unregister() { if (callback != null) { callback.unregister(); } } } private class FilteringListenerInvocation<K, V> implements CacheEntryListenerInvocation<K, V> { private final Matcher matcher; private FilteringListenerInvocation(Matcher matcher) { this.matcher = matcher; } @Override public Object getTarget() { return BaseJPAFilterIndexingServiceProvider.this; } @Override public void invoke(Event<K, V> event) { } @Override public void invoke(EventWrapper<K, V, CacheEntryEvent<K, V>> event, boolean isLocalNodePrimaryOwner) { matchEvent(event.getEvent(), matcher); } @Override public void invokeNoChecks(EventWrapper<K, V, CacheEntryEvent<K, V>> event, boolean skipQueue, boolean skipConverter) { } @Override public boolean isClustered() { return false; } @Override public boolean isSync() { return true; } @Override public UUID getIdentifier() { return null; } @Override public Listener.Observation getObservation() { return Listener.Observation.BOTH; } @Override public Class<? extends Annotation> getAnnotation() { return null; } @Override public CacheEventFilter<? super K, ? super V> getFilter() { return null; } @Override public <C> CacheEventConverter<? super K, ? super V, C> getConverter() { return null; } @Override public Set<Class<? extends Annotation>> getFilterAnnotations() { return null; } } protected abstract Matcher getMatcher(IndexedFilter<?, ?, ?> indexedFilter); protected abstract String getQueryString(IndexedFilter<?, ?, ?> indexedFilter); protected abstract Map<String, Object> getNamedParameters(IndexedFilter<?, ?, ?> indexedFilter); protected abstract boolean isDelta(IndexedFilter<?, ?, ?> indexedFilter); protected abstract void matchEvent(CacheEntryEvent event, Matcher matcher); protected abstract Object makeFilterResult(Object userContext, Object eventType, Object key, Object instance, Object[] projection, Comparable[] sortProjection); }