/**
 * Copyright (c) 2011 Jonathan Leibiusky
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.navercorp.redis.cluster.connection;

import java.util.List;

import redis.clients.util.SafeEncoder;

import com.navercorp.nbasearc.gcp.VirtualConnection;
import com.navercorp.redis.cluster.connection.RedisProtocol.Command;
import com.navercorp.redis.cluster.gateway.AffinityState;

/**
 * The Class RedisConnection.
 *
 * @author jaehong.kim
 */
public class RedisConnection {

    private RedisConnectionImpl impl;

    /**
     * Gets the timeout.
     *
     * @return the timeout
     */
    public int getTimeout() {
        return impl.getTimeout();
    }

    /**
     * Sets the timeout.
     *
     * @param timeoutMillisec the new timeout
     */
    public void setTimeout(final int timeoutMillisec) {
        impl.setTimeout(timeoutMillisec);
    }

    public void commitActiveTimeout(final int timeoutMillisec) {
        impl.commitActiveTimeout(timeoutMillisec);
    }

    public void rollbackActiveTimeout() {
        impl.rollbackActiveTimeout();
    }

    public void setVc(VirtualConnection vc) {
        impl.setVc(vc);
    }

    public void allocPc(int hash, AffinityState affinity, boolean pipelineMode) {
        impl.allocPc(hash, affinity, pipelineMode);
    }

    /**
     * Instantiates a new redis connection.
     *
     * @param host the host
     */
    public RedisConnection(final String host) {
        impl = new RedisConnectionSync(host);
    }

    public static RedisConnection createAsync(String host, int port) {
        RedisConnection con = new RedisConnection();
        con.impl = new RedisConnectionAsync(host, port);
        return con;
    }

    /**
     * Send command.
     *
     * @param cmd  the cmd
     * @param args the args
     * @return the redis connection
     */
    protected RedisConnection sendCommand(final Command cmd, final String... args) {
        final byte[][] bargs = new byte[args.length][];
        for (int i = 0; i < args.length; i++) {
            bargs[i] = SafeEncoder.encode(args[i]);
        }
        return sendCommand(cmd, bargs);
    }

    /**
     * Send command.
     *
     * @param cmd  the cmd
     * @param args the args
     * @return the redis connection
     */
    protected RedisConnection sendCommand(final Command cmd, final byte[]... args) {
        impl.sendCommand(cmd, args);
        return this;
    }

    /**
     * Send command.
     *
     * @param cmd the cmd
     * @return the redis connection
     */
    protected RedisConnection sendCommand(final Command cmd) {
        impl.sendCommand(cmd);
        return this;
    }

    /**
     * Instantiates a new redis connection.
     *
     * @param host the host
     * @param port the port
     */
    public RedisConnection(final String host, final int port) {
        impl = new RedisConnectionSync(host, port);
    }

    /**
     * Gets the host.
     *
     * @return the host
     */
    public String getHost() {
        return impl.getHost();
    }

    /**
     * Sets the host.
     *
     * @param host the new host
     */
    public void setHost(final String host) {
        impl.setHost(host);
    }

    /**
     * Gets the port.
     *
     * @return the port
     */
    public int getPort() {
        return impl.getPort();
    }

    /**
     * Sets the port.
     *
     * @param port the new port
     */
    public void setPort(final int port) {
        impl.setPort(port);
    }

    /**
     * Instantiates a new redis connection.
     */
    public RedisConnection() {
        impl = new RedisConnectionSync();
    }

    public RedisConnection(String host, int port, boolean async) {
        impl = new RedisConnectionAsync(host, port);
    }

    /**
     * Connect.
     */
    public void connect() {
        impl.connect();
    }

    /**
     * Disconnect.
     */
    public void disconnect() {
        impl.disconnect();
    }

    public void passivate() {
        impl.passivate();
    }

    /**
     * Checks if is connected.
     *
     * @return true, if is connected
     */
    public boolean isConnected() {
        return impl.isConnected();
    }

    /**
     * Gets the status code reply.
     *
     * @return the status code reply
     */
    public String getStatusCodeReply() {
        return impl.getStatusCodeReply();
    }

    /**
     * Gets the bulk reply.
     *
     * @return the bulk reply
     */
    public String getBulkReply() {
        return impl.getBulkReply();
    }

    /**
     * Gets the binary bulk reply.
     *
     * @return the binary bulk reply
     */
    public byte[] getBinaryBulkReply() {
        return impl.getBinaryBulkReply();
    }

    /**
     * Gets the integer reply.
     *
     * @return the integer reply
     */
    public Long getIntegerReply() {
        return impl.getIntegerReply();
    }

    /**
     * Gets the multi bulk reply.
     *
     * @return the multi bulk reply
     */
    public List<String> getMultiBulkReply() {
        return impl.getMultiBulkReply();
    }

    /**
     * Gets the binary multi bulk reply.
     *
     * @return the binary multi bulk reply
     */
    public List<byte[]> getBinaryMultiBulkReply() {
        return impl.getBinaryMultiBulkReply();
    }

    /**
     * Gets the object multi bulk reply.
     *
     * @return the object multi bulk reply
     */
    public List<Object> getObjectMultiBulkReply() {
        return impl.getObjectMultiBulkReply();
    }

    /**
     * Gets the integer multi bulk reply.
     *
     * @return the integer multi bulk reply
     */
    public List<Long> getIntegerMultiBulkReply() {
        return impl.getIntegerMultiBulkReply();
    }

    public List<Object> getAll() {
        return impl.getAll();
    }

    public List<Object> getAll(int except) {
        return impl.getAll(except);
    }

    /*
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return impl.toString();
    }
}


/*
 * Copyright 2015 Naver Corp.
 *
 * 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.navercorp.nbasearc.confmaster.server.workflow;

import static com.navercorp.nbasearc.confmaster.Constant.*;
import static com.navercorp.nbasearc.confmaster.Constant.Color.*;
import static com.navercorp.nbasearc.confmaster.server.workflow.WorkflowExecutor.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.springframework.context.ApplicationContext;

import com.navercorp.nbasearc.confmaster.ConfMasterException.MgmtSmrCommandException;
import com.navercorp.nbasearc.confmaster.ConfMasterException.MgmtSetquorumException;
import com.navercorp.nbasearc.confmaster.ConfMasterException.MgmtZooKeeperException;
import com.navercorp.nbasearc.confmaster.config.Config;
import com.navercorp.nbasearc.confmaster.logger.Logger;
import com.navercorp.nbasearc.confmaster.server.ZooKeeperHolder;
import com.navercorp.nbasearc.confmaster.server.cluster.ClusterComponentContainer;
import com.navercorp.nbasearc.confmaster.server.cluster.PartitionGroup;
import com.navercorp.nbasearc.confmaster.server.cluster.PartitionGroupServer;
import com.navercorp.nbasearc.confmaster.server.cluster.RedisServer;

public class MembershipGrantWorkflow extends CascadingWorkflow {
    final ApplicationContext context;
    final Config config;
    final WorkflowExecutor wfExecutor;
    final ZooKeeperHolder zk;

    final MGSetquorum setquorum;

    List<PartitionGroupServer> joinedPgsList;

    public MembershipGrantWorkflow(PartitionGroup pg, boolean cascading,
            ApplicationContext context) {
        super(cascading, pg, context.getBean(ClusterComponentContainer.class));

        this.context = context;
        this.config = context.getBean(Config.class);
        this.wfExecutor = context.getBean(WorkflowExecutor.class);
        this.zk = context.getBean(ZooKeeperHolder.class);

        this.setquorum = context.getBean(MGSetquorum.class);
    }

    @Override
    protected void _execute() throws MgmtSmrCommandException,
            MgmtZooKeeperException, MgmtSetquorumException {
        joinedPgsList = pg
                .getJoinedPgsList(container.getPgsList(pg.getClusterName(), pg.getName()));

        final Map<String, RedisServer> rsMap = new HashMap<String, RedisServer>();
        for (RedisServer rs : container.getRsList(pg.getClusterName())) {
            rsMap.put(rs.getName(), rs);
        }
        final PartitionGroupServer master = pg.getMaster(joinedPgsList);

        for (PartitionGroupServer pgs : joinedPgsList) {
            final String role = pgs.getRole();
            final Color color = pgs.getColor();

            if (role != PGS_ROLE_SLAVE || color != YELLOW) {
                continue;
            }

            RedisServer rs = rsMap.get(pgs.getName());
            try {
                String response = rs.executeQuery("ping");
                if (!response.equals(REDIS_PONG)) {
                    continue;
                }
            } catch (IOException e) {
                continue;
            }

            final int q = pg.getQuorum();
            final int d = pg.getD(joinedPgsList);

            final int curQ = q - d;
            final int newQ = curQ + 1;
            final String quorumMembers = pg.getQuorumMembersString(master,
                    joinedPgsList) + " " + pgs.getName();
            setquorum.setquorum(master, newQ, quorumMembers);

            try {
                String response = rs.executeQuery("ping");
                if (!response.equals(REDIS_PONG)) {
                    continue;
                }
            } catch (IOException e) {
                setquorum.setquorum(master, curQ,
                        pg.getQuorumMembersString(master, joinedPgsList));
                continue;
            }

            Logger.info("{} {}->{} {}->{} {}->{}", new Object[] { pgs,
                    pgs.getRole(), pgs.getRole(),
                    pgs.getColor(), GREEN, curQ, newQ });
            PartitionGroupServer.PartitionGroupServerData pgsM = pgs.clonePersistentData();
            pgsM.color = GREEN;
            zk.setData(pgs.getPath(), pgsM.toBytes(), -1);
            pgs.setPersistentData(pgsM);
        }
    }

    @Override
    protected void onSuccess() throws Exception {
        int numGreen = 0;
        for (PartitionGroupServer pgs : joinedPgsList) {
            if (pgs.getColor() == GREEN) {
                numGreen++;
            }
        }

        if (numGreen != joinedPgsList.size()) {
            Logger.info(
                    "retry to reconfig {}, copy: {}, joined: {}, green: {}",
                    new Object[] { pg, joinedPgsList.size(),
                            pg.getCopy(), numGreen });

            final long nextEpoch = pg.nextWfEpoch();
            Logger.info("next {}", nextEpoch);
            wfExecutor.performDelayed(ROLE_ADJUSTMENT,
                    config.getServerJobWorkflowPgReconfigDelay(),
                    TimeUnit.MILLISECONDS, pg, nextEpoch, context);
        } else {
            cleanMGen();
        }
    }

    @Override
    protected void onException(long nextEpoch, Exception e) {
        wfExecutor.performDelayed(ROLE_ADJUSTMENT,
                config.getServerJobWorkflowPgReconfigDelay(),
                TimeUnit.MILLISECONDS, pg, nextEpoch, context);
    }

    /*
     * When all PGSes in a PG are green, it is able to
     * clean mgen-history of PG to prevent history from growing
     * too long to cause slow operations.
     */
    private void cleanMGen() throws MgmtZooKeeperException {
        try {
        	PartitionGroup.PartitionGroupData pgM = pg.clonePersistentData();
            pgM.cleanMGen(config.getClusterPgMgenHistorySize());
            zk.setData(pg.getPath(), pgM.toBytes(), -1);
            pg.setPersistentData(pgM);
        } catch (Exception e) {
            throw new MgmtZooKeeperException(
                    "Clean mgen history of " + pg + " fail, "
                            + e.getMessage());
        }
    }
}


/**
 * Copyright (c) 2011 Jonathan Leibiusky
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.navercorp.redis.cluster;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.navercorp.redis.cluster.pipeline.BuilderFactory;

import redis.clients.jedis.BinaryClient.LIST_POSITION;
import redis.clients.jedis.GeoCoordinate;
import redis.clients.jedis.GeoRadiusResponse;
import redis.clients.jedis.GeoUnit;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
import redis.clients.jedis.Tuple;
import redis.clients.jedis.params.geo.GeoRadiusParam;
import redis.clients.jedis.params.sortedset.ZAddParams;
import redis.clients.util.SafeEncoder;

/**
 * The Class RedisCluster.
 *
 * @author jaehong.kim
 */
public class RedisCluster extends BinaryRedisCluster implements
        RedisClusterCommands {

    /**
     * Instantiates a new redis cluster.
     *
     * @param host the host
     */
    public RedisCluster(final String host) {
        super(host);
    }

    /**
     * Instantiates a new redis cluster.
     *
     * @param host the host
     * @param port the port
     */
    public RedisCluster(final String host, final int port) {
        super(host, port);
    }

    /**
     * Instantiates a new redis cluster.
     *
     * @param host    the host
     * @param port    the port
     * @param timeout the timeout
     */
    public RedisCluster(final String host, final int port, final int timeout) {
        super(host, port, timeout);
    }

    public RedisCluster(final String host, final int port, final int timeout, final boolean async) {
        super(host, port, timeout, async);
    }

    // ////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Keys

    public Long del(final String... keys) {
        client.del(keys);
        return client.getIntegerReply();
    }

    public Boolean exists(final String key) {
        client.exists(key);
        return client.getIntegerReply() == 1;
    }

    public Long exists(final String... keys) {
        client.exists(keys);
        return client.getIntegerReply();
    }

    public Long expire(final String key, final int seconds) {
        client.expire(key, seconds);
        return client.getIntegerReply();
    }

    public Long expireAt(final String key, final long secondsTimestamp) {
        client.expireAt(key, secondsTimestamp);
        return client.getIntegerReply();
    }

    public Long pexpire(final String key, final long milliseconds) {
        client.pexpire(key, milliseconds);
        return client.getIntegerReply();
    }

    public Long pexpireAt(final String key, final long millisecondsTimestamp) {
        client.pexpireAt(key, millisecondsTimestamp);
        return client.getIntegerReply();
    }

    public Long objectRefcount(String string) {
        client.objectRefcount(string);
        return client.getIntegerReply();
    }

    public String objectEncoding(String string) {
        client.objectEncoding(string);
        return client.getBulkReply();
    }

    public Long objectIdletime(String string) {
        client.objectIdletime(string);
        return client.getIntegerReply();
    }

    public Long ttl(final String key) {
        client.ttl(key);
        return client.getIntegerReply();
    }

    public Long pttl(final String key) {
        client.pttl(key);
        return client.getIntegerReply();
    }

    public String type(final String key) {
        client.type(key);
        return client.getStatusCodeReply();
    }

    public Long persist(final String key) {
        client.persist(key);
        return client.getIntegerReply();
    }

    // ////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Strings

    public Long append(final String key, final String value) {
        client.append(key, value);
        return client.getIntegerReply();
    }

    public Long decr(final String key) {
        client.decr(key);
        return client.getIntegerReply();
    }

    public Long decrBy(final String key, final long integer) {
        client.decrBy(key, integer);
        return client.getIntegerReply();
    }

    public String get(final String key) {
        client.get(key);
        return client.getBulkReply();
    }

    public Boolean getbit(String key, long offset) {
        client.getbit(key, offset);
        return client.getIntegerReply() == 1;
    }

    public String getrange(String key, long startOffset, long endOffset) {
        client.getrange(key, startOffset, endOffset);
        return client.getBulkReply();
    }

    public String substr(final String key, final int start, final int end) {
        client.substr(key, start, end);
        return client.getBulkReply();
    }

    public String getSet(final String key, final String value) {
        client.getSet(key, value);
        return client.getBulkReply();
    }

    public Long incr(final String key) {
        client.incr(key);
        return client.getIntegerReply();
    }

    public Long incrBy(final String key, final long integer) {
        client.incrBy(key, integer);
        return client.getIntegerReply();
    }

    public Double incrByFloat(final String key, final double increment) {
        client.incrByFloat(key, increment);
        String reply = client.getBulkReply();
        return (reply != null ? new Double(reply) : null);
    }

    public String set(final String key, String value) {
        client.set(key, value);
        return client.getStatusCodeReply();
    }

    public String set(final String key, final String value, final String nxxx, final String expx, final long time) {
        client.set(key, value, nxxx, expx, time);
        return client.getStatusCodeReply();
    }

    public Boolean setbit(String key, long offset, boolean value) {
        client.setbit(key, offset, value);
        return client.getIntegerReply() == 1;
    }

    public String setex(final String key, final int seconds, final String value) {
        client.setex(key, seconds, value);
        return client.getStatusCodeReply();
    }

    public Long setnx(final String key, final String value) {
        client.setnx(key, value);
        return client.getIntegerReply();
    }

    public Long setrange(String key, long offset, String value) {
        client.setrange(key, offset, value);
        return client.getIntegerReply();
    }

    public Long strlen(final String key) {
        client.strlen(key);
        return client.getIntegerReply();
    }

    public List<String> mget(final String... keys) {
        client.mget(keys);
        return client.getMultiBulkReply();
    }

    public String psetex(final String key, final long milliseconds,
                         final String value) {
        client.psetex(key, milliseconds, value);
        return client.getStatusCodeReply();
    }

    public String mset(String... keysvalues) {
        client.mset(keysvalues);
        return client.getStatusCodeReply();
    }

    public Long bitcount(final String key) {
        client.bitcount(key);
        return client.getIntegerReply();
    }

    public Long bitcount(final String key, long start, long end) {
        client.bitcount(key, start, end);
        return client.getIntegerReply();
    }

    public Long bitpos(String key, boolean bit) {
        client.bitpos(key, bit);
        return client.getIntegerReply();
    }

    public Long bitpos(String key, boolean bit, int start) {
        client.bitpos(key, bit, start);
        return client.getIntegerReply();
    }

    public Long bitpos(String key, boolean bit, int start, int end) {
        client.bitpos(key, bit, start, end);
        return client.getIntegerReply();
    }

    public List<Long> bitfield(final String key, final String... arguments) {
        client.bitfield(key, arguments);
        return client.getIntegerMultiBulkReply();
    }

    // ////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Hashes

    public Long hdel(final String key, final String... fields) {
        client.hdel(key, fields);
        return client.getIntegerReply();
    }

    public Boolean hexists(final String key, final String field) {
        client.hexists(key, field);
        return client.getIntegerReply() == 1;
    }

    public String hget(final String key, final String field) {
        client.hget(key, field);
        return client.getBulkReply();
    }

    public Map<String, String> hgetAll(final String key) {
        client.hgetAll(key);
        return BuilderFactory.STRING_MAP
                .build(client.getBinaryMultiBulkReply());
    }

    public Long hincrBy(final String key, final String field, final long value) {
        client.hincrBy(key, field, value);
        return client.getIntegerReply();
    }

    public Double hincrByFloat(final String key, final String field,
                               double increment) {
        client.hincrByFloat(key, field, increment);
        String reply = client.getBulkReply();
        return (reply != null ? new Double(reply) : null);
    }

    public Set<String> hkeys(final String key) {
        client.hkeys(key);
        return BuilderFactory.STRING_SET
                .build(client.getBinaryMultiBulkReply());
    }

    public Long hlen(final String key) {
        client.hlen(key);
        return client.getIntegerReply();
    }

    public List<String> hmget(final String key, final String... fields) {
        client.hmget(key, fields);
        return client.getMultiBulkReply();
    }

    public String hmset(final String key, final Map<String, String> hash) {
        client.hmset(key, hash);
        return client.getStatusCodeReply();
    }

    public ScanResult<String> scan(final String cursor) {
        return scan(cursor, new ScanParams());
    }

    public ScanResult<String> scan(final String cursor, final ScanParams params) {
        client.scan(cursor, params);
        List<Object> result = client.getObjectMultiBulkReply();
        String newcursor = new String((byte[]) result.get(0));
        List<String> results = new ArrayList<String>();
        @SuppressWarnings("unchecked")
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        for (byte[] bs : rawResults) {
            results.add(SafeEncoder.encode(bs));
        }
        return new ScanResult<String>(newcursor, results);
    }

    public ScanResult<String> cscan(int partitionID, String cursor) {
        return cscan(partitionID, cursor, new ScanParams());
    }

    public ScanResult<String> cscan(int partitionID, String cursor, ScanParams params) {
        client.cscan(partitionID, cursor, params);
        List<Object> result = client.getObjectMultiBulkReply();
        String newcursor = new String((byte[]) result.get(0));
        List<String> results = new ArrayList<String>();
        @SuppressWarnings("unchecked")
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        for (byte[] bs : rawResults) {
            results.add(SafeEncoder.encode(bs));
        }
        return new ScanResult<String>(newcursor, results);
    }

    public ScanResult<String> sscan(final String key, final String cursor) {
        return sscan(key, cursor, new ScanParams());
    }

    public ScanResult<String> sscan(final String key, final String cursor, final ScanParams params) {
        client.sscan(key, cursor, params);
        List<Object> result = client.getObjectMultiBulkReply();
        String newcursor = new String((byte[]) result.get(0));
        List<String> results = new ArrayList<String>();
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        for (byte[] bs : rawResults) {
            results.add(SafeEncoder.encode(bs));
        }
        return new ScanResult<String>(newcursor, results);
    }

    public ScanResult<Map.Entry<String, String>> hscan(final String key, final String cursor) {
        client.hscan(key, cursor, new ScanParams());
        List<Object> result = client.getObjectMultiBulkReply();
        String newcursor = new String((byte[]) result.get(0));
        List<Map.Entry<String, String>> results = new ArrayList<Map.Entry<String, String>>();
        @SuppressWarnings("unchecked")
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        Iterator<byte[]> iterator = rawResults.iterator();
        while (iterator.hasNext()) {
            results.add(new AbstractMap.SimpleEntry<String, String>(new String((byte[]) iterator.next()),
                    new String((byte[]) iterator.next())));
        }
        return new ScanResult<Map.Entry<String, String>>(newcursor, results);
    }

    public ScanResult<Map.Entry<String, String>> hscan(final String key, final String cursor, final ScanParams params) {
        client.hscan(key, cursor, params);
        List<Object> result = client.getObjectMultiBulkReply();
        String newcursor = new String((byte[]) result.get(0));
        List<Map.Entry<String, String>> results = new ArrayList<Map.Entry<String, String>>();
        @SuppressWarnings("unchecked")
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        Iterator<byte[]> iterator = rawResults.iterator();
        while (iterator.hasNext()) {
            results.add(new AbstractMap.SimpleEntry<String, String>(new String((byte[]) iterator.next()),
                    new String((byte[]) iterator.next())));
        }
        return new ScanResult<Map.Entry<String, String>>(newcursor, results);
    }

    public ScanResult<Tuple> zscan(final String key, final String cursor) {
        return zscan(key, cursor, new ScanParams());
    }

    public ScanResult<Tuple> zscan(final String key, final String cursor, final ScanParams params) {
        client.zscan(key, cursor, params);
        List<Object> result = client.getObjectMultiBulkReply();
        String newcursor = new String((byte[]) result.get(0));
        List<Tuple> results = new ArrayList<Tuple>();
        @SuppressWarnings("unchecked")
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        Iterator<byte[]> iterator = rawResults.iterator();
        while (iterator.hasNext()) {
            results.add(new Tuple(SafeEncoder.encode(iterator.next()),
                    Double.valueOf(SafeEncoder.encode(iterator.next()))));
        }
        return new ScanResult<Tuple>(newcursor, results);
    }

    public Long hstrlen(String key, String field) {
        client.hstrlen(key, field);
        return client.getIntegerReply();
    }

    public Long hset(final String key, final String field, final String value) {
        client.hset(key, field, value);
        return client.getIntegerReply();
    }

    public Long hsetnx(final String key, final String field, final String value) {
        client.hsetnx(key, field, value);
        return client.getIntegerReply();
    }

    public List<String> hvals(final String key) {
        client.hvals(key);
        final List<String> lresult = client.getMultiBulkReply();
        return lresult;
    }

    // ////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Lists

    public String lindex(final String key, final long index) {
        client.lindex(key, index);
        return client.getBulkReply();
    }

    public Long linsert(final String key, final LIST_POSITION where,
                        final String pivot, final String value) {
        client.linsert(key, where, pivot, value);
        return client.getIntegerReply();
    }

    public Long llen(final String key) {
        client.llen(key);
        return client.getIntegerReply();
    }

    public String lpop(final String key) {
        client.lpop(key);
        return client.getBulkReply();
    }

    public Long lpush(final String key, final String... strings) {
        client.lpush(key, strings);
        return client.getIntegerReply();
    }

    public Long lpushx(final String key, final String string) {
        client.lpushx(key, string);
        return client.getIntegerReply();
    }

    public List<String> lrange(final String key, final long start,
                               final long end) {
        client.lrange(key, start, end);
        return client.getMultiBulkReply();
    }

    public Long lrem(final String key, final long count, final String value) {
        client.lrem(key, count, value);
        return client.getIntegerReply();
    }

    public String lset(final String key, final long index, final String value) {
        client.lset(key, index, value);
        return client.getStatusCodeReply();
    }

    public String ltrim(final String key, final long start, final long end) {
        client.ltrim(key, start, end);
        return client.getStatusCodeReply();
    }

    public String rpop(final String key) {
        client.rpop(key);
        return client.getBulkReply();
    }

    public Long rpush(final String key, final String... strings) {
        client.rpush(key, strings);
        return client.getIntegerReply();
    }

    public Long rpushx(final String key, final String string) {
        client.rpushx(key, string);
        return client.getIntegerReply();
    }

    // ////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Sets

    public Long sadd(final String key, final String... members) {
        client.sadd(key, members);
        return client.getIntegerReply();
    }

    public Long scard(final String key) {
        client.scard(key);
        return client.getIntegerReply();
    }

    public Boolean sismember(final String key, final String member) {
        client.sismember(key, member);
        return client.getIntegerReply() == 1;
    }

    public Set<String> smembers(final String key) {
        client.smembers(key);
        final List<String> members = client.getMultiBulkReply();
        return new HashSet<String>(members);
    }

    public String srandmember(final String key) {
        client.srandmember(key);
        return client.getBulkReply();
    }

    public List<String> srandmember(final String key, final int count) {
        client.srandmember(key, count);
        return client.getMultiBulkReply();
    }

    public Long srem(final String key, final String... members) {
        client.srem(key, members);
        return client.getIntegerReply();
    }

    // ////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Sorted Sets

    public Long zadd(final String key, final double score, final String member) {
        client.zadd(key, score, member);
        return client.getIntegerReply();
    }

    public Long zadd(final String key, final Map<Double, String> scoreMembers) {
        client.zadd(key, scoreMembers);
        return client.getIntegerReply();
    }

    public Long zadd2(final String key, final Map<String, Double> scoreMembers) {
        client.zadd2(key, scoreMembers);
        return client.getIntegerReply();
    }

    public Long zadd(String key, double score, String member, ZAddParams params) {
        client.zadd(key, score, member, params);
        return client.getIntegerReply();
    }

    public Long zadd(String key, Map<String, Double> scoreMembers, ZAddParams params) {
        client.zadd(key, scoreMembers, params);
        return client.getIntegerReply();
    }

    public Long zcard(final String key) {
        client.zcard(key);
        return client.getIntegerReply();
    }

    public Long zcount(final String key, final double min, final double max) {
        client.zcount(key, min, max);
        return client.getIntegerReply();
    }

    public Long zcount(final String key, final String min, final String max) {
        client.zcount(key, min, max);
        return client.getIntegerReply();
    }

    public Double zincrby(final String key, final double score,
                          final String member) {
        client.zincrby(key, score, member);
        String newscore = client.getBulkReply();
        return Double.valueOf(newscore);
    }

    public Set<String> zrange(final String key, final long start, final long end) {
        client.zrange(key, start, end);
        final List<String> members = client.getMultiBulkReply();
        return new LinkedHashSet<String>(members);
    }

    public Set<String> zrangeByScore(final String key, final double min,
                                     final double max) {
        client.zrangeByScore(key, min, max);
        return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Set<String> zrangeByScore(final String key, final String min,
                                     final String max) {
        client.zrangeByScore(key, min, max);
        return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Set<String> zrangeByScore(final String key, final double min,
                                     final double max, final int offset, final int count) {
        client.zrangeByScore(key, min, max, offset, count);
        return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Set<String> zrangeByScore(final String key, final String min,
                                     final String max, final int offset, final int count) {
        client.zrangeByScore(key, min, max, offset, count);
        return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Set<Tuple> zrangeWithScores(final String key, final long start,
                                       final long end) {
        client.zrangeWithScores(key, start, end);
        Set<Tuple> set = getTupledSet();
        return set;
    }

    public Set<Tuple> zrangeByScoreWithScores(final String key,
                                              final double min, final double max) {
        client.zrangeByScoreWithScores(key, min, max);
        Set<Tuple> set = getTupledSet();
        return set;
    }

    public Set<Tuple> zrangeByScoreWithScores(final String key,
                                              final String min, final String max) {
        client.zrangeByScoreWithScores(key, min, max);
        Set<Tuple> set = getTupledSet();
        return set;
    }

    public Set<Tuple> zrangeByScoreWithScores(final String key,
                                              final double min, final double max, final int offset,
                                              final int count) {
        client.zrangeByScoreWithScores(key, min, max, offset, count);
        Set<Tuple> set = getTupledSet();
        return set;
    }

    public Set<Tuple> zrangeByScoreWithScores(final String key,
                                              final String min, final String max, final int offset,
                                              final int count) {
        client.zrangeByScoreWithScores(key, min, max, offset, count);
        Set<Tuple> set = getTupledSet();
        return set;
    }

    public Long zrank(final String key, final String member) {
        client.zrank(key, member);
        return client.getIntegerReply();
    }

    public Long zlexcount(final String key, final String min, final String max) {
      client.zlexcount(key, min, max);
      return client.getIntegerReply();
    }

    public Set<String> zrangeByLex(final String key, final String min, final String max) {
      client.zrangeByLex(key, min, max);
      return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Set<String> zrangeByLex(final String key, final String min, final String max,
        final int offset, final int count) {
      client.zrangeByLex(key, min, max, offset, count);
      return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Set<String> zrevrangeByLex(String key, String max, String min) {
      client.zrevrangeByLex(key, max, min);
      return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Set<String> zrevrangeByLex(String key, String max, String min, int offset, int count) {
      client.zrevrangeByLex(key, max, min, offset, count);
      return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Long zremrangeByLex(final String key, final String min, final String max) {
      client.zremrangeByLex(key, min, max);
      return client.getIntegerReply();
    }

    public Long zrem(final String key, final String... members) {
        client.zrem(key, members);
        return client.getIntegerReply();
    }

    public Long zremrangeByRank(final String key, final long start,
                                final long end) {
        client.zremrangeByRank(key, start, end);
        return client.getIntegerReply();
    }

    public Long zremrangeByScore(final String key, final double start,
                                 final double end) {
        client.zremrangeByScore(key, start, end);
        return client.getIntegerReply();
    }

    public Long zremrangeByScore(final String key, final String start,
                                 final String end) {
        client.zremrangeByScore(key, start, end);
        return client.getIntegerReply();
    }

    public Set<String> zrevrange(final String key, final long start,
                                 final long end) {
        client.zrevrange(key, start, end);
        final List<String> members = client.getMultiBulkReply();
        return new LinkedHashSet<String>(members);
    }

    public Set<Tuple> zrevrangeWithScores(final String key, final long start,
                                          final long end) {
        client.zrevrangeWithScores(key, start, end);
        Set<Tuple> set = getTupledSet();
        return set;
    }

    public Set<String> zrevrangeByScore(final String key, final double max,
                                        final double min) {
        client.zrevrangeByScore(key, max, min);
        return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Set<String> zrevrangeByScore(final String key, final String max,
                                        final String min) {
        client.zrevrangeByScore(key, max, min);
        return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Set<String> zrevrangeByScore(final String key, final double max,
                                        final double min, final int offset, final int count) {
        client.zrevrangeByScore(key, max, min, offset, count);
        return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Set<Tuple> zrevrangeByScoreWithScores(final String key,
                                                 final double max, final double min) {
        client.zrevrangeByScoreWithScores(key, max, min);
        Set<Tuple> set = getTupledSet();
        return set;
    }

    public Set<Tuple> zrevrangeByScoreWithScores(final String key,
                                                 final double max, final double min, final int offset,
                                                 final int count) {
        client.zrevrangeByScoreWithScores(key, max, min, offset, count);
        Set<Tuple> set = getTupledSet();
        return set;
    }

    public Set<Tuple> zrevrangeByScoreWithScores(final String key,
                                                 final String max, final String min, final int offset,
                                                 final int count) {
        client.zrevrangeByScoreWithScores(key, max, min, offset, count);
        Set<Tuple> set = getTupledSet();
        return set;
    }

    public Set<String> zrevrangeByScore(final String key, final String max,
                                        final String min, final int offset, final int count) {
        client.zrevrangeByScore(key, max, min, offset, count);
        return new LinkedHashSet<String>(client.getMultiBulkReply());
    }

    public Set<Tuple> zrevrangeByScoreWithScores(final String key,
                                                 final String max, final String min) {
        client.zrevrangeByScoreWithScores(key, max, min);
        Set<Tuple> set = getTupledSet();
        return set;
    }

    public Long zrevrank(final String key, final String member) {
        client.zrevrank(key, member);
        return client.getIntegerReply();
    }

    public Double zscore(final String key, final String member) {
        client.zscore(key, member);
        final String score = client.getBulkReply();
        return (score != null ? new Double(score) : null);
    }

    // ////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Connection

    public String ping() {
        client.ping();
        return client.getStatusCodeReply();
    }

    // ////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Server

    public String info() {
        client.info();
        return client.getBulkReply();
    }

    public String info(final String section) {
        client.info(section);
        return client.getBulkReply();
    }

    public Long dbSize() {
        client.dbSize();
        return client.getIntegerReply();
    }

    public byte[] dump(final String key) {
        client.dump(key);
        return client.getBinaryBulkReply();
    }

    public String restore(final String key, final long ttl,
                          final byte[] serializedValue) {
        client.restore(key, ttl, serializedValue);
        return client.getStatusCodeReply();
    }

    public Long geoadd(String key, double longitude, double latitude, String member) {
        client.geoadd(key, longitude, latitude, member);
        return client.getIntegerReply();
    }

    public Long geoadd(String key, Map<String, GeoCoordinate> memberCoordinateMap) {
        client.geoadd(key, memberCoordinateMap);
        return client.getIntegerReply();
    }

    public Double geodist(String key, String member1, String member2) {
        client.geodist(key, member1, member2);
        String dval = client.getBulkReply();
        return (dval != null ? new Double(dval) : null);
    }

    public Double geodist(String key, String member1, String member2, GeoUnit unit) {
        client.geodist(key, member1, member2, unit);
        String dval = client.getBulkReply();
        return (dval != null ? new Double(dval) : null);
    }

    public List<String> geohash(String key, String... members) {
        client.geohash(key, members);
        return client.getMultiBulkReply();
    }

    public List<GeoCoordinate> geopos(String key, String... members) {
        client.geopos(key, members);
        return BuilderFactory.GEO_COORDINATE_LIST.build(client.getObjectMultiBulkReply());
    }

    public List<GeoRadiusResponse> georadius(String key, double longitude, double latitude, double radius,
            GeoUnit unit) {
        client.georadius(key, longitude, latitude, radius, unit);
        return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(client.getObjectMultiBulkReply());
    }

    public List<GeoRadiusResponse> georadius(String key, double longitude, double latitude, double radius, GeoUnit unit,
            GeoRadiusParam param) {
        client.georadius(key, longitude, latitude, radius, unit, param);
        return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(client.getObjectMultiBulkReply());
    }

    public List<GeoRadiusResponse> georadiusByMember(String key, String member, double radius, GeoUnit unit) {
        client.georadiusByMember(key, member, radius, unit);
        return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(client.getObjectMultiBulkReply());
    }

    public List<GeoRadiusResponse> georadiusByMember(String key, String member, double radius, GeoUnit unit,
            GeoRadiusParam param) {
        client.georadiusByMember(key, member, radius, unit, param);
        return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(client.getObjectMultiBulkReply());
    }

    public Long pfadd(final String key, final String... elements) {
      client.pfadd(key, elements);
      return client.getIntegerReply();
    }

    public Long pfcount(final String key) {
      client.pfcount(key);
      return client.getIntegerReply();
    }

    public Long touch(String... keys) {
        client.touch(keys);
        return client.getIntegerReply();
    }

    /**
     * Quit.
     *
     * @return the string
     */
    public String quit() {
        client.quit();
        return client.getStatusCodeReply();
    }

    /**
     * Connect.
     */
    public void connect() {
        client.connect();
    }

    /**
     * Disconnect.
     */
    public void disconnect() {
        client.disconnect();
    }

    public void passivate() {
        client.passivate();
    }

    /**
     * Checks if is connected.
     *
     * @return true, if is connected
     */
    public boolean isConnected() {
        return client.isConnected();
    }

    /**
     * Gets the tupled set.
     *
     * @return the tupled set
     */
    private Set<Tuple> getTupledSet() {
        List<String> membersWithScores = client.getMultiBulkReply();
        Set<Tuple> set = new LinkedHashSet<Tuple>();
        Iterator<String> iterator = membersWithScores.iterator();
        while (iterator.hasNext()) {
            set.add(new Tuple(iterator.next(), Double.valueOf(iterator.next())));
        }
        return set;
    }
}


/*
 * Copyright 2015 Naver Corp.
 *
 * 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.
 */

/*
 * 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 com.navercorp.nbasearc.confmaster.server.leaderelection;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.navercorp.nbasearc.confmaster.config.Config;

/**
 *

 * A leader election support library implementing the ZooKeeper election recipe.
 *

 *

 * This support library is meant to simplify the construction of an exclusive
 * leader system on top of Apache ZooKeeper. Any application that can become the
 * leader (usually a process that provides a service, exclusively) would
 * configure an instance of this class with their hostname, at least one
 * listener (an implementation of {@link LeaderElectionAware}), and either an
 * instance of {@link ZooKeeper} or the proper connection information. Once
 * configured, invoking {@link #start()} will cause the client to connect to
 * ZooKeeper and create a leader offer. The library then determines if it has
 * been elected the leader using the algorithm described below. The client
 * application can follow all state transitions via the listener callback.
 *

 *

 * Leader election algorithm
 *

 *

 * The library starts in a START state. Through each state transition, a state
 * start and a state complete event are sent to all listeners. When
 * {@link #start()} is called, a leader offer is created in ZooKeeper. A leader
 * offer is an ephemeral sequential node that indicates a process that can act
 * as a leader for this service. A read of all leader offers is then performed.
 * The offer with the lowest sequence number is said to be the leader. The
 * process elected leader will transition to the leader state. All other
 * processes will transition to a ready state. Internally, the library creates a
 * ZooKeeper watch on the leader offer with the sequence ID of N - 1 (where N is
 * the process's sequence ID). If that offer disappears due to a process
 * failure, the watching process will run through the election determination
 * process again to see if it should become the leader. Note that sequence ID
 * may not be contiguous due to failed processes. A process may revoke its offer
 * to be the leader at any time by calling {@link #stop()}.
 *

 *

 * Guarantees (not) Made and Caveats
 *

 *

 *

 *
It is possible for a (poorly implemented) process to create a leader
 * offer, get the lowest sequence ID, but have something terrible occur where it
 * maintains its connection to ZK (and thus its ephemeral leader offer node) but
 * doesn't actually provide the service in question. It is up to the user to
 * ensure any failure to become the leader - and whatever that means in the
 * context of the user's application - results in a revocation of its leader
 * offer (i.e. that {@link #stop()} is called).

 *
It is possible for ZK timeouts and retries to play a role in service
 * liveliness. In other words, if process A has the lowest sequence ID but
 * requires a few attempts to read the other leader offers' sequence IDs,
 * election can seem slow. Users should apply timeouts during the determination
 * process if they need to hit a specific SLA.

 *
The library makes a "best effort" to detect catastrophic failures of the
 * process. It is possible that an unforeseen event results in (for instance) an
 * unchecked exception that propagates passed normal error handling code. This
 * normally doesn't matter as the same exception would almost certain destroy
 * the entire process and thus the connection to ZK and the leader offer
 * resulting in another round of leader determination.

 *

 *

 */
@Component
public class LeaderElectionSupport implements Watcher {

    private static final Logger logger = LoggerFactory
            .getLogger("rootLogger");

    @Autowired
    private Config config;

    private ZooKeeper zooKeeper;

    private State state;
    private final Set<LeaderElectionAware> listeners;

    private String rootNodeName;
    private LeaderOffer leaderOffer;
    private String hostName;

    public LeaderElectionSupport() {
        state = State.STOP;
        listeners = Collections
                .synchronizedSet(new HashSet<LeaderElectionAware>());
    }

    /**
     *

     * Start the election process. This method will create a leader offer,
     * determine its status, and either become the leader or become ready. If an
     * instance of {@link ZooKeeper} has not yet been configured by the user, a
     * new instance is created using the connectString and sessionTime
     * specified.
     *

     *

     * Any (anticipated) failures result in a failed event being sent to all
     * listeners.
     *

     */
    public synchronized void start() {
        state = State.START;
        dispatchEvent(LeaderElectionEventType.START);

        logger.info("Starting leader election support");

        if (zooKeeper == null) {
            throw new IllegalStateException(
                    "No instance of zookeeper provided. Hint: use setZooKeeper()");
        }

        if (hostName == null) {
            throw new IllegalStateException(
                    "No hostname provided. Hint: use setHostName()");
        }

        try {
            makeOffer();
            determineElectionStatus();
        } catch (KeeperException e) {
            becomeFailed(e);
        } catch (InterruptedException e) {
            becomeFailed(e);
        }
    }

    /**
     * Stops all election services, revokes any outstanding leader offers, and
     * disconnects from ZooKeeper.
     */
    public synchronized void stop() {
        state = State.STOP;
        dispatchEvent(LeaderElectionEventType.STOP_START);

        logger.info("Stopping leader election support");

        if (leaderOffer != null) {
            try {
                zooKeeper.delete(leaderOffer.getNodePath(), -1);
                logger.info("Removed leader offer {}",
                        leaderOffer.getNodePath());
            } catch (InterruptedException e) {
                becomeFailed(e);
            } catch (KeeperException e) {
                becomeFailed(e);
            }
        }

        dispatchEvent(LeaderElectionEventType.STOP_COMPLETE);
    }

    private void makeOffer() throws KeeperException, InterruptedException {
        state = State.OFFER;
        dispatchEvent(LeaderElectionEventType.OFFER_START);

        leaderOffer = new LeaderOffer();

        leaderOffer.setHostName(hostName);
        try {
            leaderOffer.setNodePath(zooKeeper.create(rootNodeName + "/" + "n_",
                    hostName.getBytes(config.getCharset()), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL_SEQUENTIAL));
        } catch (UnsupportedEncodingException e) {
            throw new AssertionError(config.getCharset() + " is unknown.");
        }

        logger.debug("Created leader offer {}", leaderOffer);

        dispatchEvent(LeaderElectionEventType.OFFER_COMPLETE);
    }

    private void determineElectionStatus() throws KeeperException,
            InterruptedException {

        state = State.DETERMINE;
        dispatchEvent(LeaderElectionEventType.DETERMINE_START);

        String[] components = leaderOffer.getNodePath().split("/");

        leaderOffer.setId(Integer.valueOf(components[components.length - 1]
                .substring("n_".length())));

        List<LeaderOffer> leaderOffers = toLeaderOffers(zooKeeper.getChildren(
                rootNodeName, false));

        /*
         * For each leader offer, find out where we fit in. If we're first, we
         * become the leader. If we're not elected the leader, attempt to stat
         * the offer just less than us. If they exist, watch for their failure,
         * but if they don't, become the leader.
         */
        for (int i = 0; i < leaderOffers.size(); i++) {
            LeaderOffer leaderOffer = leaderOffers.get(i);

            if (leaderOffer.getId().equals(this.leaderOffer.getId())) {
                logger.debug("There are {} leader offers. I am {} in line.",
                        leaderOffers.size(), i);

                dispatchEvent(LeaderElectionEventType.DETERMINE_COMPLETE);

                if (i == 0) {
                    becomeLeader();
                } else {
                    becomeReady(leaderOffers.get(i - 1));
                }

                /* Once we've figured out where we are, we're done. */
                break;
            }
        }
    }

    private void becomeReady(LeaderOffer neighborLeaderOffer)
            throws KeeperException, InterruptedException {
        dispatchEvent(LeaderElectionEventType.READY_START);

        logger.info("{} not elected leader. Watching node:{}",
                leaderOffer.getNodePath(), neighborLeaderOffer.getNodePath());

        /*
         * Make sure to pass an explicit Watcher because we could be sharing
         * this zooKeeper instance with someone else.
         */
        Stat stat = zooKeeper.exists(neighborLeaderOffer.getNodePath(), this);

        if (stat != null) {
            logger.debug(
                    "We're behind {} in line and they're alive. Keeping an eye on them.",
                    neighborLeaderOffer.getNodePath());
            state = State.READY;
            dispatchEvent(LeaderElectionEventType.READY_COMPLETE);
        } else {
            /*
             * If the stat fails, the node has gone missing between the call to
             * getChildren() and exists(). We need to try and become the leader.
             */
            logger.info(
                    "We were behind {} but it looks like they died. Back to determination.",
                    neighborLeaderOffer.getNodePath());
            determineElectionStatus();
        }

    }

    private void becomeLeader() {
        state = State.ELECTED;
        dispatchEvent(LeaderElectionEventType.ELECTED_START);

        logger.info("Becoming leader with node:{}", leaderOffer.getNodePath());

        dispatchEvent(LeaderElectionEventType.ELECTED_COMPLETE);
    }

    private void becomeFailed(Exception e) {
        logger.error("Failed in state {} - Exception:{}", state, e);

        state = State.FAILED;
        dispatchEvent(LeaderElectionEventType.FAILED);
    }

    /**
     * Fetch the (user supplied) hostname of the current leader. Note that by
     * the time this method returns, state could have changed so do not depend
     * on this to be strongly consistent. This method has to read all leader
     * offers from ZooKeeper to determine who the leader is (i.e. there is no
     * caching) so consider the performance implications of frequent invocation.
     * If there are no leader offers this method returns null.
     *
     * @return hostname of the current leader
     * @throws KeeperException
     * @throws InterruptedException
     */
    public String getLeaderHostName() throws KeeperException, InterruptedException {

        List<LeaderOffer> leaderOffers = toLeaderOffers(zooKeeper.getChildren(
                rootNodeName, false));

        if (leaderOffers.size() > 0) {
            return leaderOffers.get(0).getHostName();
        }

        return null;
    }

    private List<LeaderOffer> toLeaderOffers(List<String> strings)
            throws KeeperException, InterruptedException {

        List<LeaderOffer> leaderOffers = new ArrayList<LeaderOffer>(
                strings.size());

        /*
         * Turn each child of rootNodeName into a leader offer. This is a tuple
         * of the sequence number and the node name.
         */
        for (String offer : strings) {
            String hostName;
            try {
                hostName = new String(zooKeeper.getData(rootNodeName + "/"
                        + offer, false, null), config.getCharset());
            } catch (UnsupportedEncodingException e) {
                throw new AssertionError(config.getCharset() + " is unkown");
            }

            leaderOffers.add(
                new LeaderOffer(
                    Integer.valueOf(offer.substring("n_".length())),
                    rootNodeName + "/" + offer,
                    hostName));
        }

        /*
         * We sort leader offers by sequence number (which may not be zero-based
         * or contiguous) and keep their paths handy for setting watches.
         */
        Collections.sort(leaderOffers, new LeaderOffer.IdComparator());

        return leaderOffers;
    }

    @Override
    public void process(WatchedEvent event) {
        if (event.getType().equals(Watcher.Event.EventType.NodeDeleted)
                && !event.getPath().equals(leaderOffer.getNodePath())
                && state != State.STOP) {
            logger.debug(
                    "Node {} deleted. Need to run through the election process.",
                    event.getPath());
            try {
                determineElectionStatus();
            } catch (KeeperException e) {
                becomeFailed(e);
            } catch (InterruptedException e) {
                becomeFailed(e);
            }
        }
    }

    private void dispatchEvent(LeaderElectionEventType eventType) {
        logger.debug("Dispatching event:{}", eventType);

        synchronized (listeners) {
            if (listeners.size() > 0) {
                for (LeaderElectionAware observer : listeners) {
                    observer.onElectionEvent(eventType);
                }
            }
        }
    }

    /**
     * Adds {@code listener} to the list of listeners who will receive events.
     *
     * @param listener
     */
    public void addListener(LeaderElectionAware listener) {
        listeners.add(listener);
    }

    /**
     * Remove {@code listener} from the list of listeners who receive events.
     *
     * @param listener
     */
    public void removeListener(LeaderElectionAware listener) {
        listeners.remove(listener);
    }

    @Override
    public String toString() {
        return "{ state:" + state + " leaderOffer:" + leaderOffer
                + " zooKeeper:" + zooKeeper + " hostName:" + hostName
                + " listeners:" + listeners + " }";
    }

    /**
     *

     * Gets the ZooKeeper root node to use for this service.
     *

     *

     * For instance, a root node of {@code /mycompany/myservice} would be the
     * parent of all leader offers for this service. Obviously all processes
     * that wish to contend for leader status need to use the same root node.
     * Note: We assume this node already exists.
     *

     *
     * @return a znode path
     */
    public String getRootNodeName() {
        return rootNodeName;
    }

    /**
     *

     * Sets the ZooKeeper root node to use for this service.
     *

     *

     * For instance, a root node of {@code /mycompany/myservice} would be the
     * parent of all leader offers for this service. Obviously all processes
     * that wish to contend for leader status need to use the same root node.
     * Note: We assume this node already exists.
     *

     */
    public void setRootNodeName(String rootNodeName) {
        this.rootNodeName = rootNodeName;
    }

    /**
     * The {@link ZooKeeper} instance to use for all operations. Provided this
     * overrides any connectString or sessionTimeout set.
     */
    public ZooKeeper getZooKeeper() {
        return zooKeeper;
    }

    public void setZooKeeper(ZooKeeper zooKeeper) {
        this.zooKeeper = zooKeeper;
    }

    /**
     * The hostname of this process. Mostly used as a convenience for logging
     * and to respond to {@link #getLeaderHostName()} requests.
     */
    public String getHostName() {
        return hostName;
    }

    public void setHostName(String hostName) {
        this.hostName = hostName;
    }

    /**
     * The type of event.
     */
    public static enum LeaderElectionEventType {
        START, OFFER_START, OFFER_COMPLETE, DETERMINE_START, DETERMINE_COMPLETE, ELECTED_START, ELECTED_COMPLETE, READY_START, READY_COMPLETE, FAILED, STOP_START, STOP_COMPLETE,
    }

    /**
     * The internal state of the election support service.
     */
    public static enum State {
        START, OFFER, DETERMINE, ELECTED, READY, FAILED, STOP
    }

}


/*
 * Copyright 2015 NAVER Corp.
 *
 * 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.navercorp.redis.cluster.spring;

import com.navercorp.redis.cluster.RedisCluster;
import com.navercorp.redis.cluster.gateway.GatewayClient;
import com.navercorp.redis.cluster.gateway.GatewayConfig;
import com.navercorp.redis.cluster.gateway.GatewayException;

import javax.naming.OperationNotSupportedException;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.data.redis.connection.jedis.JedisConnection;

/**
 * Connection factory creating {@link RedisCluster} based connections.
 *
 * @author jaehong.kim
 */
public class RedisClusterConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory {

    private final RedisClusterExceptionConverter exceptionConverter = new RedisClusterExceptionConverter();

    /**
     * The client.
     */
    private GatewayClient client;

    /**
     * The config.
     */
    private GatewayConfig config = new GatewayConfig();

    private boolean convertPipelineAndTxResults = true;

    /**
     * Post process a newly retrieved connection. Useful for decorating or executing
     * initialization commands on a new connection.
     * This implementation simply returns the connection.
     *
     * @param connection the connection
     * @return processed connection
     */
    protected RedisClusterConnection postProcessConnection(RedisClusterConnection connection) {
        return connection;
    }

    /*
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() {
        if (client == null) {
            client = new GatewayClient(config);
        }
    }

    /*
     * @see org.springframework.beans.factory.DisposableBean#destroy()
     */
    public void destroy() {
        client.destroy();
    }

    /*
     * @see org.springframework.data.redis.connection.RedisConnectionFactory#getConnection()
     */
    public RedisClusterConnection getConnection() {
        final RedisClusterConnection connection = new RedisClusterConnection(this.client);
        connection.setConvertPipelineAndTxResults(this.convertPipelineAndTxResults);
        return postProcessConnection(connection);
    }

    /*
     * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException)
     */
    public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
        if(ex instanceof GatewayException) {
            return exceptionConverter.convert(ex);
        }

        return null;
    }

    /**
     * Gets the config.
     *
     * @return the config
     */
    public GatewayConfig getConfig() {
        return config;
    }

    /**
     * Sets the config.
     *
     * @param config the new config
     */
    public void setConfig(GatewayConfig config) {
        this.config = config;
    }

    /**
     * Specifies if pipelined results should be converted to the expected data type. If false, results of
     * {@link JedisConnection#closePipeline()} and {@link JedisConnection#exec()} will be of the type returned by the
     * Jedis driver
     *
     * @return Whether or not to convert pipeline and tx results
     */
    public boolean getConvertPipelineAndTxResults() {
        return convertPipelineAndTxResults;
    }

    /**
     * Specifies if pipelined results should be converted to the expected data type. If false, results of
     * {@link JedisConnection#closePipeline()} and {@link JedisConnection#exec()} will be of the type returned by the
     * Jedis driver
     *
     * @param convertPipelineAndTxResults Whether or not to convert pipeline and tx results
     */
    public void setConvertPipelineAndTxResults(boolean convertPipelineAndTxResults) {
        this.convertPipelineAndTxResults = convertPipelineAndTxResults;
    }

    @Deprecated
    @Override
    public org.springframework.data.redis.connection.RedisClusterConnection getClusterConnection() {
        throw new UnsupportedOperationException("nbase-arc doesn't support ClusterConnection.");
    }

    @Deprecated
    @Override
    public RedisSentinelConnection getSentinelConnection() {
        throw new UnsupportedOperationException("nbase-arc doesn't support SentinelConnection.");
    }
}


/*
 * Copyright 2015 NAVER Corp.
 *
 * 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.navercorp.redis.cluster.spring;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * The Class DefaultBoundSessionOfHashListOperations.
 *
 * @param <H>  the generic type
 * @param <HK> the generic type
 * @param <HV> the generic type
 * @author jaehong.kim
 */
public class DefaultBoundSessionOfHashListOperations<H, HK, HV> implements BoundSessionOfHashListOperations<H, HK, HV> {

    /**
     * The ops.
     */
    private final SessionOfHashListOperations<H, HK, HV> ops;

    /**
     * The key.
     */
    private H key;

    /**
     * The field.
     */
    private HK field;

    /**
     * Instantiates a new default bound session of hash list operations.
     *
     * @param key        the key
     * @param field      the field
     * @param operations the operations
     */
    public DefaultBoundSessionOfHashListOperations(H key, HK field, RedisClusterOperations<H, ?> operations) {
        this.key = key;
        this.field = field;
        this.ops = operations.opsForSessionOfHashList();
    }

    /**
     * Gets the key.
     *
     * @return the key
     */
    public H getKey() {
        return this.key;
    }

    /**
     * Gets the field.
     *
     * @return the field
     */
    public HK getField() {
        return this.field;
    }

    /*
     * @see BoundSessionOfHashListOperations#get(java.lang.Object)
     */
    public List<HV> get(HK key) {
        return ops.get(getKey(), getField(), key);
    }

    /*
     * @see BoundSessionOfHashListOperations#multiGet(java.util.Collection)
     */
    public Map<HK, List<HV>> multiGet(Collection<HK> keys) {
        return ops.multiGet(getKey(), getField(), keys);
    }

    /*
     * @see BoundSessionOfHashListOperations#keys()
     */
    public Set<HK> keys() {
        return ops.keys(getKey(), getField());
    }

    /*
     * @see BoundSessionOfHashListOperations#add(java.lang.Object, java.lang.Object)
     */
    public Long add(HK key, HV value) {
        return ops.add(getKey(), getField(), key, value);
    }

    /*
     * @see BoundSessionOfHashListOperations#add(java.lang.Object, java.lang.Object, long, java.util.concurrent.TimeUnit)
     */
    public Long add(HK key, HV value, long timeout, TimeUnit unit) {
        return ops.add(getKey(), getField(), key, value, timeout, unit);
    }

    public Long addAt(HK key, HV value, long millisecondsTimestamp) {
        return ops.addAt(getKey(), getField(), key, value, millisecondsTimestamp);
    }

    /*
     * @see BoundSessionOfHashListOperations#add(java.lang.Object, java.util.Collection)
     */
    public Long add(HK key, Collection<HV> values) {
        return ops.multiAdd(getKey(), getField(), key, values);
    }

    /*
     * @see BoundSessionOfHashListOperations#add(java.lang.Object, java.util.Collection, long, java.util.concurrent.TimeUnit)
     */
    public Long add(HK key, Collection<HV> values, long timeout, TimeUnit unit) {
        return ops.multiAdd(getKey(), getField(), key, values, timeout, unit);
    }

    public Long addAt(HK key, Collection<HV> values, long millisecondsTimestamp) {
        return ops.multiAddAt(getKey(), getField(), key, values, millisecondsTimestamp);
    }

    /*
     * @see BoundSessionOfHashListOperations#set(java.lang.Object, java.lang.Object)
     */
    public Long set(HK key, HV value) {
        return ops.set(getKey(), getField(), key, value);
    }

    /*
     * @see BoundSessionOfHashListOperations#set(java.lang.Object, java.lang.Object, long, java.util.concurrent.TimeUnit)
     */
    public Long set(HK key, HV value, long timeout, TimeUnit unit) {
        return ops.set(getKey(), getField(), key, value, timeout, unit);
    }

    /*
     * @see BoundSessionOfHashListOperations#set(java.lang.Object, java.util.Collection)
     */
    public Long set(HK key, Collection<HV> values) {
        return ops.multiSet(getKey(), getField(), key, values);
    }

    /*
     * @see BoundSessionOfHashListOperations#set(java.lang.Object, java.util.Collection, long, java.util.concurrent.TimeUnit)
     */
    public Long set(HK key, Collection<HV> values, long timeout, TimeUnit unit) {
        return ops.multiSet(getKey(), getField(), key, values, timeout, unit);
    }

    /*
     * @see BoundSessionOfHashListOperations#del(java.lang.Object)
     */
    public Long del(HK key) {
        return ops.del(getKey(), getField(), key);
    }

    /*
     * @see BoundSessionOfHashListOperations#del(java.lang.Object, java.lang.Object)
     */
    public Long del(HK key, HV value) {
        return ops.del(getKey(), getField(), key, value);
    }

    /*
     * @see BoundSessionOfHashListOperations#del(java.lang.Object, java.util.Collection)
     */
    public Long del(HK key, Collection<HV> values) {
        return ops.multiDel(getKey(), getField(), key, values);
    }

    /*
     * @see BoundSessionOfHashListOperations#count(java.lang.Object)
     */
    public Long count(HK key) {
        return ops.count(getKey(), getField(), key);
    }

    /*
     * @see BoundSessionOfHashListOperations#expire(java.lang.Object, long, java.util.concurrent.TimeUnit)
     */
    public Long expire(HK key, long timeout, TimeUnit unit) {
        return ops.expire(getKey(), getField(), key, timeout, unit);
    }

    /*
     * @see BoundSessionOfHashListOperations#expire(java.lang.Object, java.lang.Object, long, java.util.concurrent.TimeUnit)
     */
    public Long expire(HK key, HV value, long timeout, TimeUnit unit) {
        return ops.expire(getKey(), getField(), key, value, timeout, unit);
    }

    /*
     * @see BoundSessionOfHashListOperations#ttl(java.lang.Object, java.lang.Object)
     */
    public Long ttl(HK key, HV value) {
        return ops.ttl(getKey(), getField(), key, value);
    }

    public List<HV> values() {
        return ops.values(getKey(), getField());
    }
}