[
  {
    "path": ".gitignore",
    "content": ".classpath\n.project\n.settings\ntarget\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: java\n\njdk:\n  - oraclejdk8\n\ninstall: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true -B -V\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 CodisLabs.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# Jodis - Java client for codis\n\n[![Build Status](https://travis-ci.org/CodisLabs/jodis.svg)](https://travis-ci.org/CodisLabs/jodis)\n\nJodis is a java client for codis based on [Jedis](https://github.com/xetorthio/jedis) and [Curator](http://curator.apache.org/).\n\n# Features\n- Use a round robin policy to balance load to multiple codis proxies.\n- Detect proxy online and offline automatically.\n\n# How to use\nAdd this to your pom.xml. We deploy jodis to https://oss.sonatype.org.\n```xml\n<dependency>\n  <groupId>io.codis.jodis</groupId>\n  <artifactId>jodis</artifactId>\n  <version>0.5.1</version>\n</dependency>\n```\nTo use it for Codis2.x:\n```java\nJedisResourcePool jedisPool = RoundRobinJedisPool.create()\n        .curatorClient(\"zkserver:2181\", 30000).zkProxyDir(\"/zk/codis/db_xxx/proxy\").build();\ntry (Jedis jedis = jedisPool.getResource()) {\n    jedis.set(\"foo\", \"bar\");\n    String value = jedis.get(\"foo\");\n    System.out.println(value);\n}\n```\nOr for Codis3.x with `jodis_compatible=false`:\n```java\nJedisResourcePool jedisPool = RoundRobinJedisPool.create()\n        .curatorClient(\"zkserver:2181\", 30000).zkProxyDir(\"/jodis/xxx\").build();\ntry (Jedis jedis = jedisPool.getResource()) {\n    jedis.set(\"foo\", \"bar\");\n    String value = jedis.get(\"foo\");\n    System.out.println(value);\n}\n```\nNote: JDK8 is required to use and build jodis, as JDK7 has been EOL since May 2015.\n"
  },
  {
    "path": "pom.xml",
    "content": "<!--\n Copyright (c) 2014 CodisLabs.\n\n Permission is hereby granted, free of charge, to any person obtaining\n a copy of this software and associated documentation files (the\n \"Software\"), to deal in the Software without restriction, including\n without limitation the rights to use, copy, modify, merge, publish,\n distribute, sublicense, and/or sell copies of the Software, and to\n permit persons to whom the Software is furnished to do so, subject to\n the following conditions:\n\n The above copyright notice and this permission notice shall be\n included in all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n-->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>io.codis.jodis</groupId>\n    <artifactId>jodis</artifactId>\n    <version>0.6.0-SNAPSHOT</version>\n    <packaging>jar</packaging>\n    <name>${project.artifactId}</name>\n    <description>Round Robin jedis pool for codis</description>\n    <url>https://github.com/CodisLabs/jodis</url>\n    <scm>\n        <connection>scm:git:ssh://github.com:CodisLabs/jodis.git</connection>\n        <developerConnection>scm:git:ssh://github.com:CodisLabs/jodis.git</developerConnection>\n        <url>ssh://github.com:CodisLabs/codis.git</url>\n        <tag>HEAD</tag>\n    </scm>\n    <properties>\n        <java.version>1.7</java.version>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>\n        <maven.compile.encoding>UTF-8</maven.compile.encoding>\n        <zookeeper.version>3.4.11</zookeeper.version>\n        <curator.version>4.0.1</curator.version>\n        <curator-test.version>2.12.0</curator-test.version>\n        <jedis.version>2.9.0</jedis.version>\n        <jackson.version>2.9.5</jackson.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>redis.clients</groupId>\n            <artifactId>jedis</artifactId>\n            <version>${jedis.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.zookeeper</groupId>\n            <artifactId>zookeeper</artifactId>\n            <version>${zookeeper.version}</version>\n        </dependency>  \n        <dependency>\n            <groupId>org.apache.curator</groupId>\n            <artifactId>curator-recipes</artifactId>\n            <version>${curator.version}</version>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.apache.zookeeper</groupId>\n                    <artifactId>zookeeper</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n            <version>1.7.12</version>\n        </dependency>\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n            <version>23.0</version>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n            <version>${jackson.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <version>4.12</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-simple</artifactId>\n            <version>1.7.12</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.curator</groupId>\n            <artifactId>curator-test</artifactId>\n            <version>${curator-test.version}</version>\n            <scope>test</scope>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.apache.zookeeper</groupId>\n                    <artifactId>zookeeper</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n    </dependencies>\n    <distributionManagement>\n        <snapshotRepository>\n            <id>ossrh</id>\n            <url>https://oss.sonatype.org/content/repositories/snapshots</url>\n        </snapshotRepository>\n        <repository>\n            <id>ossrh</id>\n            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>\n        </repository>\n    </distributionManagement>\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>2.5.1</version>\n                <configuration>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-source-plugin</artifactId>\n                <version>2.4</version>\n                <executions>\n                    <execution>\n                        <id>attach-sources</id>\n                        <goals>\n                            <goal>jar-no-fork</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-javadoc-plugin</artifactId>\n                <version>2.10.3</version>\n                <configuration>\n                    <additionalparam>-Xdoclint:none</additionalparam>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>attach-javadocs</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-gpg-plugin</artifactId>\n                <version>1.5</version>\n                <executions>\n                    <execution>\n                        <id>sign-artifacts</id>\n                        <phase>verify</phase>\n                        <goals>\n                            <goal>sign</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.sonatype.plugins</groupId>\n                <artifactId>nexus-staging-maven-plugin</artifactId>\n                <version>1.6.7</version>\n                <extensions>true</extensions>\n                <configuration>\n                    <serverId>ossrh</serverId>\n                    <nexusUrl>https://oss.sonatype.org/</nexusUrl>\n                    <autoReleaseAfterClose>false</autoReleaseAfterClose>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.jacoco</groupId>\n                <artifactId>jacoco-maven-plugin</artifactId>\n                <version>0.7.5.201505241946</version>\n                <executions>\n                    <execution>\n                        <id>default-prepare-agent</id>\n                        <goals>\n                            <goal>prepare-agent</goal>\n                        </goals>\n                    </execution>\n                    <execution>\n                        <id>default-report</id>\n                        <phase>prepare-package</phase>\n                        <goals>\n                            <goal>report</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>2.18.1</version>\n                <configuration>\n                    <forkMode>always</forkMode>\n                    <redirectTestOutputToFile>true</redirectTestOutputToFile>\n                    <argLine>${argLine} -Dfile.encoding=UTF-8</argLine>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-report-plugin</artifactId>\n                <version>2.18.1</version>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-deploy-plugin</artifactId>\n                <version>2.8.2</version>\n                <executions>\n                    <execution>\n                        <id>deploy</id>\n                        <phase>deploy</phase>\n                        <goals>\n                            <goal>deploy</goal>\n                        </goals>\n                    </execution>\n                    <execution>\n                        <id>default-deploy</id>\n                        <configuration>\n                            <skip>true</skip>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-site-plugin</artifactId>\n                <version>3.4</version>\n                <configuration>\n                    <reportPlugins>\n                        <plugin>\n                            <groupId>org.codehaus.mojo</groupId>\n                            <artifactId>findbugs-maven-plugin</artifactId>\n                        </plugin>\n                        <plugin>\n                            <groupId>org.jacoco</groupId>\n                            <artifactId>jacoco-maven-plugin</artifactId>\n                        </plugin>\n                    </reportPlugins>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>findbugs-maven-plugin</artifactId>\n                <version>3.0.2</version>\n                <configuration>\n                    <xmlOutput>true</xmlOutput>\n                    <xmlOutputDirectory>target/findbugs</xmlOutputDirectory>\n                    <findbugsXmlOutputDirectory>target/findbugs</findbugsXmlOutputDirectory>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n    <licenses>\n        <license>\n            <name>MIT License</name>\n            <url>http://opensource.org/licenses/MIT</url>\n            <distribution>repo</distribution>\n        </license>\n    </licenses>\n    <developers>\n        <developer>\n            <id>apache9</id>\n            <name>Apache9</name>\n            <email>palomino219@gmail.com</email>\n            <roles>\n                <role>developer</role>\n            </roles>\n            <timezone>+8</timezone>\n        </developer>\n    </developers>\n</project>\n"
  },
  {
    "path": "src/main/java/io/codis/jodis/BoundedExponentialBackoffRetryUntilElapsed.java",
    "content": "/**\n * @(#)BoundedExponentialBackoffRetryUntilElapsed.java, 2014-12-2. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npackage io.codis.jodis;\n\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\n\nimport org.apache.curator.RetryPolicy;\nimport org.apache.curator.RetrySleeper;\n\n/**\n * Similar to {@link org.apache.curator.retry.BoundedExponentialBackoffRetry},\n * but limit the retry elapsed time, not retry number.\n * \n * @author Apache9\n */\npublic class BoundedExponentialBackoffRetryUntilElapsed implements RetryPolicy {\n\n    private final int baseSleepTimeMs;\n\n    private final int maxSleepTimeMs;\n\n    private final long maxElapsedTimeMs;\n\n    /**\n     * @param baseSleepTimeMs\n     *            initial amount of time to wait between retries\n     * @param maxSleepTimeMs\n     *            max time in ms to sleep on each retry\n     * @param maxElapsedTimeMs\n     *            total time in ms to retry\n     */\n    public BoundedExponentialBackoffRetryUntilElapsed(int baseSleepTimeMs, int maxSleepTimeMs,\n            long maxElapsedTimeMs) {\n        this.baseSleepTimeMs = baseSleepTimeMs;\n        this.maxSleepTimeMs = maxSleepTimeMs;\n        if (maxElapsedTimeMs < 0) {\n            this.maxElapsedTimeMs = Long.MAX_VALUE;\n        } else {\n            this.maxElapsedTimeMs = maxElapsedTimeMs;\n        }\n    }\n\n    private long getSleepTimeMs(int retryCount, long elapsedTimeMs) {\n        return Math.min(\n                maxSleepTimeMs,\n                (long) baseSleepTimeMs\n                        * Math.max(\n                                1,\n                                ThreadLocalRandom.current().nextInt(\n                                        1 << Math.min(30, retryCount + 1))));\n    }\n\n    @Override\n    public boolean allowRetry(int retryCount, long elapsedTimeMs, RetrySleeper sleeper) {\n        if (elapsedTimeMs >= maxElapsedTimeMs) {\n            return false;\n        }\n        long sleepTimeMs = Math.min(maxElapsedTimeMs - elapsedTimeMs,\n                getSleepTimeMs(retryCount, elapsedTimeMs));\n        try {\n            sleeper.sleepFor(sleepTimeMs, TimeUnit.MILLISECONDS);\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            return false;\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/codis/jodis/CodisProxyInfo.java",
    "content": "/**\n * @(#)CodisProxyInfo.java, 2015-10-14. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npackage io.codis.jodis;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\n\n/**\n * We only care about the proxy address and if it is online.\n * \n * @author Apache9\n */\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class CodisProxyInfo {\n\n    private String addr;\n\n    private String state;\n\n    public String getAddr() {\n        return addr;\n    }\n\n    public void setAddr(String addr) {\n        this.addr = addr;\n    }\n\n    public String getState() {\n        return state;\n    }\n\n    public void setState(String state) {\n        this.state = state;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/codis/jodis/JedisPoolAdaptor.java",
    "content": "/**\n * @(#)JedisPoolAdaptor.java, 2014-12-2. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npackage io.codis.jodis;\n\nimport java.net.URI;\n\nimport org.apache.commons.pool2.impl.GenericObjectPoolConfig;\n\nimport redis.clients.jedis.JedisPool;\n\n/**\n * Adaptor of JedisPool to make writing testcase easier.\n * \n * @author Apache9\n */\npublic class JedisPoolAdaptor extends JedisPool implements JedisResourcePool {\n\n    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout,\n            String password, int database, String clientName) {\n        super(poolConfig, host, port, timeout, password, database, clientName);\n    }\n\n    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout,\n            String password, int database) {\n        super(poolConfig, host, port, timeout, password, database);\n    }\n\n    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout,\n            String password) {\n        super(poolConfig, host, port, timeout, password);\n    }\n\n    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout) {\n        super(poolConfig, host, port, timeout);\n    }\n\n    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port) {\n        super(poolConfig, host, port);\n    }\n\n    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host) {\n        super(poolConfig, host);\n    }\n\n    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, URI uri, int timeout) {\n        super(poolConfig, uri, timeout);\n    }\n\n    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, URI uri) {\n        super(poolConfig, uri);\n    }\n\n    public JedisPoolAdaptor(String host, int port) {\n        super(host, port);\n    }\n\n    public JedisPoolAdaptor(String host) {\n        super(host);\n    }\n\n    public JedisPoolAdaptor(URI uri, int timeout) {\n        super(uri, timeout);\n    }\n\n    public JedisPoolAdaptor(URI uri) {\n        super(uri);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/codis/jodis/JedisResourcePool.java",
    "content": "/**\n * @(#)JedisResourcePool.java, 2014-12-2. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npackage io.codis.jodis;\n\nimport java.io.Closeable;\n\nimport redis.clients.jedis.Jedis;\n\n/**\n * Describe a pool which we can acquire jedis instance.\n * \n * @author Apache9\n */\npublic interface JedisResourcePool extends Closeable {\n\n    /**\n     * Get a jedis instance from pool.\n     * <p>\n     * We do not have a returnResource method, just close the jedis instance\n     * returned directly.\n     */\n    Jedis getResource();\n}\n"
  },
  {
    "path": "src/main/java/io/codis/jodis/RoundRobinJedisPool.java",
    "content": "/**\n * @(#)RoundRobinJedisPool.java, 2014-11-30.\n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npackage io.codis.jodis;\n\nimport static org.apache.curator.framework.imps.CuratorFrameworkState.LATENT;\nimport static org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode.BUILD_INITIAL_CACHE;\nimport static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_ADDED;\nimport static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED;\nimport static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_UPDATED;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.apache.curator.framework.CuratorFramework;\nimport org.apache.curator.framework.CuratorFrameworkFactory;\nimport org.apache.curator.framework.recipes.cache.ChildData;\nimport org.apache.curator.framework.recipes.cache.PathChildrenCache;\nimport org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;\nimport org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.Maps;\nimport com.google.common.collect.Sets;\nimport com.google.common.io.Closeables;\n\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisPool;\nimport redis.clients.jedis.JedisPoolConfig;\nimport redis.clients.jedis.Protocol;\nimport redis.clients.jedis.exceptions.JedisException;\n\n/**\n * A round robin connection pool for connecting multiple codis proxies based on\n * Jedis and Curator.\n * \n * @author Apache9\n * @see https://github.com/xetorthio/jedis\n * @see http://curator.apache.org/\n */\npublic class RoundRobinJedisPool implements JedisResourcePool {\n\n    private static final Logger LOG = LoggerFactory.getLogger(RoundRobinJedisPool.class);\n\n    private static final ObjectMapper MAPPER = new ObjectMapper();\n\n    private static final String CODIS_PROXY_STATE_ONLINE = \"online\";\n\n    private static final int CURATOR_RETRY_BASE_SLEEP_MS = 100;\n\n    private static final int CURATOR_RETRY_MAX_SLEEP_MS = 30 * 1000;\n\n    private static final long DELAY_BEFORE_CLOSING_POOL = 10000; // milliseconds\n\n    private static final ImmutableSet<PathChildrenCacheEvent.Type> RESET_TYPES = Sets\n            .immutableEnumSet(CHILD_ADDED, CHILD_UPDATED, CHILD_REMOVED);\n\n    private final CuratorFramework curatorClient;\n\n    private final boolean closeCurator;\n\n    private final PathChildrenCache watcher;\n\n    private static final class PooledObject {\n        public final String addr;\n\n        public final JedisPool pool;\n\n        public PooledObject(String addr, JedisPool pool) {\n            this.addr = addr;\n            this.pool = pool;\n        }\n\n        public Jedis getResource() {\n            return pool.getResource();\n        }\n\n        public void close() {\n            try {\n                pool.close();\n                LOG.info(\"Connection pool to {} closed\", addr);\n            } catch (Exception e) {\n                LOG.error(\"Error closing connection pool to \" + addr, e);\n            }\n        }\n    }\n\n    private volatile ImmutableList<PooledObject> pools = ImmutableList.of();\n\n    private final AtomicInteger nextIdx = new AtomicInteger(-1);\n\n    private final JedisPoolConfig poolConfig;\n\n    private final int connectionTimeoutMs;\n\n    private final int soTimeoutMs;\n\n    private final String password;\n\n    private final int database;\n\n    private final String clientName;\n\n    private final ScheduledThreadPoolExecutor jedisPoolClosingExecutor =\n            new ScheduledThreadPoolExecutor(1);\n\n    private RoundRobinJedisPool(CuratorFramework curatorClient, boolean closeCurator,\n            String zkProxyDir, JedisPoolConfig poolConfig, int connectionTimeoutMs, int soTimeoutMs,\n            String password, int database, String clientName) {\n        this.poolConfig = poolConfig;\n        this.connectionTimeoutMs = connectionTimeoutMs;\n        this.soTimeoutMs = soTimeoutMs;\n        this.password = password;\n        this.database = database;\n        this.clientName = clientName;\n        this.curatorClient = curatorClient;\n        this.closeCurator = closeCurator;\n        watcher = new PathChildrenCache(curatorClient, zkProxyDir, true);\n        watcher.getListenable().addListener(new PathChildrenCacheListener() {\n\n            private void logEvent(PathChildrenCacheEvent event) {\n                StringBuilder msg = new StringBuilder(\"Receive child event: \");\n                msg.append(\"type=\").append(event.getType());\n                ChildData data = event.getData();\n                if (data != null) {\n                    msg.append(\", path=\").append(data.getPath());\n                    msg.append(\", stat=\").append(data.getStat());\n                    if (data.getData() != null) {\n                        msg.append(\", bytes length=\").append(data.getData().length);\n                    } else {\n                        msg.append(\", no bytes\");\n                    }\n                } else {\n                    msg.append(\", no data\");\n                }\n                LOG.info(msg.toString());\n            }\n\n            @Override\n            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)\n                    throws Exception {\n                synchronized (RoundRobinJedisPool.this) {\n                    logEvent(event);\n                    if (RESET_TYPES.contains(event.getType())) {\n                        resetPools();\n                    }\n                }\n            }\n        });\n        synchronized (this) {\n            try {\n                watcher.start(BUILD_INITIAL_CACHE);\n            } catch (Exception e) {\n                close();\n                throw new JedisException(e);\n            }\n            resetPools();\n        }\n    }\n\n    private void resetPools() {\n        ImmutableList<PooledObject> pools = this.pools;\n        Map<String, PooledObject> addr2Pool = Maps.newHashMapWithExpectedSize(pools.size());\n        for (PooledObject pool: pools) {\n            addr2Pool.put(pool.addr, pool);\n        }\n        ImmutableList.Builder<PooledObject> builder = ImmutableList.builder();\n        for (ChildData childData : watcher.getCurrentData()) {\n            try {\n                CodisProxyInfo proxyInfo = MAPPER.readValue(childData.getData(),\n                        CodisProxyInfo.class);\n                if (!CODIS_PROXY_STATE_ONLINE.equals(proxyInfo.getState())) {\n                    continue;\n                }\n                String addr = proxyInfo.getAddr();\n                PooledObject pool = addr2Pool.remove(addr);\n                if (pool == null) {\n                    String[] hostAndPort = addr.split(\":\");\n                    String host = hostAndPort[0];\n                    int port = Integer.parseInt(hostAndPort[1]);\n                    pool = new PooledObject(addr,\n                            new JedisPool(poolConfig, host, port, connectionTimeoutMs, soTimeoutMs,\n                                    password, database, clientName, false, null, null, null));\n                    LOG.info(\"Add new proxy: \" + addr);\n                }\n                builder.add(pool);\n            } catch (Exception e) {\n                LOG.warn(\"parse \" + childData.getPath() + \" failed\", e);\n            }\n        }\n        this.pools = builder.build();\n        for (final PooledObject pool: addr2Pool.values()) {\n            LOG.info(\"Remove proxy: \" + pool.addr);\n            jedisPoolClosingExecutor.schedule(new Runnable() {\n                @Override\n                public void run() {\n                    pool.close();\n                }\n            }, DELAY_BEFORE_CLOSING_POOL, TimeUnit.MILLISECONDS);\n        }\n    }\n\n    @Override\n    public Jedis getResource() {\n        ImmutableList<PooledObject> pools = this.pools;\n        if (pools.isEmpty()) {\n            throw new JedisException(\"Proxy list empty\");\n        }\n        for (;;) {\n            int current = nextIdx.get();\n            int next = current >= pools.size() - 1 ? 0 : current + 1;\n            if (nextIdx.compareAndSet(current, next)) {\n                return pools.get(next).getResource();\n            }\n        }\n    }\n\n    @Override\n    public void close() {\n        try {\n            Closeables.close(watcher, true);\n        } catch (IOException e) {\n            throw new AssertionError(\"IOException should not have been thrown\", e);\n        }\n        if (closeCurator) {\n            curatorClient.close();\n        }\n        List<PooledObject> pools = this.pools;\n        this.pools = ImmutableList.of();\n        for (PooledObject pool: pools) {\n            pool.close();\n        }\n        jedisPoolClosingExecutor.shutdown();\n    }\n\n    /**\n     * Create a {@link RoundRobinJedisPool} using the fluent style api.\n     * \n     * @return\n     */\n    public static Builder create() {\n        return new Builder();\n    }\n\n    public static final class Builder {\n\n        private CuratorFramework curatorClient;\n\n        private boolean closeCurator;\n\n        private String zkProxyDir;\n\n        private String zkAddr;\n\n        private int zkSessionTimeoutMs;\n\n        private JedisPoolConfig poolConfig;\n\n        private int connectionTimeoutMs = Protocol.DEFAULT_TIMEOUT;\n\n        private int soTimeoutMs = Protocol.DEFAULT_TIMEOUT;\n\n        private String password;\n\n        private int database = Protocol.DEFAULT_DATABASE;\n\n        private String clientName;\n\n        private Builder() {}\n\n        /**\n         * Set curator client.\n         * \n         * @param curatorClient\n         *            the client to be used\n         * @param closeCurator\n         *            whether to close curator client while closing pool\n         */\n        public Builder curatorClient(CuratorFramework curatorClient, boolean closeCurator) {\n            this.curatorClient = curatorClient;\n            this.closeCurator = closeCurator;\n            return this;\n        }\n\n        /**\n         * Set codis proxy path on zk.\n         * \n         * @param zkProxyDir\n         *            the codis proxy dir on ZooKeeper. e.g.,\n         *            \"/zk/codis/db_xxx/proxy\"\n         */\n        public Builder zkProxyDir(String zkProxyDir) {\n            this.zkProxyDir = zkProxyDir;\n            return this;\n        }\n\n        /**\n         * Set curator client.\n         * <p>\n         * We will create curator client based on these parameters and close it\n         * while closing pool.\n         * \n         * @param zkAddr\n         *            ZooKeeper connect string. e.g., \"zk1:2181\"\n         * @param zkSessionTimeoutMs\n         *            ZooKeeper session timeout in milliseconds\n         */\n        public Builder curatorClient(String zkAddr, int zkSessionTimeoutMs) {\n            this.zkAddr = zkAddr;\n            this.zkSessionTimeoutMs = zkSessionTimeoutMs;\n            return this;\n        }\n\n        /**\n         * Set jedis pool config.\n         */\n        public Builder poolConfig(JedisPoolConfig poolConfig) {\n            this.poolConfig = poolConfig;\n            return this;\n        }\n\n        /**\n         * Set jedis pool timeout in milliseconds.\n         * <p>\n         * We will set connectionTimeoutMs and soTimeoutMs both.\n         * \n         * @param timeoutMs\n         *            timeout is milliseconds\n         */\n        public Builder timeoutMs(int timeoutMs) {\n            this.connectionTimeoutMs = this.soTimeoutMs = timeoutMs;\n            return this;\n        }\n\n        /**\n         * Set jedis pool connection timeout in milliseconds.\n         * \n         * @param connectionTimeoutMs\n         *            timeout is milliseconds\n         */\n        public Builder connectionTimeoutMs(int connectionTimeoutMs) {\n            this.connectionTimeoutMs = connectionTimeoutMs;\n            return this;\n        }\n\n        /**\n         * Set jedis pool connection soTimeout in milliseconds.\n         * \n         * @param soTimeoutMs\n         *            timeout is milliseconds\n         */\n        public Builder soTimeoutMs(int soTimeoutMs) {\n            this.soTimeoutMs = soTimeoutMs;\n            return this;\n        }\n\n        /**\n         * Set password.\n         */\n        public Builder password(String password) {\n            this.password = password;\n            return this;\n        }\n\n        /**\n         * Set redis database.\n         */\n        public Builder database(int database) {\n            this.database = database;\n            return this;\n        }\n\n        /**\n         * Set redis client name.\n         */\n        public Builder clientName(String clientName) {\n            this.clientName = clientName;\n            return this;\n        }\n\n        private void validate() {\n            Preconditions.checkNotNull(zkProxyDir, \"zkProxyDir can not be null\");\n            if (curatorClient == null) {\n                Preconditions.checkNotNull(zkAddr, \"zk client can not be null\");\n                curatorClient = CuratorFrameworkFactory.builder().connectString(zkAddr)\n                        .sessionTimeoutMs(zkSessionTimeoutMs)\n                        .retryPolicy(new BoundedExponentialBackoffRetryUntilElapsed(\n                                CURATOR_RETRY_BASE_SLEEP_MS, CURATOR_RETRY_MAX_SLEEP_MS, -1L))\n                        .build();\n                curatorClient.start();\n                closeCurator = true;\n            } else {\n                // we need to get the initial data so client must be started\n                if (curatorClient.getState() == LATENT) {\n                    curatorClient.start();\n                }\n            }\n            if (poolConfig == null) {\n                poolConfig = new JedisPoolConfig();\n            }\n        }\n\n        /**\n         * Create the {@link RoundRobinJedisPool}.\n         */\n        public RoundRobinJedisPool build() {\n            validate();\n            return new RoundRobinJedisPool(curatorClient, closeCurator, zkProxyDir, poolConfig,\n                    connectionTimeoutMs, soTimeoutMs, password, database, clientName);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/codis/jodis/RedisServer.java",
    "content": "/**\n * @(#)RedisServer.java, 2014-11-30. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npackage io.codis.jodis;\n\nimport java.io.IOException;\n\n/**\n * @author Apache9\n */\npublic class RedisServer {\n\n    private final ProcessBuilder builder;\n\n    private Process process;\n\n    public RedisServer(int port) {\n        builder = new ProcessBuilder().command(\"redis-server\", \"--port\",\n                Long.toString(port)).inheritIO();\n    }\n\n    public void start() throws IOException {\n        process = builder.start();\n    }\n\n    public void stop() {\n        if (process != null) {\n            process.destroy();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/codis/jodis/TestBoundedExponentialBackoffRetryUntilElapsed.java",
    "content": "/**\n * @(#)TestBoundedExponentialBackoffRetryUntilElapsed.java, 2014-12-2. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npackage io.codis.jodis;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\n\nimport org.apache.curator.RetrySleeper;\nimport org.junit.Test;\n\n/**\n * @author Apache9\n */\npublic class TestBoundedExponentialBackoffRetryUntilElapsed {\n\n    private static final class FakeRetrySleeper implements RetrySleeper {\n\n        public long sleepTimeMs;\n\n        @Override\n        public void sleepFor(long time, TimeUnit unit) {\n            this.sleepTimeMs = unit.toMillis(time);\n        }\n    }\n\n    @Test\n    public void test() {\n        FakeRetrySleeper sleeper = new FakeRetrySleeper();\n        BoundedExponentialBackoffRetryUntilElapsed r = new BoundedExponentialBackoffRetryUntilElapsed(\n                10, 2000, 60000);\n        for (int i = 0; i < 100; i++) {\n            assertTrue(r.allowRetry(i, ThreadLocalRandom.current().nextInt(60000), sleeper));\n            System.out.println(sleeper.sleepTimeMs);\n            assertTrue(sleeper.sleepTimeMs <= 2000);\n        }\n        assertTrue(r.allowRetry(1000, 59900, sleeper));\n        System.out.println(sleeper.sleepTimeMs);\n        assertTrue(sleeper.sleepTimeMs <= 100);\n        sleeper.sleepTimeMs = -1L;\n        assertFalse(r.allowRetry(1, 60000, sleeper));\n        assertEquals(-1L, sleeper.sleepTimeMs);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/codis/jodis/TestRoundRobinJedisPool.java",
    "content": "/**\n * @(#)TestRoundRobinJedisPool.java, 2014-12-1. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npackage io.codis.jodis;\n\nimport static org.junit.Assert.assertEquals;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.ServerSocket;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.SimpleFileVisitor;\nimport java.nio.file.attribute.BasicFileAttributes;\n\nimport org.apache.curator.test.TestingServer;\nimport org.apache.zookeeper.CreateMode;\nimport org.apache.zookeeper.KeeperException;\nimport org.apache.zookeeper.ZooDefs;\nimport org.apache.zookeeper.ZooKeeper;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport com.google.common.io.Closeables;\n\nimport io.codis.jodis.RoundRobinJedisPool;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.exceptions.JedisException;\n\n/**\n * @author Apache9\n */\npublic class TestRoundRobinJedisPool {\n\n    private ObjectMapper mapper = new ObjectMapper();\n\n    private int zkPort;\n\n    private File testDir = new File(getClass().getName());\n\n    private TestingServer zkServer;\n\n    private int redisPort1;\n\n    private RedisServer redis1;\n\n    private int redisPort2;\n\n    private RedisServer redis2;\n\n    private Jedis jedis1;\n\n    private Jedis jedis2;\n\n    private String zkProxyDir = \"/\" + getClass().getName();\n\n    private RoundRobinJedisPool jodisPool;\n\n    private static int probeFreePort() throws IOException {\n        try (ServerSocket ss = new ServerSocket(0)) {\n            ss.setReuseAddress(true);\n            return ss.getLocalPort();\n        }\n    }\n\n    private static void waitUntilRedisStarted(int port) throws InterruptedException {\n        for (;;) {\n            try (Jedis jedis = new Jedis(\"127.0.0.1\", port)) {\n                if (\"PONG\".equals(jedis.ping())) {\n                    break;\n                }\n            } catch (JedisException e) {}\n            Thread.sleep(100);\n        }\n    }\n\n    private void deleteDirectory(File directory) throws IOException {\n        if (!directory.exists()) {\n            return;\n        }\n        Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<Path>() {\n\n            @Override\n            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)\n                    throws IOException {\n                Files.delete(file);\n                return FileVisitResult.CONTINUE;\n            }\n\n            @Override\n            public FileVisitResult postVisitDirectory(Path dir, IOException exc)\n                    throws IOException {\n                Files.delete(dir);\n                return FileVisitResult.CONTINUE;\n            }\n\n        });\n    }\n\n    private void addNode(String name, int port, String state)\n            throws IOException, InterruptedException, KeeperException {\n        ZooKeeper zk = new ZooKeeper(\"localhost:\" + zkPort, 5000, null);\n        try {\n            if (zk.exists(zkProxyDir, null) == null) {\n                zk.create(zkProxyDir, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);\n            }\n            ObjectNode node = mapper.createObjectNode();\n            node.put(\"addr\", \"127.0.0.1:\" + port);\n            node.put(\"state\", state);\n            zk.create(zkProxyDir + \"/\" + name, mapper.writer().writeValueAsBytes(node),\n                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);\n        } finally {\n            zk.close();\n        }\n    }\n\n    private void removeNode(String name) throws InterruptedException, KeeperException, IOException {\n        ZooKeeper zk = new ZooKeeper(\"localhost:\" + zkPort, 5000, null);\n        try {\n            zk.delete(zkProxyDir + \"/\" + name, -1);\n        } finally {\n            zk.close();\n        }\n    }\n\n    @Before\n    public void setUp() throws Exception {\n        deleteDirectory(testDir);\n        testDir.mkdirs();\n        zkServer = new TestingServer(-1, testDir, true);\n        zkPort = zkServer.getPort();\n        redisPort1 = probeFreePort();\n        redis1 = new RedisServer(redisPort1);\n        redis1.start();\n        waitUntilRedisStarted(redisPort1);\n        redisPort2 = probeFreePort();\n        redis2 = new RedisServer(redisPort2);\n        redis2.start();\n        waitUntilRedisStarted(redisPort2);\n\n        jedis1 = new Jedis(\"localhost\", redisPort1);\n        jedis2 = new Jedis(\"localhost\", redisPort2);\n        addNode(\"node1\", redisPort1, \"online\");\n        jodisPool = RoundRobinJedisPool.create().curatorClient(\"localhost:\" + zkPort, 5000)\n                .zkProxyDir(zkProxyDir).build();\n    }\n\n    @After\n    public void tearDown() throws IOException {\n        Closeables.close(jodisPool, true);\n        Closeables.close(jedis1, true);\n        Closeables.close(jedis2, true);\n        if (redis1 != null) {\n            redis1.stop();\n        }\n        if (redis2 != null) {\n            redis2.stop();\n        }\n        if (zkServer != null) {\n            zkServer.stop();\n        }\n        deleteDirectory(testDir);\n    }\n\n    @Test\n    public void test() throws IOException, InterruptedException, KeeperException {\n        try (Jedis jedis = jodisPool.getResource()) {\n            jedis.set(\"k1\", \"v1\");\n        }\n        assertEquals(\"v1\", jedis1.get(\"k1\"));\n        // fake node\n        addNode(\"node2\", 12345, \"offline\");\n        Thread.sleep(3000);\n        try (Jedis jedis = jodisPool.getResource()) {\n            jedis.set(\"k2\", \"v2\");\n        }\n        assertEquals(\"v2\", jedis1.get(\"k2\"));\n\n        addNode(\"node3\", redisPort2, \"online\");\n        Thread.sleep(3000);\n        try (Jedis jedis = jodisPool.getResource()) {\n            jedis.set(\"k3\", \"v3\");\n        }\n        assertEquals(\"v3\", jedis2.get(\"k3\"));\n\n        removeNode(\"node1\");\n        Thread.sleep(3000);\n        try (Jedis jedis = jodisPool.getResource()) {\n            jedis.set(\"k4\", \"v4\");\n        }\n        assertEquals(\"v4\", jedis2.get(\"k4\"));\n    }\n}\n"
  },
  {
    "path": "wandoujia-LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Wandoujia Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  }
]