/*
 * 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.gateway;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.navercorp.redis.cluster.util.DaemonThreadFactory;

/**
 * The Class HealthCheckManager.
 *
 * @author seongminwoo
 */
public class HealthCheckManager implements Runnable {

    /**
     * The log.
     */
    private final Logger log = LoggerFactory.getLogger(HealthCheckManager.class);

    /**
     * The scheduler.
     */
    private ScheduledExecutorService scheduler;

    /**
     * The thread pool.
     */
    private ExecutorService threadPool;

    /**
     * The server data.
     */
    private final GatewayServerData serverData;

    /**
     * Instantiates a new health check manager.
     *
     * @param serverData    the server data
     * @param periodSeconds the period seconds
     * @param threadSize    the thread size
     */
    public HealthCheckManager(final GatewayServerData serverData, final int periodSeconds, final int threadSize) {
        this.serverData = serverData;
        this.threadPool = Executors.newFixedThreadPool(threadSize,
                new DaemonThreadFactory("nbase-arc-gateway-healthchecker-", true));
        this.scheduler = Executors.newScheduledThreadPool(1,
                new DaemonThreadFactory("nbase-arc-gateway-healthcheck-scheduler-", true));
        this.scheduler.scheduleAtFixedRate(this, periodSeconds, periodSeconds, TimeUnit.SECONDS);
    }

    /**
     * Shutdown.
     */
    public void shutdown() {
        try {
            this.scheduler.shutdownNow();
        } catch (Exception ignored) {
        }

        try {
            this.threadPool.shutdownNow();
        } catch (Exception ignored) {
        }
    }

    /*
     * @see java.lang.Runnable#run()
     */
    public void run() {
        for (final GatewayServer server : this.serverData.getServers()) {
            if (!server.isExist()) {
                continue;
            }

            Runnable handler = new Runnable() {
                public void run() {
                    if (!server.isExist()) {
                        return;
                    }

                    final boolean result = server.ping();
                    server.setValid(result);
                    log.debug("[HealthCheck] Result {}", server);
                }
            };
            this.threadPool.execute(handler);
        }
    }
}


/*
 * 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.gateway;

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.navercorp.redis.cluster.util.DaemonThreadFactory;

/**
 * @author jaehong.kim
 */
public class GatewayServerClear implements Runnable {
    private static final long LONG_WAITING_CHECK_PERIOD = 1000 * 60 * 60; // 1 hour
    private final Logger log = LoggerFactory.getLogger(GatewayServerClear.class);

    private ScheduledExecutorService scheduler;
    private CopyOnWriteArrayList<WaitingServer> list = new CopyOnWriteArrayList<WaitingServer>();
    private long lastRunTime = System.currentTimeMillis();

    public GatewayServerClear(final int periodSeconds) {
        this.scheduler = Executors.newScheduledThreadPool(1,
                new DaemonThreadFactory("nbase-arc-gateway-server-clearer-", true));
        this.scheduler.scheduleAtFixedRate(this, periodSeconds, periodSeconds, TimeUnit.SECONDS);
    }

    public void add(final GatewayServer server) {
        this.list.add(new WaitingServer(server));
    }

    public void run() {
        boolean checkLongWaiting = false;
        final long currentTime = System.currentTimeMillis();
        if (currentTime - this.lastRunTime > LONG_WAITING_CHECK_PERIOD) {
            this.lastRunTime = System.currentTimeMillis();
            checkLongWaiting = true;
        }

        for (final WaitingServer server : list) {
            log.debug("[GatewayServerClear] Check state {}", server);
            final boolean isLongWaiting = checkLongWaiting && server.getElapseTime() > LONG_WAITING_CHECK_PERIOD;
            if (server.isReady() || isLongWaiting) {
                if (isLongWaiting) {
                    log.warn("[GatewayServerClear] Find long waiting server {}", server);
                }

                list.remove(server);
                try {
                    server.destroy();
                    log.info("[GatewayServerClear] Destroy gateway server {}", server);
                } catch (Exception e) {
                    log.warn("[GatewayServerClear] Failed to destroy gateway server. " + server, e);
                }
            }
        }
    }

    /**
     * Shutdown.
     */
    public void shutdown() {
        try {
            this.scheduler.shutdownNow();
        } catch (Exception ignored) {
        }

        for (final WaitingServer server : list) {
            try {
                server.destroy();
                log.info("[GatewayServerClear] Destroy gateway server {}", server);
            } catch (Exception ignored) {
            }
        }
        list.clear();
    }

    class WaitingServer {
        long time;
        GatewayServer server;

        public WaitingServer(final GatewayServer server) {
            this.server = server;
            this.time = System.currentTimeMillis();
        }

        public boolean isReady() {
            this.server.flush();
            if (this.server.hasActiveConnection()) {
                return false;
            }

            return getElapseTime() > this.server.getMaxWait() + (this.server.getTimeout() * 2);
        }

        public void destroy() {
            try {
                this.server.destroy();
            } catch (Exception ignored) {
            }
        }

        public long getElapseTime() {
            return System.currentTimeMillis() - time;
        }

        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append("{");
            sb.append("elapse=").append(getElapseTime()).append(", ");
            sb.append("server=").append(server).append(", ");
            sb.append("}");
            return sb.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.redis.cluster.gateway;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.navercorp.redis.cluster.RedisCluster;
import com.navercorp.redis.cluster.RedisClusterPool;
import com.navercorp.redis.cluster.RedisClusterPoolConfig;

import redis.clients.jedis.exceptions.JedisException;

/**
 * The Class GatewayServer.
 *
 * @author jaehong.kim
 */
class GatewayServerSync implements GatewayServerImpl {
    private static final String CLIENT_SYNC_KEY_PREFIX = "##@@*25419530;CLIENT_SYNC;^##@&&*$#;620278919530;";

    /**
     * The log.
     */
    private final Logger log = LoggerFactory.getLogger(GatewayServerSync.class);

    /**
     * The Constant DEFAULT_POOL_CONFIG.
     */
    private static final RedisClusterPoolConfig DEFAULT_POOL_CONFIG = new RedisClusterPoolConfig();

    /**
     * The Constant DEFAULT_TIMEOUT_MILLISEC.
     */
    private static final int DEFAULT_TIMEOUT_MILLISEC = 1000;

    /**
     * The address.
     */
    private GatewayAddress address;

    /**
     * The pool.
     */
    private RedisClusterPool pool;

    /**
     * The valid.
     */
    private final AtomicBoolean valid = new AtomicBoolean(true);

    /**
     * The exist.
     */
    private final AtomicBoolean exist = new AtomicBoolean(true);

    /**
     * Instantiates a new gateway server.
     *
     * @param address the address
     */

    public GatewayServerSync(GatewayAddress address) {
        this(address, DEFAULT_POOL_CONFIG, DEFAULT_TIMEOUT_MILLISEC, null);
    }

    public GatewayServerSync(GatewayAddress address, RedisClusterPoolConfig poolConfig, int timeoutMillisec,
            String keyspace) {
        this.address = address;
        this.pool = new RedisClusterPool(poolConfig, address.getHost(), address.getPort(), timeoutMillisec, keyspace);
    }

    public int preload(final long syncTimeUnitMillis, final long connectPerDelayMillis,
                       final RedisClusterPoolConfig poolConfig) {
        if (syncTimeUnitMillis > 0) {
            try {
                final long syncNumber = sync();
                log.debug("[GatewayServer] Preload sync number {}", syncNumber);
                if (syncNumber > 0) {
                    log.info("[GatewayServer] Preload sync {}ms", syncNumber * syncTimeUnitMillis);
                    TimeUnit.MILLISECONDS.sleep(syncNumber * syncTimeUnitMillis);
                }
            } catch (Exception e) {
                log.warn("[GatewayServer] Failed to preload sync", e);
            }
        }

        // If initialSize > 0, preload the pool
        List<RedisCluster> preloadPool = new ArrayList<RedisCluster>();
        int max = poolConfig.getInitialSize();
        if (max > poolConfig.getMaxTotal()) {
            max = poolConfig.getMaxTotal();
        }

        for (int i = 0; i < max; i++) {
            try {
                if (connectPerDelayMillis > 0) {
                    TimeUnit.MILLISECONDS.sleep(connectPerDelayMillis);
                }
                preloadPool.add(getResource());
            } catch (Exception e) {
                log.warn("[GatewayServer] Failed to connect on preload", e);
            }
        }

        int count = 0;
        for (RedisCluster redis : preloadPool) {
            try {
                log.info("[GatewayServer] Preload connect {}", redis.connectInfo());
                if (ping(redis)) {
                    count++;
                }
            } catch (Exception e) {
                log.error("[GatewayServer] Failed to preload return resource " + redis, e);
            }
        }

        return count;
    }

    public RedisCluster getResource() {
        return this.pool.getResource();
    }

    public void returnResource(final RedisCluster redis) {
        if (redis == null) {
            return;
        }

        try {
            if (isValid()) {
                pool.returnResource(redis);
            } else {
                returnBrokenResource(redis);
            }
        } catch (JedisException e) {
            log.error("[GatewayServer] Failed to return resource", e);
        }
    }

    public void returnBrokenResource(final RedisCluster redis) {
        if (redis == null) {
            return;
        }

        try {
            pool.returnBrokenResource(redis);
        } catch (JedisException e) {
            log.error("[GatewayServer] Failed to return resource", e);
        }
    }

    /**
     * Ping.
     *
     * @return true, if successful
     * @throws HealthCheckException the health check exception
     */
    public boolean ping() {
        try {
            return ping(getResource());
        } catch (Exception e) {
            log.error("[GatewayServer] Failed to health check " + toString(), e);
        }

        return false;
    }

    private boolean ping(final RedisCluster redis) {
        boolean success = false;

        try {
            final String result = redis.ping();
            if (result != null && result.equals("PONG")) {
                success = true;
            }
            if (success) {
                returnResource(redis);
            } else {
                returnBrokenResource(redis);
            }
        } catch (Exception ex) {
            log.error("[GatewayServer] Failed to health check " + toString(), ex);
            returnBrokenResource(redis);
        }

        return success;
    }

    long sync() {
        long number = 0;
        RedisCluster redis = null;
        final String key = CLIENT_SYNC_KEY_PREFIX + address.toString()
                + TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis());
        try {
            redis = getResource();

            number = redis.incr(key);
            redis.expire(key, 60);

            returnResource(redis);
        } catch (Exception ex) {
            log.error("[GatewayServer] Failed to health check " + toString(), ex);
            returnBrokenResource(redis);
        }

        return number;
    }

    public void flush() {
        final int count = pool.getNumIdle();
        pool.clear();

        log.debug("[GatewayServer] Flush connection {} to {}", count, pool.getNumIdle());
    }

    public void close() {
        pool.stop();
        pool.clear();
    }

    public void destroy() {
        try {
            pool.destroy();
        } catch (Exception e) {
            log.error("[GatewayServer] Failed to destory. " + toString(), e);
        }
    }

    /**
     * Gets the address.
     *
     * @return the address
     */
    public GatewayAddress getAddress() {
        return address;
    }

    public boolean hasActiveConnection() {
        return this.pool.getNumActive() > 0;
    }

    public boolean isFullConnection() {
        return this.pool.getMaxTotal() == -1 ? false : this.pool.getMaxTotal() == this.pool.getNumActive();
    }

    public long getMaxWait() {
        return this.pool.getMaxWait();
    }

    public long getTimeout() {
        return this.pool.getTimeout();
    }

    /**
     * Sets the valid.
     *
     * @param valid the new valid
     */
    public void setValid(final boolean valid) {
        this.valid.set(valid);
        if (!this.valid.get()) {
            flush();
        }
    }

    /**
     * Checks if is valid.
     *
     * @return true, if is valid
     */
    public boolean isValid() {
        return valid.get();
    }

    public void setExist(final boolean exist) {
        this.exist.set(exist);
    }

    public boolean isExist() {
        return exist.get();
    }

    /*
     * @see java.lang.Object#toString()
     */
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        sb.append("address=").append(this.address).append(", ");
        if (this.pool != null) {
            sb.append("conn.active=").append(this.pool.getNumActive()).append(", ");
            sb.append("conn.idle=").append(this.pool.getNumIdle()).append(", ");
        }
        sb.append("valid=").append(this.valid).append(", ");
        sb.append("exist=").append(this.exist);
        sb.append("}");

        return sb.toString();
    }
}


/**
 * 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.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.navercorp.redis.cluster.util.RedisInputStream;
import com.navercorp.redis.cluster.util.RedisOutputStream;

import redis.clients.jedis.Protocol;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.util.SafeEncoder;

/**
 * Redis Cluster Protocol. (jedis 2.1.0)
 *
 * @author jaehong.kim
 */
public final class RedisProtocol {
    private final static Logger log = LoggerFactory
            .getLogger(RedisProtocol.class);

    /**
     * The Constant DEFAULT_PORT.
     */
    public static final int DEFAULT_PORT = 6379;

    /**
     * The Constant DEFAULT_TIMEOUT.
     */
    public static final int DEFAULT_TIMEOUT = 2000;

    /**
     * The Constant DEFAULT_DATABASE.
     */
    public static final int DEFAULT_DATABASE = 0;

    /**
     * The Constant CHARSET.
     */
    public static final String CHARSET = "UTF-8";

    /**
     * The Constant DOLLAR_BYTE.
     */
    public static final byte DOLLAR_BYTE = '$';

    /**
     * The Constant ASTERISK_BYTE.
     */
    public static final byte ASTERISK_BYTE = '*';

    /**
     * The Constant PLUS_BYTE.
     */
    public static final byte PLUS_BYTE = '+';

    /**
     * The Constant MINUS_BYTE.
     */
    public static final byte MINUS_BYTE = '-';

    /**
     * The Constant COLON_BYTE.
     */
    public static final byte COLON_BYTE = ':';

    /**
     * Instantiates a new redis protocol.
     */
    private RedisProtocol() {
        // this prevent the class from instantiation
    }

    /**
     * Send command.
     *
     * @param os      the os
     * @param command the command
     * @param args    the args
     */
    public static void sendCommand(final RedisOutputStream os,
                                   final Command command, final byte[]... args) {
        sendCommand(os, command.raw, args);
    }

    /**
     * Send command.
     *
     * @param os      the os
     * @param command the command
     * @param args    the args
     */
    private static void sendCommand(final RedisOutputStream os,
                                    final byte[] command, final byte[]... args) {
        try {
            os.write(ASTERISK_BYTE);
            os.writeIntCrLf(args.length + 1);
            os.write(DOLLAR_BYTE);
            os.writeIntCrLf(command.length);
            os.write(command);
            os.writeCrLf();

            for (final byte[] arg : args) {
                os.write(DOLLAR_BYTE);
                os.writeIntCrLf(arg.length);
                os.write(arg);
                os.writeCrLf();
            }
        } catch (IOException e) {
            throw new JedisConnectionException(e);
        }
    }

    /**
     * Process error.
     *
     * @param is the is
     */
    private static void processError(final RedisInputStream is) {
        String message = is.readLine();
        log.trace("[RedisProtocol] Reply error. -{}", message);
        throw new JedisDataException(message);
    }

    /**
     * Process.
     *
     * @param is the is
     * @return the object
     */
    private static Object process(final RedisInputStream is) {
        byte b = is.readByte();
        if (b == MINUS_BYTE) {
            processError(is);
            return null;
        } else if (b == ASTERISK_BYTE) {
            return processMultiBulkReply(is);
        } else if (b == COLON_BYTE) {
            return processInteger(is);
        } else if (b == DOLLAR_BYTE) {
            return processBulkReply(is);
        } else if (b == PLUS_BYTE) {
            return processStatusCodeReply(is);
        } else {
            throw new JedisConnectionException("Unknown reply: " + (char) b);
        }
    }

    /**
     * Process status code reply.
     *
     * @param is the is
     * @return the byte[]
     */
    private static byte[] processStatusCodeReply(final RedisInputStream is) {
        final String statusCode = is.readLine();
        log.trace("[RedisProtocol] Reply status-code. +{}", statusCode);
        return SafeEncoder.encode(statusCode);
    }

    /**
     * Process bulk reply.
     *
     * @param is the is
     * @return the byte[]
     */
    private static byte[] processBulkReply(final RedisInputStream is) {
        int len = Integer.parseInt(is.readLine());
        log.trace("[RedisProtocol] Reply bulk-reply. ${}", len);
        if (len == -1) {
            return null;
        }
        byte[] read = new byte[len];
        int offset = 0;
        while (offset < len) {
            offset += is.read(read, offset, (len - offset));
        }
        // read 2 more bytes for the command delimiter
        is.readByte();
        is.readByte();

        return read;
    }

    /**
     * Process integer.
     *
     * @param is the is
     * @return the long
     */
    private static Long processInteger(final RedisInputStream is) {
        final String num = is.readLine();
        log.trace("[RedisProtocol] Reply integer. :{}", num);
        return Long.valueOf(num);
    }

    /**
     * Process multi bulk reply.
     *
     * @param is the is
     * @return the list
     */
    private static List<Object> processMultiBulkReply(final RedisInputStream is) {
        int num = Integer.parseInt(is.readLine());
        log.trace("[RedisProtocol] Reply multi-bulk-reply. *{}", num);
        if (num == -1) {
            return null;
        }
        List<Object> ret = new ArrayList<Object>(num);
        for (int i = 0; i < num; i++) {
            try {
                ret.add(process(is));
            } catch (JedisDataException e) {
                ret.add(e);
            }
        }
        return ret;
    }

    /**
     * Read.
     *
     * @param is the is
     * @return the object
     */
    public static Object read(final RedisInputStream is) {
        return process(is);
    }

    /**
     * To byte array.
     *
     * @param value the value
     * @return the byte[]
     */
    public static final byte[] toByteArray(final boolean value) {
        return toByteArray(value ? 1 : 0);
    }

    /**
     * To byte array.
     *
     * @param value the value
     * @return the byte[]
     */
    public static final byte[] toByteArray(final int value) {
        return SafeEncoder.encode(String.valueOf(value));
    }

    /**
     * To byte array.
     *
     * @param value the value
     * @return the byte[]
     */
    public static final byte[] toByteArray(final long value) {
        return SafeEncoder.encode(String.valueOf(value));
    }

    /**
     * To byte array.
     *
     * @param value the value
     * @return the byte[]
     */
    public static final byte[] toByteArray(final double value) {
        return SafeEncoder.encode(String.valueOf(value));
    }

    /**
     * The Enum Command.
     */
    public static enum Command {

        /**
         * The PING.
         */
        PING,

        /**
         * The SET.
         */
        SET,

        /**
         * The GET.
         */
        GET,

        GEOADD,

        GEODIST,

        GEOHASH,

        GEOPOS,

        GEORADIUS,

        GEORADIUSBYMEMBER,

        PFADD,

        PFCOUNT,

        PFDEBUG,

        /**
         * The QUIT.
         */
        QUIT,

        /**
         * The EXISTS.
         */
        EXISTS,

        /**
         * The DEL.
         */
        DEL,

        /**
         * The TYPE.
         */
        TYPE,

        /**
         * The FLUSHDB.
         */
        FLUSHDB,

        /**
         * The KEYS.
         */
        KEYS,

        /**
         * The RANDOMKEY.
         */
        RANDOMKEY,

        /**
         * The RENAME.
         */
        RENAME,

        /**
         * The RENAMENX.
         */
        RENAMENX,

        /**
         * The RENAMEX.
         */
        RENAMEX,

        /**
         * The EXPIRE.
         */
        EXPIRE,

        /**
         * The EXPIREAT.
         */
        EXPIREAT,

        PEXPIRE,

        PEXPIREAT,

        /**
         * The TTL.
         */
        TTL,

        PTTL,

        /**
         * The SELECT.
         */
        SELECT,

        /**
         * The MOVE.
         */
        MOVE,

        /**
         * The FLUSHALL.
         */
        FLUSHALL,

        /**
         * The GETSET.
         */
        GETSET,

        /**
         * The MGET.
         */
        MGET,

        /**
         * The SETNX.
         */
        SETNX,

        /**
         * The SETEX.
         */
        SETEX,

        /**
         * The MSET.
         */
        MSET,

        /**
         * The MSETNX.
         */
        MSETNX,

        /**
         * The DECRBY.
         */
        DECRBY,

        /**
         * The DECR.
         */
        DECR,

        /**
         * The INCRBY.
         */
        INCRBY,

        INCRBYFLOAT,

        /**
         * The INCR.
         */
        INCR,

        /**
         * The APPEND.
         */
        APPEND,

        /**
         * The SUBSTR.
         */
        SUBSTR,

        PSETEX,

        BITCOUNT,

        BITFIELD,

        BITPOS,

        /**
         * The HSET.
         */
        HSET,

        /**
         * The HGET.
         */
        HGET,

        /**
         * The HSETNX.
         */
        HSETNX,

        /**
         * The HMSET.
         */
        HMSET,

        /**
         * The HMGET.
         */
        HMGET,

        /**
         * The HINCRBY.
         */
        HINCRBY,

        /**
         * The HEXISTS.
         */
        HEXISTS,

        /**
         * The HDEL.
         */
        HDEL,

        /**
         * The HLEN.
         */
        HLEN,

        /**
         * The HKEYS.
         */
        HKEYS,

        /**
         * The HVALS.
         */
        HVALS,

        /**
         * The HGETALL.
         */
        HGETALL,

        HSCAN,

        HSTRLEN,

        HINCRBYFLOAT,

        /**
         * The RPUSH.
         */
        RPUSH,

        /**
         * The LPUSH.
         */
        LPUSH,

        /**
         * The LLEN.
         */
        LLEN,

        /**
         * The LRANGE.
         */
        LRANGE,

        /**
         * The LTRIM.
         */
        LTRIM,

        /**
         * The LINDEX.
         */
        LINDEX,

        /**
         * The LSET.
         */
        LSET,

        /**
         * The LREM.
         */
        LREM,

        /**
         * The LPOP.
         */
        LPOP,

        /**
         * The RPOP.
         */
        RPOP,

        /**
         * The RPOPLPUSH.
         */
        RPOPLPUSH,

        /**
         * The SADD.
         */
        SADD,

        /**
         * The SMEMBERS.
         */
        SMEMBERS,

        /**
         * The SREM.
         */
        SREM,

        /**
         * The SPOP.
         */
        SPOP,

        /**
         * The SMOVE.
         */
        SMOVE,

        /**
         * The SCARD.
         */
        SCARD,

        /**
         * The SISMEMBER.
         */
        SISMEMBER,

        /**
         * The SINTER.
         */
        SINTER,

        /**
         * The SINTERSTORE.
         */
        SINTERSTORE,

        /**
         * The SUNION.
         */
        SUNION,

        /**
         * The SUNIONSTORE.
         */
        SUNIONSTORE,

        /**
         * The SDIFF.
         */
        SDIFF,

        /**
         * The SDIFFSTORE.
         */
        SDIFFSTORE,

        /**
         * The SRANDMEMBER.
         */
        SRANDMEMBER,

        SSCAN,

        /**
         * The ZADD.
         */
        ZADD,

        /**
         * The ZRANGE.
         */
        ZRANGE,

        /**
         * The ZREM.
         */
        ZREM,

        /**
         * The ZINCRBY.
         */
        ZINCRBY,

        ZLEXCOUNT,

        /**
         * The ZRANK.
         */
        ZRANK,

        ZRANGEBYLEX,

        ZREVRANGEBYLEX,

        ZREMRANGEBYLEX,

        /**
         * The ZREVRANK.
         */
        ZREVRANK,

        /**
         * The ZREVRANGE.
         */
        ZREVRANGE,

        /**
         * The ZCARD.
         */
        ZCARD,

        /**
         * The ZSCORE.
         */
        ZSCORE,

        /**
         * The MULTI.
         */
        MULTI,

        /**
         * The DISCARD.
         */
        DISCARD,

        /**
         * The EXEC.
         */
        EXEC,

        /**
         * The WATCH.
         */
        WATCH,

        /**
         * The UNWATCH.
         */
        UNWATCH,

        /**
         * The SORT.
         */
        SORT,

        /**
         * The BLPOP.
         */
        BLPOP,

        /**
         * The BRPOP.
         */
        BRPOP,

        /**
         * The AUTH.
         */
        AUTH,

        /**
         * The SUBSCRIBE.
         */
        SUBSCRIBE,

        /**
         * The PUBLISH.
         */
        PUBLISH,

        /**
         * The UNSUBSCRIBE.
         */
        UNSUBSCRIBE,

        /**
         * The PSUBSCRIBE.
         */
        PSUBSCRIBE,

        /**
         * The PUNSUBSCRIBE.
         */
        PUNSUBSCRIBE,

        /**
         * The ZCOUNT.
         */
        ZCOUNT,

        /**
         * The ZRANGEBYSCORE.
         */
        ZRANGEBYSCORE,

        /**
         * The ZREVRANGEBYSCORE.
         */
        ZREVRANGEBYSCORE,

        /**
         * The ZREMRANGEBYRANK.
         */
        ZREMRANGEBYRANK,

        /**
         * The ZREMRANGEBYSCORE.
         */
        ZREMRANGEBYSCORE,

        /**
         * The ZUNIONSTORE.
         */
        ZUNIONSTORE,

        /**
         * The ZINTERSTORE.
         */
        ZINTERSTORE,

        ZSCAN,

        /**
         * The SAVE.
         */
        SAVE,

        /**
         * The BGSAVE.
         */
        BGSAVE,

        /**
         * The BGREWRITEAOF.
         */
        BGREWRITEAOF,

        /**
         * The LASTSAVE.
         */
        LASTSAVE,

        /**
         * The SHUTDOWN.
         */
        SHUTDOWN,

        /**
         * The INFO.
         */
        INFO,

        /**
         * The MONITOR.
         */
        MONITOR,

        /**
         * The SLAVEOF.
         */
        SLAVEOF,

        /**
         * The CONFIG.
         */
        CONFIG,

        /**
         * The STRLEN.
         */
        STRLEN,

        /**
         * The SYNC.
         */
        SYNC,

        /**
         * The LPUSHX.
         */
        LPUSHX,

        /**
         * The PERSIST.
         */
        PERSIST,

        /**
         * The RPUSHX.
         */
        RPUSHX,

        /**
         * The ECHO.
         */
        ECHO,

        /**
         * The LINSERT.
         */
        LINSERT,

        /**
         * The DEBUG.
         */
        DEBUG,

        /**
         * The BRPOPLPUSH.
         */
        BRPOPLPUSH,

        /**
         * The SETBIT.
         */
        SETBIT,

        /**
         * The GETBIT.
         */
        GETBIT,

        /**
         * The SETRANGE.
         */
        SETRANGE,

        /**
         * The GETRANGE.
         */
        GETRANGE,

        /**
         * The EVAL.
         */
        EVAL,

        /**
         * The EVALSHA.
         */
        EVALSHA,

        /**
         * The SCRIPT.
         */
        SCRIPT,

        /**
         * The SLOWLOG.
         */
        SLOWLOG,

        /**
         * The OBJECT.
         */
        OBJECT,

        /**
         * The DBSIZE.
         */
        DBSIZE,

        DUMP,

        RESTORE,

        SCAN,

        CSCAN,

        CSCANDIGEST,

        CSCANLEN,

        /**
         * The S3 rem.
         */
        S3REM,

        /**
         * The S3 keys.
         */
        S3KEYS,

        /**
         * The S3 count.
         */
        S3COUNT,

        /**
         * The S3 expire.
         */
        S3EXPIRE,

        /**
         * The S3 lget.
         */
        S3LGET,

        /**
         * The S3 lmget.
         */
        S3LMGET,

        /**
         * The S3 lkeys.
         */
        S3LKEYS,

        /**
         * The S3 lvals.
         */
        S3LVALS,

        /**
         * The S3 ladd.
         */
        S3LADD,

        S3LADDAT,

        /**
         * The S3 lrem.
         */
        S3LREM,

        /**
         * The S3 lmrem.
         */
        S3LMREM,

        /**
         * The S3 lset.
         */
        S3LSET,

        /**
         * The S3 lreplace.
         */
        S3LREPLACE,

        /**
         * The S3 lcount.
         */
        S3LCOUNT,

        /**
         * The S3 lexists.
         */
        S3LEXISTS,

        /**
         * The S3 lexpire.
         */
        S3LEXPIRE,

        /**
         * The S3 lmexpire.
         */
        S3LMEXPIRE,

        /**
         * The S3 lttl.
         */
        S3LTTL,

        /**
         * The S3 lmadd.
         */
        S3LMADD,

        /**
         * The S3 sget.
         */
        S3SGET,

        /**
         * The S3 smget.
         */
        S3SMGET,

        /**
         * The S3 skeys.
         */
        S3SKEYS,

        /**
         * The S3 sadd.
         */
        S3SADD,

        S3SADDAT,

        /**
         * The S3 sset.
         */
        S3SSET,

        /**
         * The S3 srem.
         */
        S3SREM,

        /**
         * The S3 smrem.
         */
        S3SMREM,

        /**
         * The S3 scount.
         */
        S3SCOUNT,

        /**
         * The S3 sexists.
         */
        S3SEXISTS,

        /**
         * The S3 sexpire.
         */
        S3SEXPIRE,

        /**
         * The S3 sttl.
         */
        S3STTL,

        /**
         * The S3 svals.
         */
        S3SVALS,

        /**
         * The S3 smadd.
         */
        S3SMADD,

        TOUCH;

        /**
         * The raw.
         */
        public final byte[] raw;

        /**
         * Instantiates a new command.
         */
        Command() {
            raw = SafeEncoder.encode(this.name());
        }
    }

    /**
     * The Enum Keyword.
     */
    public static enum Keyword {

        /**
         * The AGGREGATE.
         */
        AGGREGATE,

        /**
         * The ALPHA.
         */
        ALPHA,

        /**
         * The ASC.
         */
        ASC,

        /**
         * The BY.
         */
        BY,

        /**
         * The DESC.
         */
        DESC,

        /**
         * The GET.
         */
        GET,

        /**
         * The LIMIT.
         */
        LIMIT,

        /**
         * The MESSAGE.
         */
        MESSAGE,

        /**
         * The NO.
         */
        NO,

        /**
         * The NOSORT.
         */
        NOSORT,

        /**
         * The PMESSAGE.
         */
        PMESSAGE,

        /**
         * The PSUBSCRIBE.
         */
        PSUBSCRIBE,

        /**
         * The PUNSUBSCRIBE.
         */
        PUNSUBSCRIBE,

        /**
         * The OK.
         */
        OK,

        /**
         * The ONE.
         */
        ONE,

        /**
         * The QUEUED.
         */
        QUEUED,

        /**
         * The SET.
         */
        SET,

        /**
         * The STORE.
         */
        STORE,

        /**
         * The SUBSCRIBE.
         */
        SUBSCRIBE,

        /**
         * The UNSUBSCRIBE.
         */
        UNSUBSCRIBE,

        /**
         * The WEIGHTS.
         */
        WEIGHTS,

        /**
         * The WITHSCORES.
         */
        WITHSCORES,

        /**
         * The RESETSTAT.
         */
        RESETSTAT,

        /**
         * The RESET.
         */
        RESET,

        /**
         * The FLUSH.
         */
        FLUSH,

        /**
         * The EXISTS.
         */
        EXISTS,

        /**
         * The LOAD.
         */
        LOAD,

        /**
         * The KILL.
         */
        KILL,

        /**
         * The LEN.
         */
        LEN,

        /**
         * The REFCOUNT.
         */
        REFCOUNT,

        /**
         * The ENCODING.
         */
        ENCODING,

        /**
         * The IDLETIME.
         */
        IDLETIME,

        INCRBY,

        SAT,

        OVERFLOW,

        PX,

        NX,

        XX;

        /**
         * The raw.
         */
        public final byte[] raw;

        /**
         * Instantiates a new keyword.
         */
        Keyword() {
            raw = SafeEncoder.encode(this.name().toLowerCase());
        }
    }
}


/*
 * 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.*;
import java.util.concurrent.TimeUnit;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;

/**
 * The Class DefaultSessionOfHashSetOperations.
 *
 * @param <H>  the generic type
 * @param <HK> the generic type
 * @param <HV> the generic type
 * @author jaehong.kim
 */
public class DefaultSessionOfHashSetOperations<H, HK, HV> extends AbstractOperations<H, Object> implements
        SessionOfHashSetOperations<H, HK, HV> {

    /**
     * Instantiates a new default session of hash set operations.
     *
     * @param template the template
     */
    @SuppressWarnings("unchecked")
    DefaultSessionOfHashSetOperations(RedisClusterTemplate<H, ?> template) {
        super((RedisClusterTemplate<H, Object>) template);
    }

    /*
     * @see SessionOfHashSetOperations#get(java.lang.Object, java.lang.Object, java.lang.Object)
     */
    public Set<HV> get(H key, HK field, HK name) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);

        Set<byte[]> rawElementValues = execute(new RedisCallback<Set<byte[]>>() {
            public Set<byte[]> doInRedis(RedisConnection connection) {
                return ((RedisClusterConnection) connection).ssGet(rawKey, rawHashKey, rawElementName);
            }
        }, true);

        return (Set<HV>) deserializeValues(rawElementValues);
    }

    /*
     * @see SessionOfHashSetOperations#mulitGet(java.lang.Object, java.lang.Object, java.util.Collection)
     */
    public Map<HK, Set<HV>> mulitGet(H key, HK field, Collection<HK> names) {
        if (names.isEmpty()) {
            return new HashMap<HK, Set<HV>>();
        }

        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[][] rawElementNames = new byte[names.size()][];
        int count = 0;
        for (HK name : names) {
            rawElementNames[count++] = rawHashKey(name);
        }

        Map<byte[], Set<byte[]>> rawElementValues = execute(new RedisCallback<Map<byte[], Set<byte[]>>>() {
            public Map<byte[], Set<byte[]>> doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssMGet(rawKey, rawHashKey, rawElementNames);
            }
        }, true);

        return deserializeHashSet(rawElementValues);
    }

    /*
     * @see SessionOfHashSetOperations#keys(java.lang.Object)
     */
    public Set<HK> keys(H key) {
        final byte[] rawKey = rawKey(key);

        Set<byte[]> rawHashKeys = execute(new RedisCallback<Set<byte[]>>() {
            public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssKeys(rawKey);
            }
        }, true);

        return deserializeHashKeys(rawHashKeys);
    }

    /*
     * @see SessionOfHashSetOperations#keys(java.lang.Object, java.lang.Object)
     */
    public Set<HK> keys(H key, HK field) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);

        Set<byte[]> rawElementNames = execute(new RedisCallback<Set<byte[]>>() {
            public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssKeys(rawKey, rawHashKey);
            }
        }, true);

        return deserializeHashKeys(rawElementNames);
    }

    /*
     * @see SessionOfHashSetOperations#add(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object)
     */
    public Long add(H key, HK field, HK name, HV value) {
        return add(key, field, name, value, 0, TimeUnit.SECONDS);
    }

    /*
     * @see SessionOfHashSetOperations#add(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, long, java.util.concurrent.TimeUnit)
     */
    public Long add(H key, HK field, HK name, HV value, long timeout, TimeUnit unit) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final byte[] rawElementValue = rawHashValue(value);
        final long expireSeconds = unit.toSeconds(timeout);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssAdd(rawKey, rawHashKey, rawElementName, expireSeconds,
                        rawElementValue);
            }
        }, true);
    }

    public Long addAt(H key, HK field, HK name, HV value, final long millisecondsTimestamp) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final byte[] rawElementValue = rawHashValue(value);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssAddAt(rawKey, rawHashKey, rawElementName,
                        millisecondsTimestamp, rawElementValue);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#multiAdd(java.lang.Object, java.lang.Object, java.lang.Object, java.util.Collection)
     */
    public Long multiAdd(H key, HK field, HK name, Collection<HV> values) {
        return multiAdd(key, field, name, values, 0, TimeUnit.SECONDS);
    }

    /*
     * @see SessionOfHashSetOperations#multiAdd(java.lang.Object, java.lang.Object, java.lang.Object, java.util.Collection, long, java.util.concurrent.TimeUnit)
     */
    public Long multiAdd(H key, HK field, HK name, Collection<HV> values, long timeout, TimeUnit unit) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final byte[][] rawElementValues = new byte[values.size()][];
        int count = 0;
        for (HV value : values) {
            rawElementValues[count++] = rawHashValue(value);
        }
        final long expireSeconds = unit.toSeconds(timeout);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssAdd(rawKey, rawHashKey, rawElementName, expireSeconds,
                        rawElementValues);
            }
        }, true);
    }

    public Long multiAddAt(H key, HK field, HK name, Collection<HV> values, final long millisecondsTimestamp) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final byte[][] rawElementValues = new byte[values.size()][];
        int count = 0;
        for (HV value : values) {
            rawElementValues[count++] = rawHashValue(value);
        }

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssAddAt(rawKey, rawHashKey, rawElementName,
                        millisecondsTimestamp, rawElementValues);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#set(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object)
     */
    public Long set(H key, HK field, HK name, HV value) {
        return set(key, field, name, value, 0, TimeUnit.SECONDS);
    }

    /*
     * @see SessionOfHashSetOperations#set(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, long, java.util.concurrent.TimeUnit)
     */
    public Long set(H key, HK field, HK name, HV value, long timeout, TimeUnit unit) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final byte[] rawElementValue = rawHashValue(value);
        final long expireSeconds = unit.toSeconds(timeout);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssSet(rawKey, rawHashKey, rawElementName, expireSeconds,
                        rawElementValue);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#multiSet(java.lang.Object, java.lang.Object, java.lang.Object, java.util.Collection)
     */
    public Long multiSet(H key, HK field, HK name, Collection<HV> values) {
        return multiSet(key, field, name, values, 0, TimeUnit.SECONDS);
    }

    /*
     * @see SessionOfHashSetOperations#multiSet(java.lang.Object, java.lang.Object, java.lang.Object, java.util.Collection, long, java.util.concurrent.TimeUnit)
     */
    public Long multiSet(H key, HK field, HK name, Collection<HV> values, long timeout, TimeUnit unit) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final byte[][] rawElementValues = new byte[values.size()][];
        int count = 0;
        for (HV value : values) {
            rawElementValues[count++] = rawHashValue(value);
        }
        final long expireSeconds = unit.toSeconds(timeout);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssSet(rawKey, rawHashKey, rawElementName, expireSeconds,
                        rawElementValues);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#del(java.lang.Object)
     */
    public Long del(H key) {
        final byte[] rawKey = rawKey(key);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssDel(rawKey);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#del(java.lang.Object, java.lang.Object)
     */
    public Long del(H key, HK field) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssRem(rawKey, rawHashKey);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#del(java.lang.Object, java.lang.Object, java.lang.Object)
     */
    public Long del(H key, HK field, HK name) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssRem(rawKey, rawHashKey, rawElementName);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#del(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object)
     */
    public Long del(H key, HK field, HK name, HV value) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final byte[] rawElementValue = rawHashValue(value);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssRem(rawKey, rawHashKey, rawElementName, rawElementValue);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#multiDel(java.lang.Object, java.lang.Object, java.lang.Object, java.util.Collection)
     */
    public Long multiDel(H key, HK field, HK name, Collection<HV> values) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final byte[][] rawElementValues = new byte[values.size()][];
        int count = 0;
        for (HV value : values) {
            rawElementValues[count++] = rawHashValue(value);
        }

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssRem(rawKey, rawHashKey, rawElementName, rawElementValues);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#count(java.lang.Object, java.lang.Object, java.lang.Object)
     */
    public Long count(H key, HK field, HK name) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssCount(rawKey, rawHashKey, rawElementName);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#exists(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object)
     */
    public Boolean exists(H key, HK field, HK name, HV value) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final byte[] rawElementValue = rawHashValue(value);

        return execute(new RedisCallback<Boolean>() {
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssExists(rawKey, rawHashKey, rawElementName,
                        rawElementValue);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#expire(java.lang.Object, long, java.util.concurrent.TimeUnit)
     */
    public Long expire(H key, long timeout, TimeUnit unit) {
        final byte[] rawKey = rawKey(key);
        final long expireSeconds = unit.toSeconds(timeout);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssExpire(rawKey, expireSeconds);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#expire(java.lang.Object, java.lang.Object, long, java.util.concurrent.TimeUnit)
     */
    public Long expire(H key, HK field, long timeout, TimeUnit unit) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final long expireSeconds = unit.toSeconds(timeout);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssExpire(rawKey, rawHashKey, expireSeconds);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#expire(java.lang.Object, java.lang.Object, java.lang.Object, long, java.util.concurrent.TimeUnit)
     */
    public Long expire(H key, HK field, HK name, long timeout, TimeUnit unit) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final long expireSeconds = unit.toSeconds(timeout);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssExpire(rawKey, rawHashKey, rawElementName, expireSeconds);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#expire(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, long, java.util.concurrent.TimeUnit)
     */
    public Long expire(H key, HK field, HK name, HV value, long timeout, TimeUnit unit) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final byte[] rawElementValue = rawHashValue(value);
        final long expireSeconds = unit.toSeconds(timeout);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssExpire(rawKey, rawHashKey, rawElementName,
                        rawElementValue, expireSeconds);
            }
        }, true);
    }

    /*
     * @see SessionOfHashSetOperations#ttl(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object)
     */
    public Long ttl(H key, HK field, HK name, HV value) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);
        final byte[] rawElementName = rawHashKey(name);
        final byte[] rawElementValue = rawHashValue(value);

        return execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssTTL(rawKey, rawHashKey, rawElementName, rawElementValue);
            }
        }, true);
    }

    public List<HV> values(H key, HK field) {
        final byte[] rawKey = rawKey(key);
        final byte[] rawHashKey = rawHashKey(field);

        List<byte[]> rawElementValues = execute(new RedisCallback<List<byte[]>>() {
            public List<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
                return ((RedisClusterConnection) connection).ssVals(rawKey, rawHashKey);
            }
        }, true);

        return deserializeHashValues(rawElementValues);
    }
}


/*
 * 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 org.springframework.data.redis.core.ZSetOperations.TypedTuple;

/**
 * The Class DefaultTypedTuple.
 *
 * @param <V> the value type
 * @author jaehong.kim
 */
public class DefaultTypedTuple<V> implements TypedTuple<V> {

    /**
     * The score.
     */
    private final Double score;

    /**
     * The value.
     */
    private final V value;

    /**
     * Constructs a new DefaultTypedTuple instance.
     *
     * @param value the value
     * @param score the score
     */
    public DefaultTypedTuple(V value, Double score) {
        this.score = score;
        this.value = value;
    }

    /*
     * @see org.springframework.data.redis.core.ZSetOperations.TypedTuple#getScore()
     */
    public Double getScore() {
        return score;
    }

    /*
     * @see org.springframework.data.redis.core.ZSetOperations.TypedTuple#getValue()
     */
    public V getValue() {
        return value;
    }

    /*
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((score == null) ? 0 : score.hashCode());
        result = prime * result + ((value == null) ? 0 : value.hashCode());
        return result;
    }

    /*
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof DefaultTypedTuple))
            return false;
        DefaultTypedTuple other = (DefaultTypedTuple) obj;
        if (score == null) {
            if (other.score != null)
                return false;
        } else if (!score.equals(other.score))
            return false;
        if (value == null) {
            if (other.value != null)
                return false;
        } else if (!value.equals(other.value))
            return false;
        return true;
    }

    /*
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    public int compareTo(Double o) {
        Double d = (score == null ? Double.valueOf(0) : score);
        Double a = (o == null ? Double.valueOf(0) : o);
        return d.compareTo(a);
    }

    public int compareTo(TypedTuple<V> o) {
        if (o == null) {
            return compareTo(Double.valueOf(0));
        }

        return compareTo(o.getScore());
    }
}


/*
 * 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.triples;

import redis.clients.util.SafeEncoder;

/**
 * The Class TriplesRedisClusterClient.
 *
 * @author jaehong.kim
 */
public class TriplesRedisClusterClient extends BinaryTriplesRedisClusterClient {

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

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

    // Hashes of Lists ////////////////////

    public TriplesRedisClusterClient(String host, int port, boolean async) {
        super(host, port, async);
    }

    /**
     * Slget.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slget(final String keyspace, final String uid, final String serviceCode, final String key) {
        slget(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Slmget.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param keys        the keys
     */
    public void slmget(final String keyspace, final String uid, final String serviceCode, final String... keys) {
        final byte[][] bkeys = new byte[keys.length][];
        for (int i = 0; i < keys.length; i++) {
            bkeys[i] = SafeEncoder.encode(keys[i]);
        }
        slmget(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode), bkeys);
    }

    /**
     * Slkeys.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void slkeys(final String keyspace, final String uid) {
        slkeys(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid));
    }

    /**
     * Slkeys.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void slkeys(final String keyspace, final String uid, final String serviceCode) {
        slkeys(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode));
    }

    /**
     * Slkeys.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slkeys(final String keyspace, final String uid, final String serviceCode, final String key) {
        slkeys(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Sladd.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param values       the values
     */
    public void sladd(final String keyspace, final String uid, final String serviceCode, final String key,
                      final long expireMillis, final String... values) {
        final byte[][] bvalues = new byte[values.length][];
        for (int i = 0; i < values.length; i++) {
            bvalues[i] = SafeEncoder.encode(values[i]);
        }
        sladd(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), expireMillis, bvalues);
    }

    public void sladdAt(final String keyspace, final String uid, final String serviceCode, final String key,
                        final long unixTime, final String... values) {
        final byte[][] bvalues = new byte[values.length][];
        for (int i = 0; i < values.length; i++) {
            bvalues[i] = SafeEncoder.encode(values[i]);
        }
        sladdAt(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), unixTime, bvalues);
    }

    /**
     * Sldel.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void sldel(final String keyspace, final String uid) {
        sldel(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid));
    }

    /**
     * Slrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void slrem(final String keyspace, final String uid, final String serviceCode) {
        slrem(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode));
    }

    /**
     * Slrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slrem(final String keyspace, final String uid, final String serviceCode, final String key) {
        slrem(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Slrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param value       the value
     */
    public void slrem(final String keyspace, final String uid, final String serviceCode, final String key,
                      final String value) {
        slrem(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), SafeEncoder.encode(value));
    }

    /**
     * Slrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param values      the values
     */
    public void slrem(final String keyspace, final String uid, final String serviceCode, final String key,
                      final String[] values) {
        final byte[][] bvalues = new byte[values.length][];
        for (int i = 0; i < values.length; i++) {
            bvalues[i] = SafeEncoder.encode(values[i]);
        }
        slrem(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), bvalues);
    }

    /**
     * Slmrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param keys        the keys
     */
    public void slmrem(final String keyspace, final String uid, final String serviceCode, final String... keys) {
        final byte[][] bkeys = new byte[keys.length][];
        for (int i = 0; i < keys.length; i++) {
            bkeys[i] = SafeEncoder.encode(keys[i]);
        }
        slmrem(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode), bkeys);
    }

    /**
     * Slcount.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void slcount(final String keyspace, final String uid) {
        slcount(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid));
    }

    /**
     * Slcount.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void slcount(final String keyspace, final String uid, final String serviceCode) {
        slcount(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode));
    }

    /**
     * Slcount.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slcount(final String keyspace, final String uid, final String serviceCode, final String key) {
        slcount(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Slexists.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slexists(final String keyspace, final String uid, final String serviceCode, final String key) {
        slexists(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Slexists.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param value       the value
     */
    public void slexists(final String keyspace, final String uid, final String serviceCode, final String key,
                         final String value) {
        slexists(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), SafeEncoder.encode(value));
    }

    /**
     * Slexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param expireMillis the expire millis
     */
    public void slexpire(final String keyspace, final String uid, long expireMillis) {
        slexpire(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), expireMillis);
    }

    /**
     * Slexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param expireMillis the expire millis
     */
    public void slexpire(final String keyspace, final String uid, final String serviceCode, long expireMillis) {
        slexpire(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode), expireMillis);
    }

    /**
     * Slexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     */
    public void slexpire(final String keyspace, final String uid, final String serviceCode, final String key,
                         long expireMillis) {
        slexpire(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), expireMillis);
    }

    /**
     * Slexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param value        the value
     * @param expireMillis the expire millis
     */
    public void slexpire(final String keyspace, final String uid, final String serviceCode, final String key,
                         final String value, final long expireMillis) {
        slexpire(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), SafeEncoder.encode(value), expireMillis);
    }

    /**
     * Slmexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param expireMillis the expire millis
     * @param serviceCode  the service code
     * @param keys         the keys
     */
    public void slmexpire(final String keyspace, final String uid, final long expireMillis, final String serviceCode,
                          final String... keys) {
        final byte[][] bkeys = new byte[keys.length][];
        for (int i = 0; i < keys.length; i++) {
            bkeys[i] = SafeEncoder.encode(keys[i]);
        }
        slmexpire(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), expireMillis, SafeEncoder.encode(serviceCode),
                bkeys);
    }

    /**
     * Slttl.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slttl(final String keyspace, final String uid, final String serviceCode, final String key) {
        slttl(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Slttl.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param value       the value
     */
    public void slttl(final String keyspace, final String uid, final String serviceCode, final String key,
                      final String value) {
        slttl(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), SafeEncoder.encode(value));
    }

    /**
     * Slvals.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void slvals(final String keyspace, final String uid, final String serviceCode) {
        slvals(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode));
    }

    /**
     * Slset.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param values       the values
     */
    public void slset(final String keyspace, final String uid, final String serviceCode, final String key,
                      final long expireMillis, final String... values) {
        byte[][] bvalues = new byte[values.length][];
        for (int i = 0; i < values.length; i++) {
            bvalues[i] = SafeEncoder.encode(values[i]);
        }
        slset(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), expireMillis, bvalues);
    }

    /**
     * Slreplace.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param expireTime  the expire time
     * @param oldValue    the old value
     * @param newValue    the new value
     */
    public void slreplace(final String keyspace, final String uid, final String serviceCode, final String key,
                          final long expireTime, final String oldValue, final String newValue) {
        slreplace(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), expireTime, SafeEncoder.encode(oldValue), SafeEncoder.encode(newValue));
    }

    // Hashes of Sets ///////////////

    /**
     * Ssget.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void ssget(final String keyspace, final String uid, final String serviceCode, final String key) {
        ssget(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Ssmget.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param keys        the keys
     */
    public void ssmget(final String keyspace, final String uid, final String serviceCode, final String... keys) {
        final byte[][] bkeys = new byte[keys.length][];
        for (int i = 0; i < keys.length; i++) {
            bkeys[i] = SafeEncoder.encode(keys[i]);
        }
        ssmget(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode), bkeys);
    }

    /**
     * Sskeys.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void sskeys(final String keyspace, final String uid) {
        sskeys(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid));
    }

    /**
     * Sskeys.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void sskeys(final String keyspace, final String uid, final String serviceCode) {
        sskeys(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode));
    }

    /**
     * Sskeys.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void sskeys(final String keyspace, final String uid, final String serviceCode, final String key) {
        sskeys(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Ssadd.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param values       the values
     */
    public void ssadd(final String keyspace, final String uid, final String serviceCode, final String key,
                      final long expireMillis, final String... values) {
        final byte[][] bvalues = new byte[values.length][];
        for (int i = 0; i < values.length; i++) {
            bvalues[i] = SafeEncoder.encode(values[i]);
        }
        ssadd(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), expireMillis, bvalues);
    }

    public void ssaddAt(final String keyspace, final String uid, final String serviceCode, final String key,
                        final long unixTime, final String... values) {
        final byte[][] bvalues = new byte[values.length][];
        for (int i = 0; i < values.length; i++) {
            bvalues[i] = SafeEncoder.encode(values[i]);
        }
        ssaddAt(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), unixTime, bvalues);
    }

    /**
     * Ssdel.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void ssdel(final String keyspace, final String uid) {
        ssdel(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid));
    }

    /**
     * Ssrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void ssrem(final String keyspace, final String uid, final String serviceCode) {
        ssrem(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode));
    }

    /**
     * Ssrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void ssrem(final String keyspace, final String uid, final String serviceCode, final String key) {
        ssrem(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Ssrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param value       the value
     */
    public void ssrem(final String keyspace, final String uid, final String serviceCode, final String key,
                      final String value) {
        ssrem(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), SafeEncoder.encode(value));
    }

    /**
     * Ssrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param values      the values
     */
    public void ssrem(final String keyspace, final String uid, final String serviceCode, final String key,
                      final String[] values) {
        final byte[][] bvalues = new byte[values.length][];
        for (int i = 0; i < values.length; i++) {
            bvalues[i] = SafeEncoder.encode(values[i]);
        }
        ssrem(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), bvalues);
    }

    /**
     * Sscount.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void sscount(final String keyspace, final String uid) {
        sscount(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid));
    }

    /**
     * Sscount.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void sscount(final String keyspace, final String uid, final String serviceCode) {
        sscount(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode));
    }

    /**
     * Sscount.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void sscount(final String keyspace, final String uid, final String serviceCode, final String key) {
        sscount(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Ssexists.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void ssexists(final String keyspace, final String uid, final String serviceCode, final String key) {
        ssexists(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Ssexists.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param value       the value
     */
    public void ssexists(final String keyspace, final String uid, final String serviceCode, final String key,
                         final String value) {
        ssexists(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), SafeEncoder.encode(value));
    }

    /**
     * Ssexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param expireMillis the expire seconds
     */
    public void ssexpire(final String keyspace, final String uid, long expireMillis) {
        ssexpire(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), expireMillis);
    }

    /**
     * Ssexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param expireMillis the expire seconds
     */
    public void ssexpire(final String keyspace, final String uid, final String serviceCode, long expireMillis) {
        ssexpire(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode), expireMillis);
    }

    /**
     * Ssexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     */
    public void ssexpire(final String keyspace, final String uid, final String serviceCode, final String key,
                         long expireMillis) {
        ssexpire(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), expireMillis);
    }

    /**
     * Ssexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param value        the value
     * @param expireMillis the expire millis
     */
    public void ssexpire(final String keyspace, final String uid, final String serviceCode, final String key,
                         final String value, final long expireMillis) {
        ssexpire(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), SafeEncoder.encode(value), expireMillis);
    }

    /**
     * Ssttl.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void ssttl(final String keyspace, final String uid, final String serviceCode, final String key) {
        ssttl(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key));
    }

    /**
     * Ssttl.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param value       the value
     */
    public void ssttl(final String keyspace, final String uid, final String serviceCode, final String key,
                      final String value) {
        ssttl(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), SafeEncoder.encode(value));
    }

    /**
     * Ssset.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param values       the values
     */
    public void ssset(final String keyspace, final String uid, final String serviceCode, final String key,
                      final long expireMillis, final String... values) {
        final byte[][] bvalues = new byte[values.length][];
        for (int i = 0; i < values.length; i++) {
            bvalues[i] = SafeEncoder.encode(values[i]);
        }
        ssset(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode),
                SafeEncoder.encode(key), expireMillis, bvalues);
    }

    /**
     * Ssvals.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void ssvals(final String keyspace, final String uid, final String serviceCode) {
        ssvals(SafeEncoder.encode(keyspace), SafeEncoder.encode(uid), SafeEncoder.encode(serviceCode));
    }
}