diff --git a/docs/src/reference/docbook/appendix/appendix-command-reference.xml b/docs/src/reference/docbook/appendix/appendix-command-reference.xml index a8c5aeb66..f00bd788e 100644 --- a/docs/src/reference/docbook/appendix/appendix-command-reference.xml +++ b/docs/src/reference/docbook/appendix/appendix-command-reference.xml @@ -24,7 +24,7 @@ BRPOPX BRPOPLPUSHX CLIENT GETNAME- - CLIENT KILL- + CLIENT KILLX CLIENT LIST- CLIENT SETNAME- CONFIG GETX diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java index 3eaba7053..b4f0addc2 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -2191,6 +2191,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection { return this.delegate.time(); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisServerCommands#killClient(byte[]) + */ + @Override + public void killClient(String host, int port) { + this.delegate.killClient(host, port); + } + /** * Specifies if pipelined and tx results should be deserialized to Strings. If false, results of * {@link #closePipeline()} and {@link #exec()} will be of the type returned by the underlying connection diff --git a/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java index d3f7fff39..3e34dffba 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java @@ -161,4 +161,14 @@ public interface RedisServerCommands { * @since 1.1 */ Long time(); + + /** + * Closes a given client connection identified by {@literal ip:port}. + * + * @param host of connection to close. + * @param port of connection to close + * @since 1.3 + */ + void killClient(String host, int port); + } diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java index 5c5216798..3af0cd6fa 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java @@ -2808,6 +2808,26 @@ public class JedisConnection implements RedisConnection { return Converters.toTimeMillis(serverTimeInformation.get(0), serverTimeInformation.get(1)); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisServerCommands#killClient(byte[]) + */ + @Override + public void killClient(String host, int port) { + + Assert.hasText(host, "Host for 'CLIENT KILL' must not be 'null' or 'empty'."); + + if (isQueueing() || isPipelined()) { + throw new UnsupportedOperationException("'CLIENT KILL' is not supported in transaction / pipline mode."); + } + + try { + this.jedis.clientKill(String.format("%s:%s", host, port)); + } catch (Exception e) { + throw convertJedisAccessException(e); + } + } + /** * Specifies if pipelined results should be converted to the expected data type. If false, results of * {@link #closePipeline()} and {@link #exec()} will be of the type returned by the Jedis driver diff --git a/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java b/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java index bfb8ae347..26bfb5d8e 100644 --- a/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/jredis/JredisConnection.java @@ -1185,4 +1185,9 @@ public class JredisConnection implements RedisConnection { public Long time() { throw new UnsupportedOperationException("The 'TIME' command is not supported by the JRedis driver."); } + + @Override + public void killClient(String host, int port) { + throw new UnsupportedOperationException("The 'CLIENT KILL' command is not supported by the JRedis driver."); + } } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java index ce7c628f1..4b1ac6a65 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java @@ -2903,6 +2903,23 @@ public class LettuceConnection implements RedisConnection { return Converters.toTimeMillis(new String(result.get(0)), new String(result.get(1))); } + @Override + public void killClient(String host, int port) { + + Assert.hasText(host, "Host for 'CLIENT KILL' must not be 'null' or 'empty'."); + + String client = String.format("%s:%s", host, port); + try { + if (isPipelined()) { + pipeline(new LettuceStatusResult(getAsyncConnection().clientKill(client))); + return; + } + getConnection().clientKill(client); + } catch (Exception e) { + convertLettuceAccessException(e); + } + } + /** * Specifies if pipelined and transaction results should be converted to the expected data type. If false, results of * {@link #closePipeline()} and {@link #exec()} will be of the type returned by the Lettuce driver diff --git a/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java b/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java index f55d06db5..2849bbf99 100644 --- a/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/srp/SrpConnection.java @@ -2222,6 +2222,24 @@ public class SrpConnection implements RedisConnection { return SrpConverters.toTimeAsLong(reply.data()); } + @Override + public void killClient(String host, int port) { + + Assert.hasText(host, "Host for 'CLIENT KILL' must not be 'null' or 'empty'."); + + String client = String.format("%s:%s", host, port); + try { + if (isPipelined()) { + pipeline(new SrpStatusResult(pipeline.client_kill(client))); + return; + } + + this.client.client_kill(client); + } catch (Exception e) { + throw convertSrpAccessException(e); + } + } + private List closeTransaction() { List results = Collections.emptyList(); if (txTracker != null) { diff --git a/src/main/java/org/springframework/data/redis/core/RedisOperations.java b/src/main/java/org/springframework/data/redis/core/RedisOperations.java index aeaa7ea78..2450bebf6 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/RedisOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import org.springframework.data.redis.serializer.RedisSerializer; * useful option for extensibility and testability (as it can be easily mocked or stubbed). * * @author Costin Leau + * @author Christoph Strobl */ public interface RedisOperations { @@ -284,4 +285,13 @@ public interface RedisOperations { RedisSerializer getHashKeySerializer(); RedisSerializer getHashValueSerializer(); + + /** + * Closes a given client connection identified by {@literal ip:port} given in {@code client}. + * + * @param host of connection to close. + * @param port of connection to close + * @since 1.3 + */ + void killClient(String host, int port); } diff --git a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java index e6dd59fe4..52d1478dc 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,6 +69,7 @@ import org.springframework.util.CollectionUtils; * This is the central class in Redis support. * * @author Costin Leau + * @author Christoph Strobl * @param the Redis key type against which the template works (usually a String) * @param the Redis value type against which the template works * @see StringRedisTemplate @@ -989,4 +990,21 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation public HashOperations opsForHash() { return new DefaultHashOperations(this); } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.RedisOperations#killClient(java.lang.Object) + */ + @Override + public void killClient(final String host, final int port) { + + execute(new RedisCallback() { + + @Override + public Void doInRedis(RedisConnection connection) throws DataAccessException { + connection.killClient(host, port); + return null; + } + }); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionUnitTestSuite.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionUnitTestSuite.java index b965dc643..6cca22922 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionUnitTestSuite.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionUnitTestSuite.java @@ -113,6 +113,16 @@ public class JedisConnectionUnitTestSuite { connection.pExpire("foo".getBytes(), msec); verifyNativeConnectionInvocation().pexpireAt(any(byte[].class), eq(expected)); } + + /** + * @see DATAREDIS-267 + */ + @Test + public void killClientShouldDelegateCallCorrectly() { + + connection.killClient("127.0.0.1", 1001); + verifyNativeConnectionInvocation().clientKill(eq("127.0.0.1:1001")); + } } public static class JedisConnectionPipelineUnitTests extends JedisConnectionUnitTests { @@ -141,6 +151,10 @@ public class JedisConnectionUnitTestSuite { super.shutdownSaveShouldBeSentCorrectlyUsingLuaScript(); } + @Test(expected = UnsupportedOperationException.class) + public void killClientShouldDelegateCallCorrectly() { + super.killClientShouldDelegateCallCorrectly(); + } } /** diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionUnitTestSuite.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionUnitTestSuite.java index a5c5df63f..3b2587e61 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionUnitTestSuite.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionUnitTestSuite.java @@ -82,6 +82,17 @@ public class LettuceConnectionUnitTestSuite { verifyNativeConnectionInvocation().shutdown(true); } + /** + * @see DATAREDIS-267 + */ + @Test + public void killClientShouldDelegateCallCorrectly() { + + String ipPort = "127.0.0.1:1001"; + connection.killClient("127.0.0.1", 1001); + verifyNativeConnectionInvocation().clientKill(eq(ipPort)); + } + } public static class LettucePipelineConnectionUnitTests extends LettuceConnectionUnitTests { diff --git a/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionUnitTestSuite.java b/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionUnitTestSuite.java index 52af77a85..2409503c3 100644 --- a/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionUnitTestSuite.java +++ b/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionUnitTestSuite.java @@ -15,6 +15,7 @@ */ package org.springframework.data.redis.connection.srp; +import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import org.junit.Before; @@ -77,6 +78,17 @@ public class SrpConnectionUnitTestSuite { verifyNativeConnectionInvocation().shutdown("NOSAVE".getBytes(Charsets.UTF_8), null); } + /** + * @see DATAREDIS-267 + */ + @Test + public void killClientShouldDelegateCallCorrectly() { + + String ipPort = "127.0.0.1:1001"; + connection.killClient("127.0.0.1", 1001); + verifyNativeConnectionInvocation().client_kill(eq(ipPort)); + } + } public static class SrpConnectionPiplineUnitTests extends AbstractConnectionUnitTestBase {