/* * Copyright (c) 2011-2016 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.spi.cluster.zookeeper.impl; import io.vertx.core.*; import io.vertx.core.json.JsonObject; import io.vertx.core.shareddata.AsyncMap; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.PathChildrenCache; import org.apache.zookeeper.data.Stat; import java.time.Instant; import java.util.Optional; import static io.vertx.spi.cluster.zookeeper.impl.AsyncMapTTLMonitor.*; /** * Created by Stream.Liu */ public class ZKAsyncMap<K, V> extends ZKMap<K, V> implements AsyncMap<K, V> { private final PathChildrenCache curatorCache; private AsyncMapTTLMonitor<K, V> asyncMapTTLMonitor; public ZKAsyncMap(Vertx vertx, CuratorFramework curator, AsyncMapTTLMonitor<K,V> asyncMapTTLMonitor, String mapName) { super(curator, vertx, ZK_PATH_ASYNC_MAP, mapName); this.curatorCache = new PathChildrenCache(curator, mapPath, true); try { this.asyncMapTTLMonitor = asyncMapTTLMonitor; curatorCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); } catch (Exception e) { throw new VertxException(e); } } @Override public void get(K k, Handler<AsyncResult<V>> asyncResultHandler) { assertKeyIsNotNull(k) .compose(aVoid -> checkExists(k)) .compose(checkResult -> { Future<V> future = Future.future(); if (checkResult) { ChildData childData = curatorCache.getCurrentData(keyPath(k)); if (childData != null && childData.getData() != null) { try { V value = asObject(childData.getData()); future.complete(value); } catch (Exception e) { future.fail(e); } } else { future.complete(); } } else { //ignore future.complete(); } return future; }) .setHandler(asyncResultHandler); } @Override public void put(K k, V v, Handler<AsyncResult<Void>> completionHandler) { put(k, v, Optional.empty(), completionHandler); } @Override public void put(K k, V v, long timeout, Handler<AsyncResult<Void>> completionHandler) { put(k, v, Optional.of(timeout), completionHandler); } private void put(K k, V v, Optional<Long> timeoutOptional, Handler<AsyncResult<Void>> completionHandler) { assertKeyAndValueAreNotNull(k, v) .compose(aVoid -> checkExists(k)) .compose(checkResult -> checkResult ? setData(k, v) : create(k, v)) .compose(aVoid -> { JsonObject body = new JsonObject().put(TTL_KEY_BODY_KEY_PATH, keyPath(k)); if (timeoutOptional.isPresent()) { asyncMapTTLMonitor.addAsyncMapWithPath(keyPath(k), this); body.put(TTL_KEY_BODY_TIMEOUT, timeoutOptional.get()); } else body.put(TTL_KEY_IS_CANCEL, true); //publish a ttl message to all nodes. vertx.eventBus().publish(TTL_KEY_HANDLER_ADDRESS, body); Future<Void> future = Future.future(); future.complete(); return future; }) .setHandler(completionHandler); } @Override public void putIfAbsent(K k, V v, Handler<AsyncResult<V>> completionHandler) { putIfAbsent(k, v, Optional.empty(), completionHandler); } @Override public void putIfAbsent(K k, V v, long timeout, Handler<AsyncResult<V>> completionHandler) { putIfAbsent(k, v, Optional.of(timeout), completionHandler); } private void putIfAbsent(K k, V v, Optional<Long> timeoutOptional, Handler<AsyncResult<V>> completionHandler) { assertKeyAndValueAreNotNull(k, v) .compose(aVoid -> { Future<V> innerFuture = Future.future(); vertx.executeBlocking(future -> { long startTime = Instant.now().toEpochMilli(); int retries = 0; for (; ; ) { try { Stat stat = new Stat(); String path = keyPath(k); V currentValue = getData(stat, path); if (compareAndSet(startTime, retries++, stat, path, currentValue, v)) { future.complete(currentValue); return; } } catch (Exception e) { future.fail(e); return; } } }, false, innerFuture.completer()); return innerFuture; }) .compose(value -> { JsonObject body = new JsonObject().put(TTL_KEY_BODY_KEY_PATH, keyPath(k)); if (timeoutOptional.isPresent()) { asyncMapTTLMonitor.addAsyncMapWithPath(keyPath(k), this); body.put(TTL_KEY_BODY_TIMEOUT, timeoutOptional.get()); } else body.put(TTL_KEY_IS_CANCEL, true); //publish a ttl message to all nodes. vertx.eventBus().publish(TTL_KEY_HANDLER_ADDRESS, body); return Future.succeededFuture(value); }) .setHandler(completionHandler); } @Override public void remove(K k, Handler<AsyncResult<V>> asyncResultHandler) { assertKeyIsNotNull(k).compose(aVoid -> { Future<V> future = Future.future(); get(k, future.completer()); return future; }).compose(value -> { Future<V> future = Future.future(); if (value != null) { return delete(k, value); } else { future.complete(); } return future; }).setHandler(asyncResultHandler); } @Override public void removeIfPresent(K k, V v, Handler<AsyncResult<Boolean>> resultHandler) { assertKeyAndValueAreNotNull(k, v) .compose(aVoid -> { Future<V> future = Future.future(); get(k, future.completer()); return future; }) .compose(value -> { Future<Boolean> future = Future.future(); if (value.equals(v)) { delete(k, v).setHandler(deleteResult -> { if (deleteResult.succeeded()) future.complete(true); else future.fail(deleteResult.cause()); }); } else { future.complete(false); } return future; }).setHandler(resultHandler); } @Override public void replace(K k, V v, Handler<AsyncResult<V>> asyncResultHandler) { assertKeyAndValueAreNotNull(k, v) .compose(aVoid -> { Future<V> innerFuture = Future.future(); vertx.executeBlocking(future -> { long startTime = Instant.now().toEpochMilli(); int retries = 0; for (; ; ) { try { Stat stat = new Stat(); String path = keyPath(k); V currentValue = getData(stat, path); //do not replace value if previous value is null if (currentValue == null) { future.complete(null); return; } if (compareAndSet(startTime, retries++, stat, path, currentValue, v)) { future.complete(currentValue); return; } } catch (Exception e) { future.fail(e); return; } } }, false, innerFuture.completer()); return innerFuture; }) .setHandler(asyncResultHandler); } @Override public void replaceIfPresent(K k, V oldValue, V newValue, Handler<AsyncResult<Boolean>> resultHandler) { assertKeyIsNotNull(k) .compose(aVoid -> assertValueIsNotNull(oldValue)) .compose(aVoid -> assertValueIsNotNull(newValue)) .compose(aVoid -> { Future<Boolean> innerFuture = Future.future(); vertx.executeBlocking(future -> { long startTime = Instant.now().toEpochMilli(); int retries = 0; for (; ; ) { try { Stat stat = new Stat(); String path = keyPath(k); V currentValue = getData(stat, path); if (!currentValue.equals(oldValue)) { future.complete(false); return; } if (compareAndSet(startTime, retries++, stat, path, oldValue, newValue)) { future.complete(true); return; } } catch (Exception e) { future.fail(e); return; } } }, false, innerFuture.completer()); return innerFuture; }) .setHandler(resultHandler); } @Override public void clear(Handler<AsyncResult<Void>> resultHandler) { //just remove parent node delete(mapPath, null).setHandler(result -> { if (result.succeeded()) { resultHandler.handle(Future.succeededFuture()); } else { resultHandler.handle(Future.failedFuture(result.cause())); } }); } @Override public void size(Handler<AsyncResult<Integer>> resultHandler) { try { curator.getChildren().inBackground((client, event) -> vertx.runOnContext(aVoid -> resultHandler.handle(Future.succeededFuture(event.getChildren().size())))) .forPath(mapPath); } catch (Exception e) { resultHandler.handle(Future.failedFuture(e)); } } }