package org.infinispan.notifications.cachelistener;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.notifications.cachelistener.event.Event;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* This is the base class for use when listening to segment completions when doing initial event
* retrieval. This will handle keeping track of concurrent key updates as well as iteration by calling
* appropriate methods at the given time.
* <p>
* This base class provides a working set for tracking of entries as they are iterated on, assuming
* the {@link QueueingSegmentListener#markKeyAsProcessing(Object)} and
* method is invoked properly. Also this class provides the events that caused entry creations that may
* not be processed yet that are returned by the {@link QueueingSegmentListener#findCreatedEntries()} method.
*
* @author wburns
* @since 7.0
*/
abstract class BaseQueueingSegmentListener<K, V, E extends Event<K, V>> implements QueueingSegmentListener<K, V, E> {
protected final Log log = LogFactory.getLog(getClass());
protected boolean trace = log.isTraceEnabled();
protected final AtomicBoolean completed = new AtomicBoolean(false);
protected final ConcurrentMap<K, Object> notifiedKeys;
protected BaseQueueingSegmentListener() {
this.notifiedKeys = new ConcurrentHashMap<>();
}
@Override
public Object markKeyAsProcessing(K key) {
// By putting the NOTIFIED value it has signaled that any more updates for this key have to be enqueud instead
// of taking the last one
Object value = notifiedKeys.put(key, NOTIFIED);
if (value != null) {
if (trace) {
log.tracef("Processing key %s as a concurrent update occurred with value %s", key, value);
}
}
return value;
}
@Override
public Set<CacheEntry<K, V>> findCreatedEntries() {
Set<CacheEntry<K, V>> set = new HashSet<>();
// We also have to look for any additional creations that we didn't iterate on
for (Map.Entry<K, Object> entry : notifiedKeys.entrySet()) {
Object value = entry.getValue();
if (value != NOTIFIED) {
K key = entry.getKey();
Object replaceValue = value;
// Now try to put NOTIFIED in there - this is in case if another concurrent event comes in like a
// PUT/REMOVE/CLEAR
while (replaceValue != NOTIFIED && !notifiedKeys.replace(key, replaceValue, NOTIFIED)) {
replaceValue = notifiedKeys.get(key);
}
// Technically we should never get NOTIFIED as this is required to be called after manually marking
// keys as processed
if (replaceValue != NOTIFIED && replaceValue != REMOVED) {
set.add((CacheEntry<K, V>)replaceValue);
}
}
}
return set;
}
@Override
public void notifiedKey(K key) {
}
@Override
public void segmentCompleted(Set<Integer> segments) {
}
protected boolean addEvent(K key, Object value) {
boolean returnValue;
Object prevEvent = notifiedKeys.get(key);
if (prevEvent == null) {
Object nowPrevious = notifiedKeys.putIfAbsent(key, value);
if (nowPrevious == null) {
returnValue = true;
} else if (nowPrevious != NOTIFIED) {
returnValue = addEvent(key, value);
} else {
returnValue = false;
}
} else if (prevEvent != NOTIFIED) {
if (notifiedKeys.replace(key, prevEvent, value)) {
returnValue = true;
} else {
returnValue = addEvent(key, value);
}
} else {
returnValue = false;
}
return returnValue;
}
}