package org.infinispan.notifications.cachelistener.cluster;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import org.infinispan.commons.util.CollectionFactory;
import org.infinispan.distexec.DistributedExecutorService;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.CacheNotifier;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryExpired;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
import org.infinispan.notifications.cachelistener.annotation.TransactionCompleted;
import org.infinispan.notifications.cachelistener.event.CacheEntryEvent;
import org.infinispan.notifications.cachelistener.event.TransactionCompletedEvent;
import org.infinispan.notifications.cachemanagerlistener.CacheManagerNotifier;
import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged;
import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent;
import org.infinispan.remoting.transport.Address;
import org.infinispan.transaction.xa.GlobalTransaction;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import net.jcip.annotations.ThreadSafe;
/**
* A listener that installed locally on each node when a cluster listener is installed on a given node.
*
* @author wburns
* @since 7.0
*/
@ThreadSafe
@Listener(primaryOnly = true, observation = Listener.Observation.POST)
public class RemoteClusterListener {
private static final Log log = LogFactory.getLog(RemoteClusterListener.class);
private static final boolean trace = log.isTraceEnabled();
private final UUID id;
private final Address origin;
private final DistributedExecutorService distExecService;
private final CacheNotifier cacheNotifier;
private final CacheManagerNotifier cacheManagerNotifier;
private final ClusterEventManager eventManager;
private final boolean sync;
private final ConcurrentMap<GlobalTransaction, Queue<CacheEntryEvent>> transactionChanges =
CollectionFactory.makeConcurrentMap();
public RemoteClusterListener(UUID id, Address origin, DistributedExecutorService distExecService, CacheNotifier cacheNotifier,
CacheManagerNotifier cacheManagerNotifier, ClusterEventManager eventManager, boolean sync) {
this.id = id;
this.origin = origin;
this.distExecService = distExecService;
this.cacheNotifier = cacheNotifier;
this.cacheManagerNotifier = cacheManagerNotifier;
this.eventManager = eventManager;
this.sync = sync;
}
public UUID getId() {
return id;
}
public Address getOwnerAddress() {
return origin;
}
@ViewChanged
public void viewChange(ViewChangedEvent event) {
if (!event.getNewMembers().contains(origin)) {
if (trace) {
log.tracef("Origin %s storing cluster listener is gone, removing local listener", origin);
}
removeListener();
}
}
public void removeListener() {
cacheNotifier.removeListener(this);
cacheManagerNotifier.removeListener(this);
}
@CacheEntryCreated
@CacheEntryModified
@CacheEntryRemoved
@CacheEntryExpired
public void handleClusterEvents(CacheEntryEvent event) throws Exception {
GlobalTransaction transaction = event.getGlobalTransaction();
if (transaction != null) {
// If we are in a transaction, queue up those events so we can send them as 1 batch.
Queue<CacheEntryEvent> events = transactionChanges.get(transaction);
if (events == null) {
events = new ConcurrentLinkedQueue<>();
Queue<CacheEntryEvent> otherQueue = transactionChanges.putIfAbsent(transaction, events);
if (otherQueue != null) {
events = otherQueue;
}
}
events.add(event);
} else {
// Send event back to origin who has the cluster listener
if (trace) {
log.tracef("Passing Event to manager %s to send to %s", event, origin);
}
eventManager.addEvents(origin, id, Collections.singleton(ClusterEvent.fromEvent(event)), sync);
}
}
@TransactionCompleted
public void transactionCompleted(TransactionCompletedEvent event) throws Exception {
Queue<CacheEntryEvent> events = transactionChanges.remove(event.getGlobalTransaction());
if (event.isTransactionSuccessful() && events != null) {
List<ClusterEvent> eventsToSend = new ArrayList<>(events.size());
for (CacheEntryEvent cacheEvent : events) {
eventsToSend.add(ClusterEvent.fromEvent(cacheEvent));
// Send event back to origin who has the cluster listener
if (trace) {
log.tracef("Passing Event(s) to manager %s to send to %s", eventsToSend, origin);
}
}
eventManager.addEvents(origin, id, eventsToSend, sync);
}
}
}