All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.infinispan.jcache.AbstractJCacheNotifier Maven / Gradle / Ivy

There is a newer version: 9.1.7.Final
Show newest version
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 {

   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> createdListeners =
         new CopyOnWriteArrayList>();

   private final List> updatedListeners =
         new CopyOnWriteArrayList>();

   private final List> removedListeners =
         new CopyOnWriteArrayList>();

   private final List> expiredListeners =
         new CopyOnWriteArrayList>();

   private final ConcurrentMap, CacheEntryListenerConfiguration> listenerCfgs =
         CollectionFactory.makeConcurrentMap();

   private AbstractJCacheListenerAdapter listenerAdapter;

   private ConcurrentMap, Queue> latchesByEventSource = new ConcurrentHashMap<>();

   public void addListener(CacheEntryListenerConfiguration reg,
         AbstractJCache jcache, AbstractJCacheNotifier notifier) {
      boolean addListenerAdapter = listenerCfgs.isEmpty();
      addListener(reg, false);

      if (addListenerAdapter) {
         listenerAdapter = createListenerAdapter(jcache, notifier);
         jcache.addListener(listenerAdapter);
      }
   }

   public void removeListener(CacheEntryListenerConfiguration reg,
         AbstractJCache jcache) {
      removeListener(reg);

      if (listenerCfgs.isEmpty())
         jcache.removeListener(listenerAdapter);
   }

   public void addSyncNotificationLatch(Cache cache, K key, V value, CountDownLatch latch) {
      EventSource eventSourceKey = new EventSource(cache, key, value);

      latchesByEventSource.computeIfAbsent(eventSourceKey, kvEventSource -> new ConcurrentLinkedQueue<>()).add(latch);
   }

   public void removeSyncNotificationLatch(Cache cache, K key, V value, CountDownLatch latch) {
      EventSource eventSourceKey = new EventSource(cache, key, value);

      Queue 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 cache, K key, V value) {
      EventSource eventSourceKey = new EventSource(cache, key, value);

      notifySync(latchesByEventSource.get(eventSourceKey));
   }

   private void notifySync(Queue latches) {
      if (latches == null) {
         return;
      }
      CountDownLatch latch = latches.poll();
      if (latch != null) {
         latch.countDown();
      }
   }

   public void notifyEntryCreated(Cache cache, K key, V value) {
      try {
         if (!createdListeners.isEmpty()) {
            List> events =
                  createEvent(cache, key, value, EventType.CREATED);
            for (CacheEntryCreatedListener listener : createdListeners)
               listener.onCreated(getEntryIterable(events, listenerCfgs.get(listener)));
         }
      } finally {
         notifySync(cache, key, value);
      }
   }

   public void notifyEntryUpdated(Cache cache, K key, V value) {
      try {
         if (!updatedListeners.isEmpty()) {
            List> events =
                  createEvent(cache, key, value, EventType.UPDATED);
            for (CacheEntryUpdatedListener listener : updatedListeners)
               listener.onUpdated(getEntryIterable(events, listenerCfgs.get(listener)));
         }
      } finally {
         notifySync(cache, key, value);
      }
   }

   public void notifyEntryRemoved(Cache cache, K key, V value) {
      try {
         if (!removedListeners.isEmpty()) {
            List> events =
                  createEvent(cache, key, value, EventType.REMOVED);
            for (CacheEntryRemovedListener listener : removedListeners) {
               listener.onRemoved(getEntryIterable(events, listenerCfgs.get(listener)));
            }
         }
      } finally {
         notifySync(cache, key, null);
      }
   }

   public void notifyEntryExpired(Cache cache, K key, V value) {
      if (!expiredListeners.isEmpty()) {
         List> events =
               createEvent(cache, key, value, EventType.EXPIRED);
         for (CacheEntryExpiredListener 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, CacheEntryListenerConfiguration> entry : listenerCfgs.entrySet()) {
         if (entry.getValue().isSynchronous() && listenerClass.isInstance(entry.getKey())) {
            return true;
         }
      }
      return false;
   }

   private Iterable> getEntryIterable(
         List> events,
         CacheEntryListenerConfiguration listenerCfg) {
      Factory> factory = listenerCfg.getCacheEntryEventFilterFactory();
      if (factory != null) {
         CacheEntryEventFilter filter = factory.create();
         return filter == null  ? events
               : new JCacheEventFilteringIterable(events, filter);
      }

      return events;
   }

   @SuppressWarnings("unchecked")
   private boolean addListener(CacheEntryListenerConfiguration listenerCfg, boolean addIfAbsent) {
      boolean added = false;
      CacheEntryListener listener =
            listenerCfg.getCacheEntryListenerFactory().create();
      if (listener instanceof CacheEntryCreatedListener)
         added = !containsListener(addIfAbsent, listener, createdListeners)
               && createdListeners.add((CacheEntryCreatedListener) listener);

      if (listener instanceof CacheEntryUpdatedListener)
         added = !containsListener(addIfAbsent, listener, updatedListeners)
               && updatedListeners.add((CacheEntryUpdatedListener) listener);

      if (listener instanceof CacheEntryRemovedListener)
         added = !containsListener(addIfAbsent, listener, removedListeners)
               && removedListeners.add((CacheEntryRemovedListener) listener);

      if (listener instanceof CacheEntryExpiredListener)
         added = !containsListener(addIfAbsent, listener, expiredListeners)
               && expiredListeners.add((CacheEntryExpiredListener) listener);

      if (added)
         listenerCfgs.put(listener, listenerCfg);

      return added;
   }

   private boolean containsListener(boolean addIfAbsent,
         CacheEntryListener listenerToAdd,
         List> listeners) {
      // If add only if no listener present, check the listeners collection
      if (addIfAbsent) {
         for (CacheEntryListener listener : listeners) {
            if (listener.equals(listenerToAdd))
               return true;
         }
      }

      return false;
   }

   private void removeListener(CacheEntryListenerConfiguration listenerCfg) {
      for (Map.Entry, CacheEntryListenerConfiguration> entry : listenerCfgs.entrySet()) {
         CacheEntryListenerConfiguration cfg = entry.getValue();
         if (cfg.equals(listenerCfg)) {
            CacheEntryListener 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> createEvent(
         Cache cache, K key, V value, EventType eventType) {
      List> events =
            Collections.>singletonList(
                  new RICacheEntryEvent(cache, key, value, eventType));
      if (isTrace) log.tracef("Received event: %s", events);
      return events;
   }

   protected abstract AbstractJCacheListenerAdapter createListenerAdapter(AbstractJCache jcache, AbstractJCacheNotifier notifier);

   private static class EventSource {
      private final Cache cache;
      private final K key;
      private final V value;

      public EventSource(final Cache 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;
      }
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy