All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kz.greetgo.logging.zookeeper.config.EventConfigStorageZooKeeper Maven / Gradle / Ivy

There is a newer version: 0.2.0
Show newest version
package kz.greetgo.logging.zookeeper.config;

import kz.greetgo.logging.zookeeper.core.ZookeeperConnectParams;
import lombok.NonNull;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

public class EventConfigStorageZooKeeper extends EventConfigStorageAbstract implements AutoCloseable {

  private final @NonNull ZookeeperConnectParams connectParams;
  private final String rootPath;

  private final AtomicReference zkHolder = new AtomicReference<>(null);

  private final AtomicReference clientHolder = new AtomicReference<>(null);

  public EventConfigStorageZooKeeper(String rootPath, @NonNull ZookeeperConnectParams connectParams) {
    this.connectParams = connectParams;
    this.rootPath = rootPath;
  }

  public void reset() {
    {
      ZooKeeper current = zkHolder.getAndSet(null);
      if (current != null) {
        try {
          current.close();
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
    }
    {
      CuratorFramework current = clientHolder.getAndSet(null);
      if (current != null) {
        current.close();
      }
    }
  }

  public CuratorFramework client() {
    if (!opened.get()) {
      throw new RuntimeException(getClass().getSimpleName() + " closed");
    }
    return clientHolder.accumulateAndGet(null, (current, ignore) -> current != null ? current : createClient());
  }

  private CuratorFramework createClient() {
    CuratorFramework client = connectParams.createClient();
    client.start();
    prepareWatchers(client);
    return client;
  }

  private final AtomicBoolean opened = new AtomicBoolean(true);

  @Override
  public void close() {
    opened.set(false);
    reset();
  }

  private String slashRootPath() {
    if (rootPath == null) {
      return "/";
    }
    if (rootPath.startsWith("/")) {
      return rootPath;
    }
    return "/" + rootPath;
  }

  private String zNode(String path) {

    String slashPath = path == null ? "/" : (path.startsWith("/") ? path : "/" + path);

    if (rootPath == null) {
      return slashPath;
    }

    String slashRootPath = slashRootPath();
    if (slashRootPath.endsWith("/")) {
      return slashRootPath + slashPath.substring(1);
    } else {
      return slashRootPath + slashPath;
    }

  }

  private String zNodeToPath(String zNode) {
    if (rootPath == null) {
      if (zNode == null || zNode.isEmpty()) {
        return null;
      }
      if (zNode.startsWith("/")) {
        return zNode.substring(1);
      }
      return zNode;
    }

    String slashRootPath = slashRootPath();

    if (zNode == null || zNode.isEmpty()) {
      return null;
    }

    if (slashRootPath.equals(zNode)) {
      return slashRootPath;
    }

    if (!zNode.startsWith(slashRootPath + "/")) {
      return null;
    }

    return zNode.substring(slashRootPath.length() + 1);
  }

  @Override
  public Optional createdAt(String path) {

    try {

      //noinspection resource
      return Optional.ofNullable(

        client().checkExists().forPath(zNode(path))

      ).map(Stat::getCtime).map(Date::new);

    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }

  }

  private  Result retrySome(Set> retryExceptions, ZookeeperTry zookeeperTry) {
    int tryCount = connectParams.getRetriesOnSessionExpiredException();
    int tries = 0;

    List prevExceptions = new ArrayList<>();

    TRY:
    while (true) {
      tries++;
      try {

        return zookeeperTry.tryOperation();

      } catch (Exception error) {

        for (Class retryException : retryExceptions) {
          if (retryException.isInstance(error)) {

            if (tries <= tryCount) {
              prevExceptions.add(error.getClass().getName() + ": " + error.getMessage());
              reset();
              continue TRY;
            }

            throw new RuntimeException("IWQ5D8RolL :: PrevExceptions: " + String.join("; ", prevExceptions), error);
          }
        }

        throw new RuntimeException("doJF0012B1 :: PrevExceptions: " + String.join("; ", prevExceptions), error);
      }
    }
  }

  @Override
  public Optional lastModifiedAt(String path) {
    return retrySome(Set.of(
      KeeperException.SessionExpiredException.class
    ), () -> {
      //noinspection resource
      return Optional.ofNullable(

        client().checkExists().forPath(zNode(path))

      ).map(Stat::getMtime).map(Date::new);
    });
  }

  @Override
  public byte[] readContent(String path) {
    return retrySome(Set.of(
      KeeperException.SessionExpiredException.class
    ), () -> {

      //noinspection resource
      CuratorFramework client = client();
      String zNode = zNode(path);

      client.checkExists().forPath(zNode);

      Stat stat = client.checkExists().forPath(zNode);
      if (stat == null) {
        return null;
      }

      return client.getData().forPath(zNode);
    });
  }

  @Override
  public void writeContent(String path, byte[] content) {
    retrySome(Set.of(
      KeeperException.SessionExpiredException.class,
      KeeperException.NodeExistsException.class,
      KeeperException.BadVersionException.class
    ), () -> {
      doWriteContent(path, content);
      return null;
    });
  }

  private void doWriteContent(String path, byte[] content) throws Exception {
    byte[] current = readContent(path);

    if (Arrays.equals(current, content)) {
      return;
    }

    //noinspection resource
    CuratorFramework client = client();
    String zNode = zNode(path);

    Stat stat = client.checkExists().forPath(zNode);

    if (content == null) {

      if (stat == null) {
        return;
      }

      nodesData.remove(path);

      client.delete().withVersion(stat.getVersion()).forPath(zNode);

    } else {

      nodesData.put(path, content);

      if (stat == null) {
        client.create().creatingParentContainersIfNeeded().forPath(zNode, content);
      } else {
        client.setData().withVersion(stat.getVersion()).forPath(zNode, content);
      }

    }
  }

  private final ConcurrentHashMap lookingForMap = new ConcurrentHashMap<>();

  private void prepareWatchers(CuratorFramework newClient) {

    List zNodes = new ArrayList<>(lookingForMap.keySet());

    for (String zNode : zNodes) {
      installWatcherOn(newClient, zNode);
    }

  }

  private void installWatcherOn(CuratorFramework client, String zNode) {
    retrySome(Set.of(
      KeeperException.SessionExpiredException.class
    ), () -> {

      lookingForMap.put(zNode, client);
      client.checkExists().usingWatcher((CuratorWatcher) this::processEvent).forPath(zNode);

      return null;
    });
  }

  private static ConfigEventType eventTypeToType(Watcher.Event.EventType type) {
    if (type == null) {
      return null;
    }
    switch (type) {
      case NodeCreated:
        return ConfigEventType.CREATE;
      case NodeDataChanged:
        return ConfigEventType.UPDATE;
      case NodeDeleted:
        return ConfigEventType.DELETE;
      default:
        return null;
    }
  }

  @Override
  public void ensureLookingFor(String path) {

    if (path == null) {
      throw new IllegalArgumentException("path == null");
    }

    CuratorFramework client = client();
    String zNode = zNode(path);

    if (client == lookingForMap.get(zNode)) {
      return;
    }

    {
      byte[] content = readContent(path);
      if (content == null) {
        nodesData.remove(path);
      } else {
        nodesData.put(path, content);
      }
    }

    installWatcherOn(client, zNode);
  }

  private void processEvent(WatchedEvent event) {

    String zNode = event.getPath();

    String path = zNodeToPath(zNode);

    if (path == null) {
      return;
    }

    ConfigEventType eventType = eventTypeToType(event.getType());
    if (eventType == null) {
      return;
    }

    if (opened.get() && lookingForMap.containsKey(zNode)) {
      installWatcherOn(client(), zNode);
    }

    fireConfigEventHandlerLocal(path, eventType);

  }

  private final ConcurrentHashMap nodesData = new ConcurrentHashMap<>();

  private void fireConfigEventHandlerLocal(String path, ConfigEventType eventType) {

    if (eventType == ConfigEventType.CREATE || eventType == ConfigEventType.UPDATE) {

      while (true) {
        byte[] current = readContent(path);
        if (current == null) {
          return;
        }
        byte[] cached = nodesData.get(path);
        if (Arrays.equals(current, cached)) {
          return;
        }
        if (nodesData.replace(path, cached, current)) {
          fireConfigEventHandler(path, eventType);
          return;
        }
      }

    }

    if (eventType == ConfigEventType.DELETE) {
      while (true) {
        byte[] current = readContent(path);
        if (current != null) {
          return;
        }

        byte[] cached = nodesData.get(path);
        if (cached == null) {
          return;
        }

        if (nodesData.remove(path, cached)) {
          fireConfigEventHandler(path, eventType);
        }
      }
    }

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy