package org.infinispan.notifications.cachelistener.cluster;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.infinispan.Cache;
import org.infinispan.commons.marshall.AbstractExternalizer;
import org.infinispan.commons.marshall.MarshallUtil;
import org.infinispan.distexec.DistributedCallable;
import org.infinispan.distexec.DistributedExecutorService;
import org.infinispan.factories.ComponentRegistry;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.marshall.core.Ids;
import org.infinispan.notifications.cachelistener.CacheNotifier;
import org.infinispan.notifications.cachelistener.filter.CacheEventConverter;
import org.infinispan.notifications.cachelistener.filter.CacheEventFilter;
import org.infinispan.notifications.cachelistener.filter.CacheEventFilterConverter;
import org.infinispan.notifications.cachemanagerlistener.CacheManagerNotifier;
import org.infinispan.remoting.transport.Address;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* This DistributedCallable is used to install a {@link RemoteClusterListener} on the resulting node. This class
* also has checks to ensure that if the listener is attempted to be installed from more than 1 source only 1 will be
* installed as well if a node goes down while installing will also remove the listener.
*
* @author wburns
* @since 7.0
*/
public class ClusterListenerReplicateCallable<K, V> implements DistributedCallable<K, V, Void> {
private static final Log log = LogFactory.getLog(ClusterListenerReplicateCallable.class);
private static final boolean trace = log.isTraceEnabled();
private transient EmbeddedCacheManager cacheManager;
private transient CacheNotifier cacheNotifier;
private transient CacheManagerNotifier cacheManagerNotifier;
private transient DistributedExecutorService distExecutor;
private transient Address ourAddress;
private transient ClusterEventManager<K, V> eventManager;
private final UUID identifier;
private final CacheEventFilter<K, V> filter;
private final CacheEventConverter<K, V, ?> converter;
private final Address origin;
private final boolean sync;
private final Set<Class<? extends Annotation>> filterAnnotations;
public ClusterListenerReplicateCallable(UUID identifier, Address origin, CacheEventFilter<K, V> filter,
CacheEventConverter<K, V, ?> converter, boolean sync,
Set<Class<? extends Annotation>> filterAnnotations) {
this.identifier = identifier;
this.origin = origin;
this.filter = filter;
this.converter = converter;
this.sync = sync;
this.filterAnnotations = filterAnnotations;
if (trace)
log.tracef("Created clustered listener replicate callable for: %s", filterAnnotations);
}
@Override
public void setEnvironment(Cache<K, V> cache, Set<K> inputKeys) {
cacheManager = cache.getCacheManager();
ComponentRegistry componentRegistry = cache.getAdvancedCache().getComponentRegistry();
cacheNotifier = componentRegistry.getComponent(CacheNotifier.class);
cacheManagerNotifier = cache.getCacheManager().getGlobalComponentRegistry().getComponent(
CacheManagerNotifier.class);
distExecutor = SecurityActions.getDefaultExecutorService(cache);
ourAddress = cache.getCacheManager().getAddress();
eventManager = componentRegistry.getComponent(ClusterEventManager.class);
if (filter != null) {
componentRegistry.wireDependencies(filter);
}
if (converter != null && converter != filter) {
componentRegistry.wireDependencies(converter);
}
}
@Override
public Void call() throws Exception {
// Only register listeners if we aren't the ones that registered the cluster listener
if (!ourAddress.equals(origin)) {
// Make sure the origin is around otherwise don't register the listener - some way with identifier (CHM maybe?)
if (cacheManager.getMembers().contains(origin)) {
// Prevent multiple invocations to get in here at once, which should prevent concurrent registration of
// the same id. Note we can't use a static CHM due to running more than 1 node in same JVM
synchronized (cacheNotifier) {
boolean alreadyInstalled = false;
// First make sure the listener is not already installed, if it is we don't do anything.
for (Object installedListener : cacheNotifier.getListeners()) {
if (installedListener instanceof RemoteClusterListener &&
identifier.equals(((RemoteClusterListener)installedListener).getId())) {
alreadyInstalled = true;
break;
}
}
if (!alreadyInstalled) {
RemoteClusterListener listener = new RemoteClusterListener(identifier, origin, distExecutor, cacheNotifier,
cacheManagerNotifier, eventManager, sync);
cacheNotifier.addFilteredListener(listener, filter, converter, filterAnnotations);
cacheManagerNotifier.addListener(listener);
// It is possible the member is now gone after registered, if so we have to remove just to be sure
if (!cacheManager.getMembers().contains(origin)) {
cacheNotifier.removeListener(listener);
cacheManagerNotifier.removeListener(listener);
if (trace) {
log.tracef("Removing local cluster listener for remote cluster listener that was just registered, as the origin %s went away concurrently", origin);
}
} else if (trace) {
log.tracef("Registered local cluster listener for remote cluster listener from origin %s with id %s",
origin, identifier);
}
} else if (trace) {
log.tracef("Local cluster listener from origin %s with id %s was already installed, ignoring",
origin, identifier);
}
}
} else if (trace) {
log.tracef("Not registering local cluster listener for remote cluster listener from origin %s, as the origin went away",
origin);
}
} else if (trace) {
log.trace("Not registering local cluster listener as we are the node who registered the cluster listener");
}
return null;
}
public static class Externalizer extends AbstractExternalizer<ClusterListenerReplicateCallable> {
@Override
public Set<Class<? extends ClusterListenerReplicateCallable>> getTypeClasses() {
return Collections.singleton(ClusterListenerReplicateCallable.class);
}
@Override
public void writeObject(ObjectOutput output, ClusterListenerReplicateCallable object) throws IOException {
output.writeObject(object.identifier);
output.writeObject(object.origin);
output.writeObject(object.filter);
if (object.filter == object.converter && object.filter instanceof CacheEventFilterConverter) {
output.writeBoolean(true);
} else {
output.writeBoolean(false);
output.writeObject(object.converter);
}
output.writeBoolean(object.sync);
MarshallUtil.marshallCollection(object.filterAnnotations, output);
}
@Override
public ClusterListenerReplicateCallable readObject(ObjectInput input) throws IOException, ClassNotFoundException {
UUID id = (UUID)input.readObject();
Address address = (Address)input.readObject();
CacheEventFilter filter = (CacheEventFilter)input.readObject();
boolean sameConverter = input.readBoolean();
CacheEventConverter converter;
if (sameConverter) {
converter = (CacheEventFilterConverter)filter;
} else {
converter = (CacheEventConverter)input.readObject();
}
boolean sync = input.readBoolean();
Set<Class<? extends Annotation>> listenerAnnots = MarshallUtil.unmarshallCollection(input, HashSet::new);
return new ClusterListenerReplicateCallable(id, address, filter, converter, sync, listenerAnnots);
}
@Override
public Integer getId() {
return Ids.CLUSTER_LISTENER_REPLICATE_CALLABLE;
}
}
}