/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.zookeeper.admin;

import java.io.IOException;
import java.util.List;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.zookeeper.AsyncCallback.DataCallback;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.client.ZKClientConfig;
import org.apache.zookeeper.common.StringUtils;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.proto.GetDataResponse;
import org.apache.zookeeper.proto.ReconfigRequest;
import org.apache.zookeeper.proto.ReplyHeader;
import org.apache.zookeeper.proto.RequestHeader;
import org.apache.zookeeper.server.DataTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This is the main class for ZooKeeperAdmin client library.
 * This library is used to perform cluster administration tasks,
 * such as reconfigure cluster membership. The ZooKeeperAdmin class
 * inherits ZooKeeper and has similar usage pattern as ZooKeeper class.
 * Please check {@link ZooKeeper} class document for more details.
 *
 * @since 3.5.3
 */
// See ZooKeeper.java for an explanation of why we need @SuppressWarnings("try")
@SuppressWarnings("try")
@InterfaceAudience.Public
public class ZooKeeperAdmin extends ZooKeeper {

    private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperAdmin.class);

    /**
     * Create a ZooKeeperAdmin object which is used to perform dynamic reconfiguration
     * operations.
     *
     * @param connectString
     *            comma separated host:port pairs, each corresponding to a zk
     *            server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" If
     *            the optional chroot suffix is used the example would look
     *            like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a"
     *            where the client would be rooted at "/app/a" and all paths
     *            would be relative to this root - ie getting/setting/etc...
     *            "/foo/bar" would result in operations being run on
     *            "/app/a/foo/bar" (from the server perspective).
     * @param sessionTimeout
     *            session timeout in milliseconds
     * @param watcher
     *            a watcher object which will be notified of state changes, may
     *            also be notified for node events
     *
     * @throws IOException
     *             in cases of network failure
     * @throws IllegalArgumentException
     *             if an invalid chroot path is specified
     *
     * @see ZooKeeper#ZooKeeper(String, int, Watcher)
     *
     */
    public ZooKeeperAdmin(
        String connectString,
        int sessionTimeout,
        Watcher watcher) throws IOException {
        super(connectString, sessionTimeout, watcher);
    }

    /**
     * Create a ZooKeeperAdmin object which is used to perform dynamic reconfiguration
     * operations.
     *
     * @param connectString
     *            comma separated host:port pairs, each corresponding to a zk
     *            server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" If
     *            the optional chroot suffix is used the example would look
     *            like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a"
     *            where the client would be rooted at "/app/a" and all paths
     *            would be relative to this root - ie getting/setting/etc...
     *            "/foo/bar" would result in operations being run on
     *            "/app/a/foo/bar" (from the server perspective).
     * @param sessionTimeout
     *            session timeout in milliseconds
     * @param watcher
     *            a watcher object which will be notified of state changes, may
     *            also be notified for node events
     * @param conf
     *            passing this conf object gives each client the flexibility of
     *            configuring properties differently compared to other instances
     *
     * @throws IOException
     *             in cases of network failure
     * @throws IllegalArgumentException
     *             if an invalid chroot path is specified
     *
     * @see ZooKeeper#ZooKeeper(String, int, Watcher, ZKClientConfig)
     */
    public ZooKeeperAdmin(
        String connectString,
        int sessionTimeout,
        Watcher watcher,
        ZKClientConfig conf) throws IOException {
        super(connectString, sessionTimeout, watcher, conf);
    }

    /**
     * Create a ZooKeeperAdmin object which is used to perform dynamic reconfiguration
     * operations.
     *
     * @param connectString
     *            comma separated host:port pairs, each corresponding to a zk
     *            server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" If
     *            the optional chroot suffix is used the example would look
     *            like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a"
     *            where the client would be rooted at "/app/a" and all paths
     *            would be relative to this root - ie getting/setting/etc...
     *            "/foo/bar" would result in operations being run on
     *            "/app/a/foo/bar" (from the server perspective).
     * @param sessionTimeout
     *            session timeout in milliseconds
     * @param watcher
     *            a watcher object which will be notified of state changes, may
     *            also be notified for node events
     * @param canBeReadOnly
     *            whether the created client is allowed to go to
     *            read-only mode in case of partitioning. Read-only mode
     *            basically means that if the client can't find any majority
     *            servers but there's partitioned server it could reach, it
     *            connects to one in read-only mode, i.e. read requests are
     *            allowed while write requests are not. It continues seeking for
     *            majority in the background.
     * @param conf
     *            passing this conf object gives each client the flexibility of
     *            configuring properties differently compared to other instances
     *
     * @throws IOException
     *             in cases of network failure
     * @throws IllegalArgumentException
     *             if an invalid chroot path is specified
     *
     * @see ZooKeeper#ZooKeeper(String, int, Watcher, boolean, ZKClientConfig)
     *
     * @since 3.6.1
     */
    public ZooKeeperAdmin(
        String connectString,
        int sessionTimeout,
        Watcher watcher,
        boolean canBeReadOnly,
        ZKClientConfig conf) throws IOException {
        super(connectString, sessionTimeout, watcher, canBeReadOnly, conf);
    }

    /**
     * Create a ZooKeeperAdmin object which is used to perform dynamic reconfiguration
     * operations.
     *
     * @param connectString
     *            comma separated host:port pairs, each corresponding to a zk
     *            server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" If
     *            the optional chroot suffix is used the example would look
     *            like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a"
     *            where the client would be rooted at "/app/a" and all paths
     *            would be relative to this root - ie getting/setting/etc...
     *            "/foo/bar" would result in operations being run on
     *            "/app/a/foo/bar" (from the server perspective).
     * @param sessionTimeout
     *            session timeout in milliseconds
     * @param watcher
     *            a watcher object which will be notified of state changes, may
     *            also be notified for node events
     * @param canBeReadOnly
     *            whether the created client is allowed to go to
     *            read-only mode in case of partitioning. Read-only mode
     *            basically means that if the client can't find any majority
     *            servers but there's partitioned server it could reach, it
     *            connects to one in read-only mode, i.e. read requests are
     *            allowed while write requests are not. It continues seeking for
     *            majority in the background.
     *
     * @throws IOException
     *             in cases of network failure
     * @throws IllegalArgumentException
     *             if an invalid chroot path is specified
     *
     * @see ZooKeeper#ZooKeeper(String, int, Watcher, boolean)
     */
    public ZooKeeperAdmin(
        String connectString,
        int sessionTimeout,
        Watcher watcher,
        boolean canBeReadOnly) throws IOException {
        super(connectString, sessionTimeout, watcher, canBeReadOnly);
    }

    /**
     * Reconfigure - add/remove servers. Return the new configuration.
     * @param joiningServers
     *                a comma separated list of servers being added (incremental reconfiguration)
     * @param leavingServers
     *                a comma separated list of servers being removed (incremental reconfiguration)
     * @param newMembers
     *                a comma separated list of new membership (non-incremental reconfiguration)
     * @param fromConfig
     *                version of the current configuration
     *                (optional - causes reconfiguration to throw an exception if configuration is no longer current)
     * @param stat the stat of /zookeeper/config znode will be copied to this
     *             parameter if not null.
     * @return new configuration
     * @throws InterruptedException If the server transaction is interrupted.
     * @throws KeeperException If the server signals an error with a non-zero error code.
     */
    public byte[] reconfigure(
        String joiningServers,
        String leavingServers,
        String newMembers,
        long fromConfig,
        Stat stat) throws KeeperException, InterruptedException {
        RequestHeader h = new RequestHeader();
        h.setType(ZooDefs.OpCode.reconfig);
        ReconfigRequest request = new ReconfigRequest(joiningServers, leavingServers, newMembers, fromConfig);
        GetDataResponse response = new GetDataResponse();
        ReplyHeader r = cnxn.submitRequest(h, request, response, null);
        if (r.getErr() != 0) {
            throw KeeperException.create(KeeperException.Code.get(r.getErr()), "");
        }
        if (stat != null) {
            DataTree.copyStat(response.getStat(), stat);
        }
        return response.getData();
    }

    /**
     * Convenience wrapper around reconfig that takes Lists of strings instead of comma-separated servers.
     *
     * @see #reconfigure
     *
     */
    public byte[] reconfigure(
        List<String> joiningServers,
        List<String> leavingServers,
        List<String> newMembers,
        long fromConfig,
        Stat stat) throws KeeperException, InterruptedException {
        return reconfigure(
            StringUtils.joinStrings(joiningServers, ","),
            StringUtils.joinStrings(leavingServers, ","),
            StringUtils.joinStrings(newMembers, ","),
            fromConfig,
            stat);
    }

    /**
     * The Asynchronous version of reconfig.
     *
     * @see #reconfigure
     *
     **/
    public void reconfigure(
        String joiningServers,
        String leavingServers,
        String newMembers,
        long fromConfig,
        DataCallback cb,
        Object ctx) {
        RequestHeader h = new RequestHeader();
        h.setType(ZooDefs.OpCode.reconfig);
        ReconfigRequest request = new ReconfigRequest(joiningServers, leavingServers, newMembers, fromConfig);
        GetDataResponse response = new GetDataResponse();
        cnxn.queuePacket(
            h,
            new ReplyHeader(),
            request,
            response,
            cb,
            ZooDefs.CONFIG_NODE,
            ZooDefs.CONFIG_NODE,
            ctx,
            null);
    }

    /**
     * Convenience wrapper around asynchronous reconfig that takes Lists of strings instead of comma-separated servers.
     *
     * @see #reconfigure
     *
     */
    public void reconfigure(
        List<String> joiningServers,
        List<String> leavingServers,
        List<String> newMembers,
        long fromConfig,
        DataCallback cb,
        Object ctx) {
        reconfigure(
            StringUtils.joinStrings(joiningServers, ","),
            StringUtils.joinStrings(leavingServers, ","),
            StringUtils.joinStrings(newMembers, ","),
            fromConfig,
            cb,
            ctx);
    }

    /**
     * String representation of this ZooKeeperAdmin client. Suitable for things
     * like logging.
     *
     * Do NOT count on the format of this string, it may change without
     * warning.
     *
     * @since 3.5.3
     */
    @Override
    public String toString() {
        return super.toString();
    }

}
