io.vertx.spi.cluster.zookeeper.impl.ZKMap Maven / Gradle / Ivy
/*
* 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.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.VertxException;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.shareddata.impl.ClusterSerializable;
import org.apache.curator.RetryLoop;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.ACLBackgroundPathAndBytesable;
import org.apache.curator.framework.api.CuratorEventType;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;
import java.io.*;
import java.lang.reflect.Constructor;
import java.time.Instant;
import java.util.Base64;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Created by Stream.Liu
*/
abstract class ZKMap {
final CuratorFramework curator;
protected final Vertx vertx;
final String mapPath;
static final String ZK_PATH_ASYNC_MAP = "asyncMap";
static final String ZK_PATH_SYNC_MAP = "syncMap";
static final Predicate pathChecker = path -> {
Objects.requireNonNull(path, "zookeeper node path can not be null.");
if (path.contains("/")) throw new IllegalArgumentException("can not contain forward slash char in ZK node path");
return true;
};
private final RetryPolicy retryPolicy = new ExponentialBackoffRetry(100, 5);
ZKMap(CuratorFramework curator, Vertx vertx, String mapType, String mapName) {
this.curator = curator;
this.vertx = vertx;
pathChecker.test(mapName);
this.mapPath = "/" + mapType + "/" + mapName;
}
String keyPath(K k) {
pathChecker.test(k.toString());
return mapPath + "/" + k.toString();
}
Future assertKeyIsNotNull(Object key) {
boolean result = key == null;
if (result) return Future.failedFuture("key can not be null.");
else return Future.succeededFuture();
}
Future assertValueIsNotNull(Object value) {
boolean result = value == null;
if (result) return Future.failedFuture("value can not be null.");
else return Future.succeededFuture();
}
Future assertKeyAndValueAreNotNull(Object key, Object value) {
return assertKeyIsNotNull(key).compose(aVoid -> assertValueIsNotNull(value));
}
byte[] asByte(Object object) throws IOException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
DataOutput dataOutput = new DataOutputStream(byteOut);
if (object instanceof ClusterSerializable) {
ClusterSerializable clusterSerializable = (ClusterSerializable) object;
dataOutput.writeBoolean(true);
dataOutput.writeUTF(object.getClass().getName());
Buffer buffer = Buffer.buffer();
clusterSerializable.writeToBuffer(buffer);
byte[] bytes = buffer.getBytes();
dataOutput.writeInt(bytes.length);
dataOutput.write(bytes);
} else {
dataOutput.writeBoolean(false);
ByteArrayOutputStream javaByteOut = new ByteArrayOutputStream();
ObjectOutput objectOutput = new ObjectOutputStream(javaByteOut);
objectOutput.writeObject(object);
dataOutput.write(javaByteOut.toByteArray());
}
return byteOut.toByteArray();
}
T asObject(byte[] bytes) throws Exception {
if (bytes == null) return null; //TTL
ByteArrayInputStream byteIn = new ByteArrayInputStream(bytes);
DataInputStream in = new DataInputStream(byteIn);
boolean isClusterSerializable = in.readBoolean();
if (isClusterSerializable) {
String className = in.readUTF();
Class> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
int length = in.readInt();
byte[] body = new byte[length];
in.readFully(body);
try {
ClusterSerializable clusterSerializable;
//check clazz if have a public Constructor method.
if (clazz.getConstructors().length == 0) {
Constructor constructor = (Constructor) clazz.getDeclaredConstructor();
constructor.setAccessible(true);
clusterSerializable = (ClusterSerializable) constructor.newInstance();
} else {
clusterSerializable = (ClusterSerializable) clazz.newInstance();
}
clusterSerializable.readFromBuffer(0, Buffer.buffer(body));
return (T) clusterSerializable;
} catch (Exception e) {
throw new IllegalStateException("Failed to load class " + e.getMessage(), e);
}
} else {
byte[] body = new byte[in.available()];
in.readFully(body);
ObjectInputStream objectIn = new ObjectInputStream(new ByteArrayInputStream(body));
return (T) objectIn.readObject();
}
}
/**
* get data with Stat
*
* @param stat new Stat
* @param path node path
* @param result
* @return T
* @throws Exception
*/
T getData(Stat stat, String path) throws Exception {
T result = null;
if (null != curator.checkExists().forPath(path)) {
result = asObject(curator.getData().storingStatIn(stat).forPath(path));
} else {
curator.create().creatingParentsIfNeeded().forPath(path, asByte(null));
}
return result;
}
/**
* CAS Operation.
*
* @param startTime start time for backOff
* @param retries retry times backOff
* @param path node path
* @param expect the value expect in path.
* @param update the value should be update to the path.
* @return boolean
* @throws Exception
*/
boolean compareAndSet(long startTime, int retries, Stat stat, String path, V expect, V update) throws Exception {
V currentValue = getData(stat, path);
if (currentValue == expect || currentValue.equals(expect)) {
try {
curator.setData().withVersion(stat.getVersion()).forPath(path, asByte(update));
} catch (KeeperException.BadVersionException | KeeperException.NoNodeException e) {
// If the version has changed, block on the retry policy if necessary. If no more retries are remaining,
// fail the operation.
if (!retryPolicy.allowRetry(retries, Instant.now().toEpochMilli() - startTime, RetryLoop.getDefaultRetrySleeper())) {
throw new VertxException("failed to acquire optimistic lock");
}
}
return true;
} else {
return false;
}
}
Future checkExists(K k) {
return checkExists(keyPath(k));
}
Future checkExists(String path) {
Promise future = Promise.promise();
try {
curator.sync().inBackground((clientSync, eventSync) -> {
try {
if (eventSync.getType() == CuratorEventType.SYNC) {
curator.checkExists().inBackground((clientCheck, eventCheck) -> {
if (eventCheck.getType() == CuratorEventType.EXISTS) {
if (eventCheck.getStat() == null) {
vertx.runOnContext(aVoid -> future.complete(false));
} else {
vertx.runOnContext(aVoid -> future.complete(true));
}
}
}).forPath(path);
}
} catch (Exception ex) {
vertx.runOnContext(aVoid -> future.fail(ex));
}
}).forPath(path);
} catch (Exception ex) {
vertx.runOnContext(aVoid -> future.fail(ex));
}
return future.future();
}
Future create(K k, V v, Optional timeToLive) {
return create(keyPath(k), v, timeToLive);
}
Future create(String path, V v, Optional timeToLive) {
Promise future = Promise.promise();
try {
ACLBackgroundPathAndBytesable creator = timeToLive.isPresent()
? curator.create().withTtl(timeToLive.get()).creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT_WITH_TTL)
: curator.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT);
creator.inBackground((cl, el) -> {
if (el.getType() == CuratorEventType.CREATE) {
vertx.runOnContext(aVoid -> future.complete(el.getStat()));
}
}).forPath(path, asByte(v));
} catch (Exception ex) {
vertx.runOnContext(event -> future.fail(ex));
}
return future.future();
}
Future setData(K k, V v) {
return setData(keyPath(k), v);
}
Future setData(String path, V v) {
Promise future = Promise.promise();
try {
curator.setData().inBackground((client, event) -> {
if (event.getType() == CuratorEventType.SET_DATA) {
vertx.runOnContext(e -> future.complete(event.getStat()));
}
}).forPath(path, asByte(v));
} catch (Exception ex) {
vertx.runOnContext(event -> future.fail(ex));
}
return future.future();
}
Future delete(K k, V v) {
return delete(keyPath(k), v);
}
Future delete(String path, V v) {
Promise future = Promise.promise();
try {
curator.delete().deletingChildrenIfNeeded().inBackground((client, event) -> {
if (event.getType() == CuratorEventType.DELETE) {
//clean parent node if doesn't have child node.
String[] paths = path.split("/");
String parentNodePath = Stream.of(paths).limit(paths.length - 1).reduce((previous, current) -> previous + "/" + current).get();
curator.getChildren().inBackground((childClient, childEvent) -> {
if (childEvent.getChildren().size() == 0) {
curator.delete().inBackground((deleteClient, deleteEvent) -> {
if (deleteEvent.getType() == CuratorEventType.DELETE)
vertx.runOnContext(ea -> future.complete(v));
}).forPath(parentNodePath);
} else {
vertx.runOnContext(ea -> future.complete(v));
}
}).forPath(parentNodePath);
}
}).forPath(path);
} catch (Exception ex) {
vertx.runOnContext(aVoid -> future.fail(ex));
}
return future.future();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy