co.cask.cdap.common.zookeeper.store.ZKPropertyStore.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.common.zookeeper.store.ZKPropertyStore.java

Source

/*
 * Copyright  2014 Cask Data, Inc.
 *
 * 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 co.cask.cdap.common.zookeeper.store;

import co.cask.cdap.api.common.Bytes;
import co.cask.cdap.common.conf.AbstractPropertyStore;
import co.cask.cdap.common.conf.PropertyUpdater;
import co.cask.cdap.common.io.Codec;
import co.cask.cdap.common.zookeeper.ZKExtOperations;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.apache.twill.common.Threads;
import org.apache.twill.zookeeper.NodeData;
import org.apache.twill.zookeeper.ZKClient;
import org.apache.twill.zookeeper.ZKClients;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Set;

/**
 * This class uses ZK for storing properties/configures. It provides update methods for updating properties,
 * and listener methods for watching for changes in properties.
 *
 * TODO: Unify this and SharedResourceCache in security module.
 *
 * @param <T> Type of property object
 */
public final class ZKPropertyStore<T> extends AbstractPropertyStore<T> {

    private static final Logger LOG = LoggerFactory.getLogger(ZKPropertyStore.class);
    private static final int MAX_ZK_FAILURE_RETRIES = 10;

    private final ZKClient zkClient;
    private final Codec<T> codec;
    private final Set<String> watchedSet;

    /**
     * Creates an instance of {@link ZKPropertyStore}.
     *
     * @param zkClient client for interacting with ZooKeeper. Nodes will be created at root represented by this ZKClient.
     * @param codec The codec for encode/decode property
     */
    public static <T> ZKPropertyStore<T> create(ZKClient zkClient, Codec<T> codec) {
        return new ZKPropertyStore<>(zkClient, codec);
    }

    /**
     * Creates an instance of {@link ZKPropertyStore} with nodes created under the given namespace.
     *
     * @param zkClient client for interacting with ZooKeeper
     * @param namespace Namespace for zk nodes to reside in
     * @param codec The codec for encode/decode property
     */
    public static <T> ZKPropertyStore<T> create(ZKClient zkClient, String namespace, Codec<T> codec) {
        return new ZKPropertyStore<>(ZKClients.namespace(zkClient, namespace), codec);
    }

    /**
     * Constructor.
     */
    private ZKPropertyStore(ZKClient zkClient, Codec<T> codec) {
        this.zkClient = zkClient;
        this.codec = codec;
        this.watchedSet = Sets.newHashSet();
    }

    @Override
    public ListenableFuture<T> update(String name, PropertyUpdater<T> updater) {
        return ZKExtOperations.updateOrCreate(zkClient, getPath(name), updater, codec);
    }

    @Override
    public ListenableFuture<T> set(String name, T property) {
        try {
            return ZKExtOperations.setOrCreate(zkClient, getPath(name), codec.encode(property), property,
                    MAX_ZK_FAILURE_RETRIES);
        } catch (IOException e) {
            return Futures.immediateFailedFuture(e);
        }
    }

    @Override
    protected synchronized boolean listenerAdded(String name) {
        if (watchedSet.add(name)) {
            // Start watching for node change and maintain cached value.
            // Invocation of listener would be triggered inside ZK callback.
            existsAndWatch(name);
            return false;
        }

        // Invoke it with the cached property if available when first added.
        // If no cache value exists, meaning either property was removed or still pending for update for the first time
        // For first case, no need to invoke listener. For second case, when the cache get updated, the newly
        // added listener would get triggered
        return true;
    }

    private String getPath(String name) {
        return "/" + name;
    }

    private void getDataAndWatch(final String name) {
        Futures.addCallback(zkClient.getData(getPath(name), new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                if (isClosed()) {
                    return;
                }

                if (event.getType() == Event.EventType.NodeDeleted) {
                    existsAndWatch(name);
                } else {
                    getDataAndWatch(name);
                }
            }
        }), new FutureCallback<NodeData>() {
            @Override
            public void onSuccess(NodeData result) {
                byte[] data = result.getData();
                if (data == null) {
                    updateAndNotify(name, null);
                } else {
                    try {
                        updateAndNotify(name, codec.decode(data));
                    } catch (IOException e) {
                        LOG.error("Failed to decode property data for {}: {}", name, Bytes.toStringBinary(data), e);
                        notifyError(name, e);
                    }
                }
            }

            @Override
            public void onFailure(Throwable t) {
                if (t instanceof KeeperException.NoNodeException) {
                    // If node not exists, watch for exists.
                    existsAndWatch(name);
                } else {
                    LOG.error("Failed to get property data for {}", name, t);
                    notifyError(name, t);
                }
            }
        }, Threads.SAME_THREAD_EXECUTOR);
    }

    private void existsAndWatch(final String name) {
        Futures.addCallback(zkClient.exists(getPath(name), new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                if (isClosed()) {
                    return;
                }

                // If the event is not node created, meaning the node was existed.
                // Hence getDataAndWatch should be handling that case already
                if (event.getType() == Event.EventType.NodeCreated) {
                    getDataAndWatch(name);
                }
            }
        }), new FutureCallback<Stat>() {
            @Override
            public void onSuccess(Stat result) {
                // If the node exists, call getData. Otherwise, the watcher should handle the case when the node is created
                if (result != null) {
                    getDataAndWatch(name);
                }
            }

            @Override
            public void onFailure(Throwable t) {
                LOG.error("Failed to check exists for property data for {}", name, t);
                notifyError(name, t);
            }
        }, Threads.SAME_THREAD_EXECUTOR);
    }
}