/* * Copyright (c) 2011-2013 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.test.fakecluster; import io.vertx.core.AsyncResult; import io.vertx.core.Context; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.TaskQueue; import io.vertx.core.impl.VertxInternal; import io.vertx.core.shareddata.AsyncMap; import io.vertx.core.shareddata.Counter; import io.vertx.core.shareddata.Lock; import io.vertx.core.shareddata.impl.AsynchronousCounter; import io.vertx.core.shareddata.impl.AsynchronousLock; import io.vertx.core.spi.cluster.AsyncMultiMap; import io.vertx.core.spi.cluster.ChoosableIterable; import io.vertx.core.spi.cluster.ClusterManager; import io.vertx.core.spi.cluster.NodeListener; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Predicate; public class FakeClusterManager implements ClusterManager { private static Map<String, FakeClusterManager> nodes = Collections.synchronizedMap(new LinkedHashMap<>()); private static ConcurrentMap<String, ConcurrentMap> asyncMaps = new ConcurrentHashMap<>(); private static ConcurrentMap<String, ConcurrentMap> asyncMultiMaps = new ConcurrentHashMap<>(); private static ConcurrentMap<String, Map> syncMaps = new ConcurrentHashMap<>(); private static ConcurrentMap<String, AsynchronousLock> locks = new ConcurrentHashMap<>(); private static ConcurrentMap<String, AtomicLong> counters = new ConcurrentHashMap<>(); private String nodeID; private NodeListener nodeListener; private VertxInternal vertx; public void setVertx(Vertx vertx) { this.vertx = (VertxInternal) vertx; } private static void doJoin(String nodeID, FakeClusterManager node) { if (nodes.containsKey(nodeID)) { throw new IllegalStateException("Node has already joined!"); } nodes.put(nodeID, node); synchronized (nodes) { for (Map.Entry<String, FakeClusterManager> entry : nodes.entrySet()) { if (!entry.getKey().equals(nodeID)) { new Thread(() -> entry.getValue().memberAdded(nodeID)).start(); } } } } private synchronized void memberAdded(String nodeID) { if (isActive()) { try { if (nodeListener != null) { nodeListener.nodeAdded(nodeID); } } catch (Throwable t) { t.printStackTrace(); } } } private static void doLeave(String nodeID) { nodes.remove(nodeID); synchronized (nodes) { for (Map.Entry<String, FakeClusterManager> entry : nodes.entrySet()) { if (!entry.getKey().equals(nodeID)) { new Thread(() -> entry.getValue().memberRemoved(nodeID)).start(); } } } } private synchronized void memberRemoved(String nodeID) { if (isActive()) { try { if (nodeListener != null) { nodeListener.nodeLeft(nodeID); } } catch (Throwable t) { t.printStackTrace(); } } } @Override public <K, V> void getAsyncMultiMap(String name, Handler<AsyncResult<AsyncMultiMap<K, V>>> resultHandler) { ConcurrentMap map = asyncMultiMaps.get(name); if (map == null) { map = new ConcurrentHashMap<>(); ConcurrentMap prevMap = asyncMultiMaps.putIfAbsent(name, map); if (prevMap != null) { map = prevMap; } } @SuppressWarnings("unchecked") ConcurrentMap<K, ChoosableSet<V>> theMap = map; vertx.runOnContext(v -> resultHandler.handle(Future.succeededFuture(new FakeAsyncMultiMap<>(theMap)))); } @Override public <K, V> void getAsyncMap(String name, Handler<AsyncResult<AsyncMap<K, V>>> resultHandler) { ConcurrentMap map = asyncMaps.get(name); if (map == null) { map = new ConcurrentHashMap<>(); ConcurrentMap prevMap = asyncMaps.putIfAbsent(name, map); if (prevMap != null) { map = prevMap; } } @SuppressWarnings("unchecked") ConcurrentMap<K, V> theMap = map; vertx.runOnContext(v -> resultHandler.handle(Future.succeededFuture(new FakeAsyncMap<>(theMap)))); } @Override public <K, V> Map<K, V> getSyncMap(String name) { Map map = syncMaps.get(name); if (map == null) { map = new ConcurrentHashMap<>(); Map prevMap = syncMaps.putIfAbsent(name, map); if (prevMap != null) { map = prevMap; } } @SuppressWarnings("unchecked") Map<K, V> theMap = map; return theMap; } @Override public void getLockWithTimeout(String name, long timeout, Handler<AsyncResult<Lock>> resultHandler) { AsynchronousLock lock = new AsynchronousLock(vertx); AsynchronousLock prev = locks.putIfAbsent(name, lock); if (prev != null) { lock = prev; } FakeLock flock = new FakeLock(lock); flock.acquire(timeout, resultHandler); } @Override public void getCounter(String name, Handler<AsyncResult<Counter>> resultHandler) { AtomicLong counter = new AtomicLong(); AtomicLong prev = counters.putIfAbsent(name, counter); if (prev != null) { counter = prev; } AtomicLong theCounter = counter; Context context = vertx.getOrCreateContext(); context.runOnContext(v -> resultHandler.handle(Future.succeededFuture(new AsynchronousCounter(vertx, theCounter)))); } @Override public String getNodeID() { return nodeID; } @Override public List<String> getNodes() { ArrayList<String> res; synchronized (nodes) { res = new ArrayList<>(nodes.keySet()); } return res; } @Override public void nodeListener(NodeListener listener) { this.nodeListener = listener; } @Override public void join(Handler<AsyncResult<Void>> resultHandler) { vertx.executeBlocking(fut -> { synchronized (this) { this.nodeID = UUID.randomUUID().toString(); doJoin(nodeID, this); } fut.complete(); }, resultHandler); } @Override public void leave(Handler<AsyncResult<Void>> resultHandler) { vertx.executeBlocking(fut -> { synchronized (this) { if (nodeID != null) { if (nodeListener != null) { nodeListener = null; } doLeave(nodeID); this.nodeID = null; } } fut.complete(); }, resultHandler); } @Override public boolean isActive() { return nodeID != null; } public static void reset() { nodes.clear(); asyncMaps.clear(); asyncMultiMaps.clear(); locks.clear(); counters.clear(); syncMaps.clear(); } private class FakeLock implements Lock { private final AsynchronousLock delegate; public FakeLock(AsynchronousLock delegate) { this.delegate = delegate; } public void acquire(long timeout, Handler<AsyncResult<Lock>> resultHandler) { Context context = vertx.getOrCreateContext(); delegate.doAcquire(context, timeout, resultHandler); } @Override public void release() { delegate.release(); } } private class FakeAsyncMap<K, V> implements AsyncMap<K, V> { private final Map<K, V> map; public FakeAsyncMap(Map<K, V> map) { this.map = map; } @Override public void get(final K k, Handler<AsyncResult<V>> resultHandler) { vertx.executeBlocking(fut -> fut.complete(map.get(k)), resultHandler); } @Override public void put(final K k, final V v, Handler<AsyncResult<Void>> resultHandler) { vertx.executeBlocking(fut -> { map.put(k, v); fut.complete(); }, resultHandler); } @Override public void putIfAbsent(K k, V v, Handler<AsyncResult<V>> resultHandler) { vertx.executeBlocking(fut -> fut.complete(map.putIfAbsent(k, v)), resultHandler); } @Override public void put(K k, V v, long timeout, Handler<AsyncResult<Void>> completionHandler) { put(k, v, completionHandler); vertx.setTimer(timeout, tid -> map.remove(k)); } @Override public void putIfAbsent(K k, V v, long timeout, Handler<AsyncResult<V>> completionHandler) { Future<V> future = Future.future(); putIfAbsent(k, v, future); future.map(vv -> { if (vv == null) vertx.setTimer(timeout, tid -> map.remove(k)); return vv; }).setHandler(completionHandler); } @Override public void removeIfPresent(K k, V v, Handler<AsyncResult<Boolean>> resultHandler) { vertx.executeBlocking(fut -> fut.complete(map.remove(k, v)), resultHandler); } @Override public void replace(K k, V v, Handler<AsyncResult<V>> resultHandler) { vertx.executeBlocking(fut -> fut.complete(map.replace(k, v)), resultHandler); } @Override public void replaceIfPresent(K k, V oldValue, V newValue, Handler<AsyncResult<Boolean>> resultHandler) { vertx.executeBlocking(fut -> fut.complete(map.replace(k, oldValue, newValue)), resultHandler); } @Override public void clear(Handler<AsyncResult<Void>> resultHandler) { vertx.executeBlocking(fut -> { map.clear(); fut.complete(); }, resultHandler); } @Override public void size(Handler<AsyncResult<Integer>> resultHandler) { vertx.executeBlocking(fut -> fut.complete(map.size()), resultHandler); } @Override public void remove(final K k, Handler<AsyncResult<V>> resultHandler) { vertx.executeBlocking(fut -> fut.complete(map.remove(k)), resultHandler); } } private class FakeAsyncMultiMap<K, V> implements AsyncMultiMap<K, V> { private final ConcurrentMap<K, ChoosableSet<V>> map; private final TaskQueue taskQueue; public FakeAsyncMultiMap(ConcurrentMap<K, ChoosableSet<V>> map) { taskQueue = new TaskQueue(); this.map = map; } @Override public void add(final K k, final V v, Handler<AsyncResult<Void>> completionHandler) { ContextInternal ctx = vertx.getOrCreateContext(); ctx.executeBlocking(fut -> { ChoosableSet<V> vals = map.get(k); if (vals == null) { vals = new ChoosableSet<>(1); ChoosableSet<V> prevVals = map.putIfAbsent(k, vals); if (prevVals != null) { vals = prevVals; } } vals.add(v); fut.complete(); }, taskQueue, completionHandler); } @Override public void get(final K k, Handler<AsyncResult<ChoosableIterable<V>>> asyncResultHandler) { ContextInternal ctx = vertx.getOrCreateContext(); ctx.executeBlocking(fut -> { ChoosableIterable<V> it = map.get(k); if (it == null) { it = new ChoosableSet<>(0); } fut.complete(it); }, taskQueue, asyncResultHandler); } @Override public void remove(final K k, final V v, Handler<AsyncResult<Boolean>> completionHandler) { ContextInternal ctx = vertx.getOrCreateContext(); ctx.executeBlocking(fut -> { ChoosableSet<V> vals = map.get(k); boolean found = false; if (vals != null) { boolean removed = vals.remove(v); if (removed) { if (vals.isEmpty()) { map.remove(k); } found = true; } } fut.complete(found); }, taskQueue, completionHandler); } @Override public void removeAllForValue(final V v, Handler<AsyncResult<Void>> completionHandler) { removeAllMatching(v::equals, completionHandler); } @Override public void removeAllMatching(Predicate<V> p, Handler<AsyncResult<Void>> completionHandler) { ContextInternal ctx = vertx.getOrCreateContext(); ctx.executeBlocking(fut -> { Iterator<Map.Entry<K, ChoosableSet<V>>> mapIter = map.entrySet().iterator(); while (mapIter.hasNext()) { Map.Entry<K, ChoosableSet<V>> entry = mapIter.next(); ChoosableSet<V> vals = entry.getValue(); Iterator<V> iter = vals.iterator(); while (iter.hasNext()) { V val = iter.next(); if (p.test(val)) { iter.remove(); } } if (vals.isEmpty()) { mapIter.remove(); } } fut.complete(); }, taskQueue, completionHandler); } } }