com.spotify.helios.servicescommon.coordination.DefaultZooKeeperClient.java Source code

Java tutorial

Introduction

Here is the source code for com.spotify.helios.servicescommon.coordination.DefaultZooKeeperClient.java

Source

/*
 * Copyright (c) 2014 Spotify AB.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.spotify.helios.servicescommon.coordination;

import com.fasterxml.jackson.databind.JavaType;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.transaction.CuratorTransactionFinal;
import org.apache.curator.framework.api.transaction.CuratorTransactionResult;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.listen.Listenable;
import org.apache.curator.framework.recipes.nodes.PersistentEphemeralNode;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.utils.PathUtils;
import org.apache.curator.utils.ZKPaths;
import org.apache.zookeeper.CreateMode;
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.ACL;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.base.Throwables.propagateIfInstanceOf;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newLinkedList;
import static com.google.common.collect.Lists.reverse;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;

public class DefaultZooKeeperClient implements ZooKeeperClient {

    private static final Logger log = LoggerFactory.getLogger(DefaultZooKeeperClient.class);

    private final CuratorFramework client;
    private final String clusterId;
    private final AtomicBoolean clusterIdExists;
    private final Watcher watcher;
    private final ConnectionStateListener connectionStateListener;

    public DefaultZooKeeperClient(final CuratorFramework client) {
        this(client, null);
    }

    public DefaultZooKeeperClient(final CuratorFramework client, final String clusterId) {
        this.client = client;
        this.clusterId = clusterId;

        if (clusterId == null) {
            this.clusterIdExists = null;
            this.watcher = null;
            this.connectionStateListener = null;
            return;
        }

        this.clusterIdExists = new AtomicBoolean(false);

        this.watcher = new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                log.info("DefaultZooKeeperClient processing WatchedEvent - {}", event);
                checkClusterIdExists(clusterId, "watcher");
            }
        };

        connectionStateListener = new ConnectionStateListener() {
            @Override
            public void stateChanged(CuratorFramework client, ConnectionState newState) {
                log.info("DefaultZooKeeperClient connection state change - {}", newState);
                if (newState == ConnectionState.RECONNECTED) {
                    checkClusterIdExists(clusterId, "connectionStateListener");
                }
            }
        };
    }

    @Override
    public CuratorFramework getCuratorFramework() {
        return client;
    }

    @Override
    /** {@inheritDoc} */
    public void ensurePath(final String path) throws KeeperException {
        ensurePath(path, false);
    }

    @Override
    /** {@inheritDoc} */
    public void ensurePath(final String path, final boolean excludingLast) throws KeeperException {
        PathUtils.validatePath(path);

        assertClusterIdFlagTrue();

        final String[] parts = path.substring(1).split(Pattern.quote("/"));

        final int end = excludingLast ? parts.length - 1 : parts.length;
        String current = "";
        for (int i = 0; i < end; i++) {
            current += "/" + parts[i];
            if (exists(current) == null) {
                create(current);
            }
        }
    }

    @Override
    public byte[] getData(final String path) throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            return client.getData().forPath(path);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public Node getNode(final String path) throws KeeperException {
        assertClusterIdFlagTrue();
        final Stat stat = new Stat();
        try {
            final byte[] bytes = client.getData().storingStatIn(stat).forPath(path);
            return new Node(path, bytes, stat);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public Stat exists(final String path) throws KeeperException {
        return stat(path);
    }

    @Override
    public void start() {
        if (client.getState() != CuratorFrameworkState.STARTED) {
            client.start();

            if (clusterId != null) {
                client.getConnectionStateListenable().addListener(connectionStateListener);
                checkClusterIdExists(clusterId, "start");
            }
        }
    }

    @Override
    public void close() {
        if (clusterId != null) {
            client.getConnectionStateListenable().removeListener(connectionStateListener);
        }
        client.close();
    }

    @Override
    public PersistentEphemeralNode persistentEphemeralNode(final String path,
            final PersistentEphemeralNode.Mode mode, final byte[] data) {
        assertClusterIdFlagTrue();

        final PersistentEphemeralNode node = new PersistentEphemeralNode(client, mode, path, data);

        // ugly hack to work around a problem with curator, wherein PersistentEphemeralNode creates
        // all parent paths if they don't exist but applies the ACL for its own path to its parents
        try {
            final Field field = node.getClass().getDeclaredField("createMethod");
            field.setAccessible(true);
            field.set(node, client.create());
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw propagate(e);
        }

        return node;
    }

    @Override
    public Listenable<ConnectionStateListener> getConnectionStateListenable() {
        return client.getConnectionStateListenable();
    }

    @Override
    public ZooKeeper.States getState() throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            return client.getZookeeperClient().getZooKeeper().getState();
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public List<String> getChildren(final String path) throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            return client.getChildren().forPath(path);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public void deleteRecursive(final String path) throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            final List<String> nodes = listRecursive(path);
            if (nodes.isEmpty()) {
                return;
            }
            final CuratorTransactionFinal t = client.inTransaction().check().forPath(path).and();
            for (final String node : reverse(nodes)) {
                t.delete().forPath(node).and();
            }
            t.commit();
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public List<String> listRecursive(final String path) throws KeeperException {
        assertClusterIdFlagTrue();
        final Deque<String> queue = newLinkedList();
        final List<String> tree = newArrayList();

        queue.add(path);
        tree.add(path);

        while (!queue.isEmpty()) {
            final String node = queue.pollFirst();
            final List<String> children = getChildren(node);

            for (final String child : children) {
                final String childPath = node.replaceAll("/$", "") + "/" + child;
                queue.add(childPath);
                tree.add(childPath);
            }
        }

        return tree;
    }

    @Override
    public void delete(final String path) throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            client.delete().forPath(path);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public void delete(final String path, final int version) throws KeeperException {
        assertClusterIdFlagTrue();

        final String namespace = emptyToNull(client.getNamespace());
        final String namespacedPath = ZKPaths.fixForNamespace(namespace, path);
        try {
            client.getZookeeperClient().getZooKeeper().delete(namespacedPath, version);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public void createAndSetData(final String path, final byte[] data) throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            client.create().forPath(path, data);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public void create(final String path) throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            client.create().forPath(path);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public void createWithMode(final String path, final CreateMode mode) throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            client.create().withMode(mode).forPath(path);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public void setData(final String path, final byte[] data) throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            client.setData().forPath(path, data);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public Stat stat(final String path) throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            return client.checkExists().forPath(path);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public <T> PersistentPathChildrenCache<T> pathChildrenCache(final String path, final Path snapshotFile,
            final JavaType valueType) throws IOException, InterruptedException {
        return new PersistentPathChildrenCache<T>(client, path, clusterId, snapshotFile, valueType);
    }

    @Override
    public Collection<CuratorTransactionResult> transaction(final List<ZooKeeperOperation> operations)
            throws KeeperException {
        assertClusterIdFlagTrue();
        log.debug("transaction: {}", operations);

        if (operations.isEmpty()) {
            return emptyList();
        }

        // Assemble transaction
        final CuratorTransactionFinal transaction = (CuratorTransactionFinal) client.inTransaction();
        for (final ZooKeeperOperation operation : operations) {
            try {
                operation.register(transaction);
            } catch (final Exception e) {
                throw propagate(e);
            }
        }

        // Commit
        try {
            return transaction.commit();
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public Collection<CuratorTransactionResult> transaction(final ZooKeeperOperation... operations)
            throws KeeperException {
        return transaction(asList(operations));
    }

    @Override
    public void setAcl(final String path, final List<ACL> aclList) throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            client.setACL().withACL(aclList).forPath(path);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    @Override
    public List<ACL> getAcl(final String path) throws KeeperException {
        assertClusterIdFlagTrue();
        try {
            return client.getACL().forPath(path);
        } catch (Exception e) {
            propagateIfInstanceOf(e, KeeperException.class);
            throw propagate(e);
        }
    }

    private void assertClusterIdFlagTrue() {
        if (clusterId != null && !clusterIdExists.get()) {
            throw new IllegalStateException("ZooKeeper cluster ID does not exist");
        }
    }

    private void checkClusterIdExists(final String id, final String checker) {
        try {
            final Stat stat = client.checkExists().usingWatcher(watcher).forPath(Paths.configId(id));
            final boolean exists = stat != null;
            clusterIdExists.set(exists);
            log.info("Cluster ID {} {} when checked by {}", id, exists ? "exists" : "does not exist", checker);
        } catch (Exception e) {
            clusterIdExists.set(false);
            log.error("Exception while checking ZooKeeper cluster ID {}", clusterId, e);
        }
    }
}