package org.infinispan.util;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.partitionhandling.AvailabilityMode;
import org.infinispan.remoting.transport.Address;
import org.infinispan.test.TestingUtil;
import org.infinispan.topology.CacheJoinInfo;
import org.infinispan.topology.CacheStatusResponse;
import org.infinispan.topology.CacheTopology;
import org.infinispan.topology.ClusterCacheStatus;
import org.infinispan.topology.ClusterTopologyManager;
import org.infinispan.topology.RebalancingStatus;
public class BlockingClusterTopologyManager implements ClusterTopologyManager {
private final ClusterTopologyManager delegate;
private final CopyOnWriteArrayList<Handle<CacheTopology>> topologyUpdates = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<Handle<Integer>> topologyConfirmations = new CopyOnWriteArrayList<>();
public static BlockingClusterTopologyManager replace(EmbeddedCacheManager cacheManager) {
ClusterTopologyManager original = TestingUtil.extractGlobalComponent(cacheManager, ClusterTopologyManager.class);
BlockingClusterTopologyManager bctm = new BlockingClusterTopologyManager(original);
TestingUtil.replaceComponent(cacheManager, ClusterTopologyManager.class, bctm, true);
try {
Field cacheStatusMapField = original.getClass().getDeclaredField("cacheStatusMap");
cacheStatusMapField.setAccessible(true);
ConcurrentMap<String, ClusterCacheStatus> cacheStatusMap = (ConcurrentMap<String, ClusterCacheStatus>) cacheStatusMapField.get(original);
Field clusterTopologyManagerField = ClusterCacheStatus.class.getDeclaredField("clusterTopologyManager");
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(clusterTopologyManagerField, clusterTopologyManagerField.getModifiers() & ~Modifier.FINAL);
clusterTopologyManagerField.setAccessible(true);
for (ClusterCacheStatus status : cacheStatusMap.values()) {
clusterTopologyManagerField.set(status, bctm);
}
} catch (Exception e) {
new IllegalStateException(e);
}
return bctm;
}
protected BlockingClusterTopologyManager(ClusterTopologyManager delegate) {
this.delegate = delegate;
}
@Override
public CacheStatusResponse handleJoin(String cacheName, Address joiner, CacheJoinInfo joinInfo, int viewId) throws Exception {
return delegate.handleJoin(cacheName, joiner, joinInfo, viewId);
}
@Override
public void handleLeave(String cacheName, Address leaver, int viewId) throws Exception {
delegate.handleLeave(cacheName, leaver, viewId);
}
public Handle<Integer> startBlockingTopologyConfirmations(Predicate<Integer> condition) {
Handle<Integer> handle = new Handle<>(condition);
topologyConfirmations.add(handle);
return handle;
}
@Override
public void handleRebalancePhaseConfirm(String cacheName, Address node, int topologyId, Throwable throwable, int viewId) throws Exception {
for (Handle<Integer> h : topologyConfirmations) {
if (h.condition.test(topologyId)) {
h.latch.blockIfNeeded();
}
}
delegate.handleRebalancePhaseConfirm(cacheName, node, topologyId, throwable, viewId);
}
@Override
public void broadcastRebalanceStart(String cacheName, CacheTopology cacheTopology, boolean totalOrder, boolean distributed) {
delegate.broadcastRebalanceStart(cacheName, cacheTopology, totalOrder, distributed);
}
public Handle<CacheTopology> startBlockingTopologyUpdate(Predicate<CacheTopology> condition) {
Handle<CacheTopology> handle = new Handle<>(condition);
topologyUpdates.add(handle);
return handle;
}
@Override
public void broadcastTopologyUpdate(String cacheName, CacheTopology cacheTopology, AvailabilityMode availabilityMode, boolean totalOrder, boolean distributed) {
for (Handle<CacheTopology> h : topologyUpdates) {
if (h.condition.test(cacheTopology)) {
h.latch.blockIfNeeded();
}
}
delegate.broadcastTopologyUpdate(cacheName, cacheTopology, availabilityMode, totalOrder, distributed);
}
@Override
public void broadcastStableTopologyUpdate(String cacheName, CacheTopology cacheTopology, boolean totalOrder, boolean distributed) {
delegate.broadcastStableTopologyUpdate(cacheName, cacheTopology, totalOrder, distributed);
}
@Override
public boolean isRebalancingEnabled() {
return delegate.isRebalancingEnabled();
}
@Override
public boolean isRebalancingEnabled(String cacheName) {
return delegate.isRebalancingEnabled(cacheName);
}
@Override
public void setRebalancingEnabled(boolean enabled) {
delegate.setRebalancingEnabled(enabled);
}
@Override
public void setRebalancingEnabled(String cacheName, boolean enabled) {
delegate.setRebalancingEnabled(cacheName, enabled);
}
@Override
public RebalancingStatus getRebalancingStatus(String cacheName) {
return delegate.getRebalancingStatus(cacheName);
}
@Override
public void forceRebalance(String cacheName) {
delegate.forceRebalance(cacheName);
}
@Override
public void forceAvailabilityMode(String cacheName, AvailabilityMode availabilityMode) {
delegate.forceAvailabilityMode(cacheName, availabilityMode);
}
@Override
public void handleShutdownRequest(String cacheName) throws Exception {
delegate.handleShutdownRequest(cacheName);
}
@Override
public void broadcastShutdownCache(String cacheName, CacheTopology currentTopology, boolean totalOrder, boolean distributed) throws Exception {
delegate.broadcastShutdownCache(cacheName, currentTopology, totalOrder, distributed);
}
public class Handle<T> {
private final Predicate<T> condition;
private final NotifierLatch latch = new NotifierLatch();
public Handle(Predicate<T> condition) {
this.condition = condition;
latch.startBlocking();
}
public void stopBlocking() {
latch.stopBlocking();
topologyUpdates.remove(this);
}
public void waitToBlock() throws InterruptedException {
latch.waitToBlock();
}
public void unblockOnce() {
latch.unblockOnce();
}
}
}