package org.infinispan.jcache;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import javax.cache.Cache;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.configuration.Factory;
import javax.cache.event.CacheEntryCreatedListener;
import javax.cache.event.CacheEntryEvent;
import javax.cache.event.CacheEntryEventFilter;
import javax.cache.event.CacheEntryExpiredListener;
import javax.cache.event.CacheEntryListener;
import javax.cache.event.CacheEntryRemovedListener;
import javax.cache.event.CacheEntryUpdatedListener;
import javax.cache.event.EventType;
import org.infinispan.commons.logging.LogFactory;
import org.infinispan.commons.util.CollectionFactory;
import org.infinispan.jcache.logging.Log;
/**
* JCache notifications dispatcher.
*
* TODO: Deal with asynchronous listeners...
*
* @author Galder ZamarreƱo
* @since 5.3
*/
public abstract class AbstractJCacheNotifier<K, V> {
private static final Log log =
LogFactory.getLog(AbstractJCacheNotifier.class, Log.class);
private static final boolean isTrace = log.isTraceEnabled();
// Traversals are a not more common than mutations when it comes to
// keeping track of registered listeners, so use copy-on-write lists.
private final List<CacheEntryCreatedListener<K, V>> createdListeners =
new CopyOnWriteArrayList<CacheEntryCreatedListener<K, V>>();
private final List<CacheEntryUpdatedListener<K, V>> updatedListeners =
new CopyOnWriteArrayList<CacheEntryUpdatedListener<K, V>>();
private final List<CacheEntryRemovedListener<K, V>> removedListeners =
new CopyOnWriteArrayList<CacheEntryRemovedListener<K, V>>();
private final List<CacheEntryExpiredListener<K, V>> expiredListeners =
new CopyOnWriteArrayList<CacheEntryExpiredListener<K, V>>();
private final ConcurrentMap<CacheEntryListener<? super K, ? super V>, CacheEntryListenerConfiguration<K, V>> listenerCfgs =
CollectionFactory.makeConcurrentMap();
private AbstractJCacheListenerAdapter<K,V> listenerAdapter;
private ConcurrentMap<EventSource<K, V>, Queue<CountDownLatch>> latchesByEventSource = new ConcurrentHashMap<>();
public void addListener(CacheEntryListenerConfiguration<K, V> reg,
AbstractJCache<K, V> jcache, AbstractJCacheNotifier<K, V> notifier) {
boolean addListenerAdapter = listenerCfgs.isEmpty();
addListener(reg, false);
if (addListenerAdapter) {
listenerAdapter = createListenerAdapter(jcache, notifier);
jcache.addListener(listenerAdapter);
}
}
public void removeListener(CacheEntryListenerConfiguration<K, V> reg,
AbstractJCache<K, V> jcache) {
removeListener(reg);
if (listenerCfgs.isEmpty())
jcache.removeListener(listenerAdapter);
}
public void addSyncNotificationLatch(Cache<K, V> cache, K key, V value, CountDownLatch latch) {
EventSource<K, V> eventSourceKey = new EventSource<K, V>(cache, key, value);
latchesByEventSource.computeIfAbsent(eventSourceKey, kvEventSource -> new ConcurrentLinkedQueue<>()).add(latch);
}
public void removeSyncNotificationLatch(Cache<K, V> cache, K key, V value, CountDownLatch latch) {
EventSource<K, V> eventSourceKey = new EventSource<K, V>(cache, key, value);
Queue<CountDownLatch> latches = latchesByEventSource.get(eventSourceKey);
if (latches == null) {
return;
}
latchesByEventSource.compute(eventSourceKey, (kvEventSource, countDownLatches) -> {
countDownLatches.remove(latch);
return countDownLatches.isEmpty() ? null : countDownLatches;
});
}
private void notifySync(Cache<K, V> cache, K key, V value) {
EventSource<K, V> eventSourceKey = new EventSource<K, V>(cache, key, value);
notifySync(latchesByEventSource.get(eventSourceKey));
}
private void notifySync(Queue<CountDownLatch> latches) {
if (latches == null) {
return;
}
CountDownLatch latch = latches.poll();
if (latch != null) {
latch.countDown();
}
}
public void notifyEntryCreated(Cache<K, V> cache, K key, V value) {
try {
if (!createdListeners.isEmpty()) {
List<CacheEntryEvent<? extends K, ? extends V>> events =
createEvent(cache, key, value, EventType.CREATED);
for (CacheEntryCreatedListener<K, V> listener : createdListeners)
listener.onCreated(getEntryIterable(events, listenerCfgs.get(listener)));
}
} finally {
notifySync(cache, key, value);
}
}
public void notifyEntryUpdated(Cache<K, V> cache, K key, V value) {
try {
if (!updatedListeners.isEmpty()) {
List<CacheEntryEvent<? extends K, ? extends V>> events =
createEvent(cache, key, value, EventType.UPDATED);
for (CacheEntryUpdatedListener<K, V> listener : updatedListeners)
listener.onUpdated(getEntryIterable(events, listenerCfgs.get(listener)));
}
} finally {
notifySync(cache, key, value);
}
}
public void notifyEntryRemoved(Cache<K, V> cache, K key, V value) {
try {
if (!removedListeners.isEmpty()) {
List<CacheEntryEvent<? extends K, ? extends V>> events =
createEvent(cache, key, value, EventType.REMOVED);
for (CacheEntryRemovedListener<K, V> listener : removedListeners) {
listener.onRemoved(getEntryIterable(events, listenerCfgs.get(listener)));
}
}
} finally {
notifySync(cache, key, null);
}
}
public void notifyEntryExpired(Cache<K, V> cache, K key, V value) {
if (!expiredListeners.isEmpty()) {
List<CacheEntryEvent<? extends K, ? extends V>> events =
createEvent(cache, key, value, EventType.EXPIRED);
for (CacheEntryExpiredListener<K, V> listener : expiredListeners) {
listener.onExpired(getEntryIterable(events, listenerCfgs.get(listener)));
}
}
}
public boolean hasSyncCreatedListener() {
return hasSyncListener(CacheEntryCreatedListener.class);
}
public boolean hasSyncRemovedListener() {
return hasSyncListener(CacheEntryRemovedListener.class);
}
public boolean hasSyncUpdatedListener() {
return hasSyncListener(CacheEntryUpdatedListener.class);
}
private boolean hasSyncListener(Class<?> listenerClass) {
for (Map.Entry<CacheEntryListener<? super K, ? super V>, CacheEntryListenerConfiguration<K, V>> entry : listenerCfgs.entrySet()) {
if (entry.getValue().isSynchronous() && listenerClass.isInstance(entry.getKey())) {
return true;
}
}
return false;
}
private Iterable<CacheEntryEvent<? extends K, ? extends V>> getEntryIterable(
List<CacheEntryEvent<? extends K, ? extends V>> events,
CacheEntryListenerConfiguration<K, V> listenerCfg) {
Factory<CacheEntryEventFilter<? super K,? super V>> factory = listenerCfg.getCacheEntryEventFilterFactory();
if (factory != null) {
CacheEntryEventFilter<? super K, ? super V> filter = factory.create();
return filter == null ? events
: new JCacheEventFilteringIterable<K, V>(events, filter);
}
return events;
}
@SuppressWarnings("unchecked")
private boolean addListener(CacheEntryListenerConfiguration<K, V> listenerCfg, boolean addIfAbsent) {
boolean added = false;
CacheEntryListener<? super K, ? super V> listener =
listenerCfg.getCacheEntryListenerFactory().create();
if (listener instanceof CacheEntryCreatedListener)
added = !containsListener(addIfAbsent, listener, createdListeners)
&& createdListeners.add((CacheEntryCreatedListener<K, V>) listener);
if (listener instanceof CacheEntryUpdatedListener)
added = !containsListener(addIfAbsent, listener, updatedListeners)
&& updatedListeners.add((CacheEntryUpdatedListener<K, V>) listener);
if (listener instanceof CacheEntryRemovedListener)
added = !containsListener(addIfAbsent, listener, removedListeners)
&& removedListeners.add((CacheEntryRemovedListener<K, V>) listener);
if (listener instanceof CacheEntryExpiredListener)
added = !containsListener(addIfAbsent, listener, expiredListeners)
&& expiredListeners.add((CacheEntryExpiredListener<K, V>) listener);
if (added)
listenerCfgs.put(listener, listenerCfg);
return added;
}
private boolean containsListener(boolean addIfAbsent,
CacheEntryListener<? super K, ? super V> listenerToAdd,
List<? extends CacheEntryListener<? super K, ? super V>> listeners) {
// If add only if no listener present, check the listeners collection
if (addIfAbsent) {
for (CacheEntryListener<? super K, ? super V> listener : listeners) {
if (listener.equals(listenerToAdd))
return true;
}
}
return false;
}
private void removeListener(CacheEntryListenerConfiguration<K, V> listenerCfg) {
for (Map.Entry<CacheEntryListener<? super K, ? super V>, CacheEntryListenerConfiguration<K, V>> entry : listenerCfgs.entrySet()) {
CacheEntryListenerConfiguration<K, V> cfg = entry.getValue();
if (cfg.equals(listenerCfg)) {
CacheEntryListener<? super K, ? super V> listener = entry.getKey();
if (listener instanceof CacheEntryCreatedListener)
createdListeners.remove(listener);
if (listener instanceof CacheEntryUpdatedListener)
updatedListeners.remove(listener);
if (listener instanceof CacheEntryRemovedListener)
removedListeners.remove(listener);
if (listener instanceof CacheEntryExpiredListener)
expiredListeners.remove(listener);
}
}
}
private List<CacheEntryEvent<? extends K, ? extends V>> createEvent(
Cache<K, V> cache, K key, V value, EventType eventType) {
List<CacheEntryEvent<? extends K, ? extends V>> events =
Collections.<CacheEntryEvent<? extends K, ? extends V>>singletonList(
new RICacheEntryEvent<K, V>(cache, key, value, eventType));
if (isTrace) log.tracef("Received event: %s", events);
return events;
}
protected abstract AbstractJCacheListenerAdapter<K, V> createListenerAdapter(AbstractJCache<K, V> jcache, AbstractJCacheNotifier<K, V> notifier);
private static class EventSource<K, V> {
private final Cache<K, V> cache;
private final K key;
private final V value;
public EventSource(final Cache<K, V> cache, final K key, final V value) {
this.cache = cache;
this.key = key;
this.value = value;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof EventSource)) {
return false;
}
EventSource<?, ?> otherEventSource = (EventSource<?, ?>) obj;
return equalOrNull(cache, otherEventSource.cache)
&& equalOrNull(key, otherEventSource.key)
&& equalOrNull(value, otherEventSource.value);
}
private static boolean equalOrNull(Object obj1, Object obj2) {
return ((obj1 == null) && (obj2 == null)) || ((obj1 != null) && obj1.equals(obj2));
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + (cache == null ? 0 : cache.hashCode());
result = 31 * result + (key == null ? 0 : key.hashCode());
result = 31 * result + (value == null ? 0 : value.hashCode());
return result;
}
}
}