package org.infinispan.notifications.cachelistener.cluster.impl; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.UUID; import org.infinispan.Cache; import org.infinispan.commons.CacheException; import org.infinispan.distexec.DistributedExecutionCompletionService; import org.infinispan.distexec.DistributedExecutorService; import org.infinispan.factories.annotations.Start; import org.infinispan.notifications.cachelistener.cluster.ClusterEvent; import org.infinispan.notifications.cachelistener.cluster.ClusterEventCallable; import org.infinispan.notifications.cachelistener.cluster.ClusterEventManager; import org.infinispan.notifications.cachelistener.cluster.MultiClusterEventCallable; import org.infinispan.remoting.transport.Address; public class BatchingClusterEventManagerImpl<K, V> implements ClusterEventManager<K, V> { private final Cache<K, V> cache; private DistributedExecutorService distExecService; private final ThreadLocal<EventContext<K, V>> localContext = new ThreadLocal<>(); public BatchingClusterEventManagerImpl(Cache<K, V> cache) { this.cache = cache; } @Start public void start() { distExecService = SecurityActions.getDefaultExecutorService(cache); } @Override public void addEvents(Address target, UUID identifier, Collection<ClusterEvent<K, V>> events, boolean sync) { EventContext<K, V> ctx = localContext.get(); if (ctx == null) { ctx = new UnicastEventContext<>(); localContext.set(ctx); } ctx.addTargets(target, identifier, events, sync); } @Override public void sendEvents() { EventContext<K, V> ctx = localContext.get(); if (ctx != null) { ctx.sendToTargets(distExecService); localContext.remove(); } } @Override public void dropEvents() { localContext.remove(); } private interface EventContext<K, V> { void addTargets(Address address, UUID identifier, Collection<ClusterEvent<K, V>> events, boolean sync); void sendToTargets(DistributedExecutorService service); } protected static class UnicastEventContext<K, V> implements EventContext<K, V> { protected final Map<Address, TargetEvents<K, V>> targets = new HashMap<>(); @Override public void addTargets(Address address, UUID identifier, Collection<ClusterEvent<K, V>> events, boolean sync) { TargetEvents<K, V> targetEvents = targets.get(address); if (targetEvents == null) { targetEvents = new TargetEvents<>(); targets.put(address, targetEvents); } Map<UUID, Collection<ClusterEvent<K, V>>> listenerEvents = targetEvents.events; // This shouldn't be set before, so do put instead of doing get then put Collection<ClusterEvent<K, V>> prevEvents = listenerEvents.put(identifier, events); if (prevEvents != null) { // If we have multiple events to the same node for the same uuid condense them. This shouldn't really happen... events.addAll(prevEvents); } if (sync) { targetEvents.sync = true; } } @Override public void sendToTargets(DistributedExecutorService service) { DistributedExecutionCompletionService<Void> completion = new DistributedExecutionCompletionService<>(service); int syncCount = 0; for (Entry<Address, TargetEvents<K, V>> entry : targets.entrySet()) { TargetEvents<K, V> value = entry.getValue(); if (value.events.size() > 1) { if (value.sync) { completion.submit(entry.getKey(), new MultiClusterEventCallable<>(value.events)); syncCount++; } else { service.submit(entry.getKey(), new MultiClusterEventCallable<>(value.events)); } } else if (value.events.size() == 1) { Entry<UUID, Collection<ClusterEvent<K, V>>> entryValue = value.events.entrySet().iterator().next(); if (value.sync) { completion.submit(entry.getKey(), new ClusterEventCallable<>(entryValue.getKey(), entryValue.getValue())); syncCount++; } else { service.submit(entry.getKey(), new ClusterEventCallable<>(entryValue.getKey(), entryValue.getValue())); } } } try { for (int i = 0; i < syncCount; ++i) { completion.take(); } } catch (InterruptedException e) { throw new CacheException("Interrupted while waiting for event notifications to complete.", e); } } } private static class TargetEvents<K, V> { final Map<UUID, Collection<ClusterEvent<K, V>>> events = new HashMap<>(); boolean sync = false; } }