From 2679405e968f756e2d6cd6dbee274ac72e1de2f3 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 24 Mar 2014 09:05:33 +0100 Subject: [PATCH] DATAREDIS-268 - Add support for 'CLIENT LIST'. 'RedisConnection' and 'RedisTemplate' have been extended by 'getClientList' retrieving client informations from redis. 'RedisClientInfo' provides access via specified getters as well as an more general approach directly using the keys (like 'qubuf'). The operation is available for 'jedis', 'lettuce' and 'srp'. Original Pull Request: #53 --- .../appendix/appendix-command-reference.xml | 4 +- .../DefaultStringRedisConnection.java | 6 + .../redis/connection/RedisServerCommands.java | 11 + .../connection/StringRedisConnection.java | 7 + .../StringToRedisClientInfoConverter.java | 52 ++++ .../connection/jedis/JedisConnection.java | 13 + .../connection/jedis/JedisConverters.java | 22 +- .../connection/jredis/JredisConnection.java | 5 + .../connection/lettuce/LettuceConnection.java | 16 + .../connection/lettuce/LettuceConverters.java | 24 ++ .../redis/connection/srp/SrpConnection.java | 14 + .../redis/connection/srp/SrpConverters.java | 49 +++ .../data/redis/core/RedisOperations.java | 9 + .../data/redis/core/RedisTemplate.java | 12 + .../redis/core/types/RedisClientInfo.java | 285 ++++++++++++++++++ .../AbstractConnectionIntegrationTests.java | 24 +- ...disConnectionPipelineIntegrationTests.java | 9 + ...sConnectionPipelineTxIntegrationTests.java | 31 +- ...ConnectionTransactionIntegrationTests.java | 8 + .../jedis/JedisConvertersUnitTests.java | 62 ++++ .../JRedisConnectionIntegrationTests.java | 9 + ...uceConnectionPipelineIntegrationTests.java | 9 + ...eConnectionPipelineTxIntegrationTests.java | 9 + .../lettuce/LettuceConvertersUnitTests.java | 64 ++++ ...ConnectionTransactionIntegrationTests.java | 8 + .../srp/SrpConvertersUnitTests.java | 96 ++++++ .../data/redis/core/RedisTemplateTests.java | 22 +- .../core/types/RedisClientInfoUnitTests.java | 74 +++++ 28 files changed, 937 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/springframework/data/redis/connection/convert/StringToRedisClientInfoConverter.java create mode 100644 src/main/java/org/springframework/data/redis/core/types/RedisClientInfo.java create mode 100644 src/test/java/org/springframework/data/redis/connection/jedis/JedisConvertersUnitTests.java create mode 100644 src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConvertersUnitTests.java create mode 100644 src/test/java/org/springframework/data/redis/connection/srp/SrpConvertersUnitTests.java create mode 100644 src/test/java/org/springframework/data/redis/core/types/RedisClientInfoUnitTests.java diff --git a/docs/src/reference/docbook/appendix/appendix-command-reference.xml b/docs/src/reference/docbook/appendix/appendix-command-reference.xml index 4a1d46111..1e793f682 100644 --- a/docs/src/reference/docbook/appendix/appendix-command-reference.xml +++ b/docs/src/reference/docbook/appendix/appendix-command-reference.xml @@ -1,4 +1,4 @@ -# + Spring Data Redis Supported Commands @@ -25,7 +25,7 @@ BRPOPLPUSHX CLIENT KILLX CLIENT GETNAMEX - CLIENT LIST- + CLIENT LISTX CLIENT SETNAMEX CONFIG GETX CONFIG RESETSTATX 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 e8ac41eed..e779d006a 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -32,6 +32,7 @@ import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.connection.convert.ListConverter; import org.springframework.data.redis.connection.convert.MapConverter; import org.springframework.data.redis.connection.convert.SetConverter; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.util.Assert; @@ -2191,6 +2192,11 @@ public class DefaultStringRedisConnection implements StringRedisConnection { return this.delegate.time(); } + @Override + public List getClientList() { + return this.delegate.getClientList(); + } + /** * 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 1268d8375..999d27372 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java @@ -18,6 +18,8 @@ package org.springframework.data.redis.connection; import java.util.List; import java.util.Properties; +import org.springframework.data.redis.core.types.RedisClientInfo; + /** * Server-specific commands supported by Redis. * @@ -186,4 +188,13 @@ public interface RedisServerCommands { * @since 1.3 */ String getClientName(); + + /** + * Request information and statistics about connected clients. + * + * @return {@link List} of {@link RedisClientInfo} objects. + * @since 1.3 + * @see http://redis.io/commands/client-list + */ + List getClientList(); } diff --git a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java index ae3d9684e..4a25597f1 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -22,6 +22,7 @@ import java.util.Set; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.data.redis.serializer.RedisSerializer; /** @@ -301,4 +302,10 @@ public interface StringRedisConnection extends RedisConnection { * @sice 1.3 */ void setClientName(String name); + + /** + * @see RedisConnection#getClientList() + * @since 1.3 + */ + List getClientList(); } diff --git a/src/main/java/org/springframework/data/redis/connection/convert/StringToRedisClientInfoConverter.java b/src/main/java/org/springframework/data/redis/connection/convert/StringToRedisClientInfoConverter.java new file mode 100644 index 000000000..8c1fe7449 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/convert/StringToRedisClientInfoConverter.java @@ -0,0 +1,52 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.connection.convert; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.redis.core.types.RedisClientInfo; +import org.springframework.data.redis.core.types.RedisClientInfo.RedisClientInfoBuilder; + +/** + * {@link Converter} implementation to create one {@link RedisClientInfo} per line entry in given {@link String} array. + * + *
+ * ## sample of single line
+ * addr=127.0.0.1:60311 fd=6 name= age=4059 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
+ * 
+ * + * @author Christoph Strobl + * @since 1.3 + */ +public class StringToRedisClientInfoConverter implements Converter> { + + @Override + public List convert(String[] lines) { + + if (lines == null) { + return Collections.emptyList(); + } + List infos = new ArrayList(lines.length); + for (String line : lines) { + infos.add(RedisClientInfoBuilder.fromString(line)); + } + return infos; + } + +} 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 406142381..7671f965f 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 @@ -42,6 +42,7 @@ import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.Subscription; import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.connection.convert.TransactionResultConverter; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; @@ -2854,6 +2855,18 @@ public class JedisConnection implements RedisConnection { return jedis.clientGetname(); } + /* + * @see org.springframework.data.redis.connection.RedisServerCommands#getClientName() + */ + @Override + public List getClientList() { + + if (isQueueing() || isPipelined()) { + throw new UnsupportedOperationException("'CLIENT LIST' is not supported in in pipeline / multi mode."); + } + return JedisConverters.toListOfRedisClientInformation(this.jedis.clientList()); + } + /** * 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/jedis/JedisConverters.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java index 69c6a2663..b1bc24832 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -15,6 +15,8 @@ */ package org.springframework.data.redis.connection.jedis; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; @@ -31,7 +33,10 @@ import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.connection.convert.ListConverter; import org.springframework.data.redis.connection.convert.MapConverter; import org.springframework.data.redis.connection.convert.SetConverter; +import org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import redis.clients.jedis.BinaryClient.LIST_POSITION; import redis.clients.jedis.BitOP; @@ -42,6 +47,7 @@ import redis.clients.util.SafeEncoder; * Jedis type converters * * @author Jennifer Hickey + * @author Christoph Strobl */ abstract public class JedisConverters extends Converters { @@ -51,6 +57,7 @@ abstract public class JedisConverters extends Converters { private static final MapConverter STRING_MAP_TO_BYTE_MAP; private static final SetConverter TUPLE_SET_TO_TUPLE_SET; private static final Converter EXCEPTION_CONVERTER = new JedisExceptionConverter(); + private static final Converter> STRING_TO_CLIENT_INFO_CONVERTER = new StringToRedisClientInfoConverter(); static { STRING_TO_BYTES = new Converter() { @@ -114,6 +121,19 @@ abstract public class JedisConverters extends Converters { return source == null ? null : SafeEncoder.encode(source); } + /** + * @param source + * @return + * @since 1.3 + */ + public static List toListOfRedisClientInformation(String source) { + + if (!StringUtils.hasText(source)) { + return Collections.emptyList(); + } + return STRING_TO_CLIENT_INFO_CONVERTER.convert(source.split("\\r?\\n")); + } + public static DataAccessException toDataAccessException(Exception ex) { return EXCEPTION_CONVERTER.convert(ex); } 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 24613a97f..1f2b538a4 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 @@ -43,6 +43,7 @@ import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.ReturnType; import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.Subscription; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; @@ -1204,4 +1205,8 @@ public class JredisConnection implements RedisConnection { public String getClientName() { throw new UnsupportedOperationException("The 'CLIENT GETNAME' command is not supported by the JRedis driver."); } + + public List getClientList() { + throw new UnsupportedOperationException(); + } } 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 aa1750e25..4483b6c5a 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 @@ -49,6 +49,7 @@ import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.Subscription; import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.connection.convert.TransactionResultConverter; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -2959,6 +2960,21 @@ public class LettuceConnection implements RedisConnection { } } + @Override + public List getClientList() { + + if (isPipelined()) { + throw new UnsupportedOperationException("Cannot be called in pipeline mode."); + } + if (isQueueing()) { + transaction(new LettuceTxResult(getAsyncConnection().clientList(), + LettuceConverters.stringToRedisClientListConverter())); + return null; + } + + return LettuceConverters.toListOfRedisClientInformation(getConnection().clientList()); + } + /** * 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/lettuce/LettuceConverters.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java index 11cac3959..6e5604c18 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java @@ -17,6 +17,7 @@ package org.springframework.data.redis.connection.lettuce; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.LinkedHashSet; import java.util.List; @@ -31,7 +32,10 @@ import org.springframework.data.redis.connection.ReturnType; import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.SortParameters.Order; import org.springframework.data.redis.connection.convert.Converters; +import org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import com.lambdaworks.redis.KeyValue; import com.lambdaworks.redis.ScoredValue; @@ -55,6 +59,8 @@ abstract public class LettuceConverters extends Converters { private static final Converter, Tuple> SCORED_VALUE_TO_TUPLE; private static final Converter EXCEPTION_CONVERTER = new LettuceExceptionConverter(); + private static final Converter> STRING_TO_LIST_OF_CLIENT_INFO = new StringToRedisClientInfoConverter(); + static { DATE_TO_LONG = new Converter() { public Long convert(Date source) { @@ -115,6 +121,20 @@ abstract public class LettuceConverters extends Converters { }; } + public static Converter> stringToRedisClientListConverter() { + return new Converter>() { + + @Override + public List convert(String source) { + if (!StringUtils.hasText(source)) { + return Collections.emptyList(); + } + + return STRING_TO_LIST_OF_CLIENT_INFO.convert(source.split("\\r?\\n")); + } + }; + } + public static Converter dateToLong() { return DATE_TO_LONG; } @@ -235,4 +255,8 @@ abstract public class LettuceConverters extends Converters { } return args; } + + public static List toListOfRedisClientInformation(String clientList) { + return stringToRedisClientListConverter().convert(clientList); + } } 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 7ebb9a5c4..65afaed30 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 @@ -40,6 +40,7 @@ import org.springframework.data.redis.connection.RedisSubscribedConnectionExcept import org.springframework.data.redis.connection.ReturnType; import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.Subscription; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.util.Assert; import redis.Command; @@ -2280,6 +2281,19 @@ public class SrpConnection implements RedisConnection { } } + @Override + public List getClientList() { + if (isQueueing()) { + throw new UnsupportedOperationException(); + } + if (isPipelined()) { + pipeline(new SrpGenericResult(pipeline.client_list(), SrpConverters.replyToListOfRedisClientInfo())); + return null; + } + + return SrpConverters.toListOfRedisClientInformation(this.client.client_list()); + } + private List closeTransaction() { List results = Collections.emptyList(); if (txTracker != null) { diff --git a/src/main/java/org/springframework/data/redis/connection/srp/SrpConverters.java b/src/main/java/org/springframework/data/redis/connection/srp/SrpConverters.java index 612839335..d9975aac3 100644 --- a/src/main/java/org/springframework/data/redis/connection/srp/SrpConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/srp/SrpConverters.java @@ -34,6 +34,8 @@ import org.springframework.data.redis.connection.RedisListCommands.Position; import org.springframework.data.redis.connection.RedisStringCommands.BitOperation; import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; import org.springframework.data.redis.connection.convert.Converters; +import org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.util.Assert; import redis.client.RedisException; @@ -65,8 +67,12 @@ abstract public class SrpConverters extends Converters { private static final Converter BYTES_TO_STRING; private static final Converter BYTES_TO_DOUBLE; private static final Converter REPLIES_TO_TIME_AS_LONG; + private static final Converter> REPLY_T0_LIST_OF_CLIENT_INFO; + private static final Converter> STRING_TO_LIST_OF_CLIENT_INFO = new StringToRedisClientInfoConverter(); + private static final Converter> BYTEARRAY_T0_LIST_OF_CLIENT_INFO; static { + REPLIES_TO_BYTES_LIST = new Converter>() { public List convert(Reply[] replies) { if (replies == null) { @@ -85,21 +91,25 @@ abstract public class SrpConverters extends Converters { return list; } }; + REPLIES_TO_BYTES_SET = new Converter>() { public Set convert(Reply[] source) { return source != null ? new LinkedHashSet(SrpConverters.toBytesList(source)) : null; } }; + BYTES_TO_PROPERTIES = new Converter() { public Properties convert(byte[] source) { return source != null ? SrpConverters.toProperties(new String(source, Charsets.UTF_8)) : null; } }; + BYTES_TO_DOUBLE = new Converter() { public Double convert(byte[] bytes) { return (bytes == null || bytes.length == 0 ? null : Double.valueOf(new String(bytes, Charsets.UTF_8))); } }; + REPLIES_TO_TIME_AS_LONG = new Converter() { @Override @@ -115,6 +125,7 @@ abstract public class SrpConverters extends Converters { } }; + REPLIES_TO_TUPLE_SET = new Converter>() { public Set convert(Reply[] byteArrays) { if (byteArrays == null) { @@ -130,6 +141,7 @@ abstract public class SrpConverters extends Converters { return tuples; } }; + REPLIES_TO_BYTES_MAP = new Converter>() { public Map convert(Reply[] byteArrays) { if (byteArrays == null) { @@ -142,11 +154,13 @@ abstract public class SrpConverters extends Converters { return map; } }; + BYTES_TO_STRING = new Converter() { public String convert(byte[] data) { return data != null ? new String((byte[]) data, Charsets.UTF_8) : null; } }; + REPLIES_TO_BOOLEAN_LIST = new Converter>() { public List convert(Reply[] source) { if (source == null) { @@ -159,6 +173,7 @@ abstract public class SrpConverters extends Converters { return results; } }; + REPLIES_TO_STRING_LIST = new Converter>() { public List convert(Reply[] source) { if (source == null) { @@ -171,6 +186,7 @@ abstract public class SrpConverters extends Converters { return results; } }; + REPLY_TO_STRING = new Converter() { @Override @@ -181,6 +197,32 @@ abstract public class SrpConverters extends Converters { return SrpConverters.toString((byte[]) source.data()); } }; + + REPLY_T0_LIST_OF_CLIENT_INFO = new Converter>() { + + @Override + public List convert(Reply source) { + + if (source == null || source.data() == null) { + return Collections.emptyList(); + } + Assert.isInstanceOf(byte[].class, source.data(), "Expected data to be an instace of byte []."); + return BYTEARRAY_T0_LIST_OF_CLIENT_INFO.convert((byte[]) source.data()); + } + }; + + BYTEARRAY_T0_LIST_OF_CLIENT_INFO = new Converter>() { + + @Override + public List convert(byte[] source) { + if (source == null || source.length == 0) { + return Collections.emptyList(); + } + + String s = SrpConverters.toString(source); + return STRING_TO_LIST_OF_CLIENT_INFO.convert(s.split("\\r?\\n")); + } + }; } public static Converter> repliesToBytesList() { @@ -227,6 +269,10 @@ abstract public class SrpConverters extends Converters { return REPLIES_TO_TIME_AS_LONG; } + public static List toListOfRedisClientInformation(Reply reply) { + return REPLY_T0_LIST_OF_CLIENT_INFO.convert(reply); + } + public static List toBytesList(Reply[] source) { return REPLIES_TO_BYTES_LIST.convert(source); } @@ -312,4 +358,7 @@ abstract public class SrpConverters extends Converters { return Collections.singletonList(source); } + public static Converter> replyToListOfRedisClientInfo() { + return BYTEARRAY_T0_LIST_OF_CLIENT_INFO; + } } 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 2450bebf6..edb3ac357 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/RedisOperations.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.core.query.SortQuery; import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.data.redis.serializer.RedisSerializer; /** @@ -174,6 +175,14 @@ public interface RedisOperations { List exec(); + /** + * /** Request information and statistics about connected clients. + * + * @return {@link List} of {@link RedisClientInfo} objects. + * @since 1.3 + */ + List getClientList(); + /** * Execute a transaction, using the provided {@link RedisSerializer} to deserialize any results that are byte[]s or * Collections of byte[]s. If a result is a Map, the provided {@link RedisSerializer} will be used for both the keys 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 52d1478dc..374354c64 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java @@ -40,6 +40,7 @@ import org.springframework.data.redis.core.query.SortQuery; import org.springframework.data.redis.core.script.DefaultScriptExecutor; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.data.redis.core.script.ScriptExecutor; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationUtils; @@ -1007,4 +1008,15 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation } }); } + + @Override + public List getClientList() { + return execute(new RedisCallback>() { + + @Override + public List doInRedis(RedisConnection connection) throws DataAccessException { + return connection.getClientList(); + } + }); + } } diff --git a/src/main/java/org/springframework/data/redis/core/types/RedisClientInfo.java b/src/main/java/org/springframework/data/redis/core/types/RedisClientInfo.java new file mode 100644 index 000000000..583072d9c --- /dev/null +++ b/src/main/java/org/springframework/data/redis/core/types/RedisClientInfo.java @@ -0,0 +1,285 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.core.types; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Properties; + +import org.springframework.util.Assert; + +/** + * {@link RedisClientInfo} provides general and statistical information about client connections. + * + * @author Christoph Strobl + * @since 1.3 + */ +public class RedisClientInfo { + + public static enum INFO { + ADDRESS_PORT("addr"), FILE_DESCRIPTOR("fd"), CONNECTION_NAME("name"), CONNECTION_AGE("age"), CONNECTION_IDLE("idle"), FLAGS( + "flags"), DATABSE_ID("db"), CHANNEL_SUBSCRIBTIONS("sub"), PATTERN_SUBSCRIBTIONS("psub"), MULIT_COMMAND_CONTEXT( + "multi"), BUFFER_LENGTH("qbuf"), BUFFER_FREE_SPACE("qbuf-free"), OUTPUT_BUFFER_LENGTH("obl"), OUTPUT_LIST_LENGTH( + "oll"), OUTPUT_BUFFER_MEMORY_USAGE("omem"), EVENTS("events"), LAST_COMMAND("cmd"); + + String key; + + INFO(String key) { + this.key = key; + } + + } + + private final Properties clientProperties; + + public RedisClientInfo(Properties properties) { + + Assert.notNull(properties, "Cannot initialize client information for given 'null' properties."); + this.clientProperties = new Properties(); + this.clientProperties.putAll(properties); + } + + /** + * Get address/port of the client. + * + * @return + */ + public String getAddressPort() { + return get(INFO.ADDRESS_PORT); + } + + /** + * Get file descriptor corresponding to the socket + * + * @return + */ + public String getFileDescriptor() { + return get(INFO.FILE_DESCRIPTOR); + } + + /** + * Get the clients name. + * + * @return + */ + public String getName() { + return get(INFO.CONNECTION_NAME); + } + + /** + * Get total duration of the connection in seconds. + * + * @return + */ + public Long getAge() { + return getLongValueOf(INFO.CONNECTION_AGE); + } + + /** + * Get idle time of the connection in seconds. + * + * @return + */ + public Long getIdle() { + return getLongValueOf(INFO.CONNECTION_IDLE); + } + + /** + * Get client flags. + * + * @return + */ + public String getFlags() { + return get(INFO.FLAGS); + } + + /** + * Get current database index. + * + * @return + */ + public Long getDatabaseId() { + return getLongValueOf(INFO.DATABSE_ID); + } + + /** + * Get number of channel subscriptions. + * + * @return + */ + public Long getChannelSubscribtions() { + return getLongValueOf(INFO.CHANNEL_SUBSCRIBTIONS); + } + + /** + * Get number of pattern subscriptions. + * + * @return + */ + public Long getPatternSubscrbtions() { + return getLongValueOf(INFO.PATTERN_SUBSCRIBTIONS); + } + + /** + * Get the number of commands in a MULTI/EXEC context. + * + * @return + */ + public Long getMultiCommandContext() { + return getLongValueOf(INFO.MULIT_COMMAND_CONTEXT); + } + + /** + * Get the query buffer length. + * + * @return + */ + public Long getBufferLength() { + return getLongValueOf(INFO.BUFFER_LENGTH); + } + + /** + * Get the free space of the query buffer. + * + * @return + */ + public Long getBufferFreeSpace() { + return getLongValueOf(INFO.BUFFER_FREE_SPACE); + } + + /** + * Get the output buffer length. + * + * @return + */ + public Long getOutputBufferLength() { + return getLongValueOf(INFO.OUTPUT_BUFFER_LENGTH); + } + + /** + * Get number queued replies in output buffer. + * + * @return + */ + public Long getOutputListLength() { + return getLongValueOf(INFO.OUTPUT_LIST_LENGTH); + } + + /** + * Get output buffer memory usage. + * + * @return + */ + public Long getOutputBufferMemoryUsage() { + return getLongValueOf(INFO.OUTPUT_BUFFER_MEMORY_USAGE); + } + + /** + * Get file descriptor events. + * + * @return + */ + public String getEvents() { + return get(INFO.EVENTS); + } + + /** + * Get last command played. + * + * @return + */ + public String getLastCommand() { + return get(INFO.LAST_COMMAND); + } + + /** + * @param info must not be null + * @return {@literal null} if no entry found for requested {@link INFO}. + */ + public String get(INFO info) { + + Assert.notNull(info, "Cannot retrieve client information for 'null'."); + return get(info.key); + } + + /** + * @param key must not be {@literal null} or {@literal empty}. + * @return {@literal null} if no entry found for requested {@code key}. + */ + public String get(String key) { + + Assert.hasText(key, "Cannot get client information for 'empty' / 'null' key."); + return this.clientProperties.getProperty(key); + } + + private Long getLongValueOf(INFO info) { + + String value = get(info); + return value == null ? null : Long.valueOf(value); + } + + @Override + public String toString() { + return this.clientProperties.toString(); + } + + public static class RedisClientInfoBuilder { + + public static RedisClientInfo fromString(String source) { + + Assert.notNull(source, "Cannot read client properties form 'null'."); + Properties properties = new Properties(); + try { + properties.load(new StringReader(source.replace(' ', '\n'))); + } catch (IOException e) { + throw new IllegalArgumentException(String.format("Properties could not be loaded from String '%s'.", source), e); + } + return new RedisClientInfo(properties); + } + + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((clientProperties == null) ? 0 : clientProperties.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof RedisClientInfo)) { + return false; + } + RedisClientInfo other = (RedisClientInfo) obj; + if (clientProperties == null) { + if (other.clientProperties != null) { + return false; + } + } else if (!clientProperties.equals(other.clientProperties)) { + return false; + } + return true; + } + +} diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index 9929e223a..ccb3c8f06 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -15,8 +15,7 @@ */ package org.springframework.data.redis.connection; -import static org.hamcrest.core.IsEqual.*; -import static org.hamcrest.core.IsNull.*; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.junit.Assume.*; import static org.springframework.data.redis.SpinBarrier.*; @@ -38,6 +37,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import org.hamcrest.core.IsNot; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -54,6 +54,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; import org.springframework.data.redis.connection.SortParameters.Order; import org.springframework.data.redis.connection.StringRedisConnection.StringTuple; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -1892,6 +1893,25 @@ public abstract class AbstractConnectionIntegrationTests { connection.setClientName("foo".getBytes()); } + /** + * @see DATAREDIS-268 + */ + @Test + public void testListClientsContainsAtLeastOneElement() { + + actual.add(connection.getClientList()); + + List results = getResults(); + assertNotNull(results.get(0)); + + List firstEntry = (List) results.get(0); + assertThat(firstEntry.size(), IsNot.not(0)); + assertThat(firstEntry.get(0), is(instanceOf(RedisClientInfo.class))); + + RedisClientInfo info = (RedisClientInfo) firstEntry.get(0); + assertThat(info.getDatabaseId(), is(notNullValue())); + } + protected void verifyResults(List expected) { assertEquals(expected, getResults()); } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionPipelineIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionPipelineIntegrationTests.java index f6994aad1..807b0270f 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionPipelineIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionPipelineIntegrationTests.java @@ -254,4 +254,13 @@ public class JedisConnectionPipelineIntegrationTests extends AbstractConnectionP public void clientSetNameWorksCorrectly() { super.clientSetNameWorksCorrectly(); } + + /** + * @see DATAREDIS-268 + */ + @Override + @Test(expected = UnsupportedOperationException.class) + public void testListClientsContainsAtLeastOneElement() { + super.testListClientsContainsAtLeastOneElement(); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionPipelineTxIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionPipelineTxIntegrationTests.java index 792f2c81a..3435fdd9b 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionPipelineTxIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionPipelineTxIntegrationTests.java @@ -1,7 +1,21 @@ +/* + * Copyright 2013-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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.data.redis.connection.jedis; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.*; import java.util.List; @@ -10,6 +24,10 @@ import org.junit.Test; import org.springframework.data.redis.connection.RedisPipelineException; import org.springframework.test.annotation.IfProfileValue; +/** + * @author Jennifer Hickey + * @author Christoph Strobl + */ public class JedisConnectionPipelineTxIntegrationTests extends JedisConnectionTransactionIntegrationTests { @Ignore("Jedis issue: Pipeline tries to return String instead of List") @@ -50,4 +68,13 @@ public class JedisConnectionPipelineTxIntegrationTests extends JedisConnectionTr // Return exec results and this test should behave exactly like its superclass return txResults; } + + /** + * @see DATAREDIS-268 + */ + @Override + @Test(expected = UnsupportedOperationException.class) + public void testListClientsContainsAtLeastOneElement() { + super.testListClientsContainsAtLeastOneElement(); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionTransactionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionTransactionIntegrationTests.java index a3435b7de..0501fd898 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionTransactionIntegrationTests.java @@ -200,4 +200,12 @@ public class JedisConnectionTransactionIntegrationTests extends AbstractConnecti super.clientSetNameWorksCorrectly(); } + /** + * @see DATAREDIS-268 + */ + @Override + @Test(expected = UnsupportedOperationException.class) + public void testListClientsContainsAtLeastOneElement() { + super.testListClientsContainsAtLeastOneElement(); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConvertersUnitTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConvertersUnitTests.java new file mode 100644 index 000000000..ccf3e5955 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConvertersUnitTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.connection.jedis; + +import static org.hamcrest.core.IsEqual.*; +import static org.junit.Assert.*; + +import java.util.Collections; + +import org.junit.Test; +import org.springframework.data.redis.core.types.RedisClientInfo; + +/** + * @author Christoph Strobl + */ +public class JedisConvertersUnitTests { + + private static final String CLIENT_ALL_SINGLE_LINE_RESPONSE = "addr=127.0.0.1:60311 fd=6 name= age=4059 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client"; + + /** + * @see DATAREDIS-268 + */ + @Test + public void convertingEmptyStringToListOfRedisClientInfoShouldReturnEmptyList() { + assertThat(JedisConverters.toListOfRedisClientInformation(""), equalTo(Collections. emptyList())); + } + + /** + * @see DATAREDIS-268 + */ + @Test + public void convertingNullToListOfRedisClientInfoShouldReturnEmptyList() { + assertThat(JedisConverters.toListOfRedisClientInformation(null), equalTo(Collections. emptyList())); + } + + /** + * @see DATAREDIS-268 + */ + @Test + public void convertingMultipleLiesToListOfRedisClientInfoReturnsListCorrectly() { + + StringBuilder sb = new StringBuilder(); + sb.append(CLIENT_ALL_SINGLE_LINE_RESPONSE); + sb.append("\r\n"); + sb.append(CLIENT_ALL_SINGLE_LINE_RESPONSE); + + assertThat(JedisConverters.toListOfRedisClientInformation(sb.toString()).size(), equalTo(2)); + } +} diff --git a/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java index ab30964d3..ad9707887 100644 --- a/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jredis/JRedisConnectionIntegrationTests.java @@ -818,4 +818,13 @@ public class JRedisConnectionIntegrationTests extends AbstractConnectionIntegrat public void clientSetNameWorksCorrectly() { super.clientSetNameWorksCorrectly(); } + + /** + * @see DATAREDIS-268 + */ + @Override + @Test(expected = UnsupportedOperationException.class) + public void testListClientsContainsAtLeastOneElement() { + super.testListClientsContainsAtLeastOneElement(); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionPipelineIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionPipelineIntegrationTests.java index 58a6943e3..db102aa3c 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionPipelineIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionPipelineIntegrationTests.java @@ -113,4 +113,13 @@ public class LettuceConnectionPipelineIntegrationTests extends AbstractConnectio } } + /** + * @see DATAREDIS-268 + */ + @Override + @Test(expected = UnsupportedOperationException.class) + public void testListClientsContainsAtLeastOneElement() { + super.testListClientsContainsAtLeastOneElement(); + } + } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionPipelineTxIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionPipelineTxIntegrationTests.java index 59aa57d2f..67d9b2e63 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionPipelineTxIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionPipelineTxIntegrationTests.java @@ -83,4 +83,13 @@ public class LettuceConnectionPipelineTxIntegrationTests extends LettuceConnecti return txResults; } + /** + * @see DATAREDIS-268 + */ + @Override + @Test(expected = UnsupportedOperationException.class) + public void testListClientsContainsAtLeastOneElement() { + super.testListClientsContainsAtLeastOneElement(); + } + } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConvertersUnitTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConvertersUnitTests.java new file mode 100644 index 000000000..aa605c964 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConvertersUnitTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.connection.lettuce; + +import static org.hamcrest.core.IsEqual.*; +import static org.junit.Assert.*; + +import java.util.Collections; + +import org.junit.Test; +import org.springframework.data.redis.core.types.RedisClientInfo; + +/** + * @author Christoph Strobl + */ +public class LettuceConvertersUnitTests { + + private static final String CLIENT_ALL_SINGLE_LINE_RESPONSE = "addr=127.0.0.1:60311 fd=6 name= age=4059 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client"; + + /** + * @see DATAREDIS-268 + */ + @Test + public void convertingEmptyStringToListOfRedisClientInfoShouldReturnEmptyList() { + assertThat(LettuceConverters.toListOfRedisClientInformation(""), equalTo(Collections. emptyList())); + } + + /** + * @see DATAREDIS-268 + */ + @Test + public void convertingNullToListOfRedisClientInfoShouldReturnEmptyList() { + assertThat(LettuceConverters.toListOfRedisClientInformation(null), + equalTo(Collections. emptyList())); + } + + /** + * @see DATAREDIS-268 + */ + @Test + public void convertingMultipleLiesToListOfRedisClientInfoReturnsListCorrectly() { + + StringBuilder sb = new StringBuilder(); + sb.append(CLIENT_ALL_SINGLE_LINE_RESPONSE); + sb.append("\r\n"); + sb.append(CLIENT_ALL_SINGLE_LINE_RESPONSE); + + assertThat(LettuceConverters.toListOfRedisClientInformation(sb.toString()).size(), equalTo(2)); + } + +} diff --git a/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionTransactionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionTransactionIntegrationTests.java index d636c2261..bf9d5dd5a 100644 --- a/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/srp/SrpConnectionTransactionIntegrationTests.java @@ -59,4 +59,12 @@ public class SrpConnectionTransactionIntegrationTests extends AbstractConnection public void testZUnionStoreAggWeights() { super.testZUnionStoreAggWeights(); } + + /** + * @see DATAREDIS-268 + */ + @Test(expected = UnsupportedOperationException.class) + public void testListClientsContainsAtLeastOneElement() { + super.testListClientsContainsAtLeastOneElement(); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/srp/SrpConvertersUnitTests.java b/src/test/java/org/springframework/data/redis/connection/srp/SrpConvertersUnitTests.java new file mode 100644 index 000000000..c33619d07 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/connection/srp/SrpConvertersUnitTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.connection.srp; + +import static org.hamcrest.core.IsEqual.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; + +import org.junit.Test; +import org.springframework.data.redis.core.types.RedisClientInfo; + +import redis.reply.BulkReply; +import redis.reply.Reply; + +/** + * @author Christoph Strobl + */ +public class SrpConvertersUnitTests { + + private static final String CLIENT_ALL_SINGLE_LINE_RESPONSE = "addr=127.0.0.1:60311 fd=6 name= age=4059 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client"; + + /** + * @see DATAREDIS-268 + */ + @Test + public void convertingNullReplyToListOfRedisClientInfoShouldReturnEmptyList() { + assertThat(SrpConverters.toListOfRedisClientInformation(new BulkReply(null)), + equalTo(Collections. emptyList())); + } + + /** + * @see DATAREDIS-268 + */ + @Test + public void convertingEmptyReplyToListOfRedisClientInfoShouldReturnEmptyList() { + assertThat(SrpConverters.toListOfRedisClientInformation(new BulkReply(new byte[0])), + equalTo(Collections. emptyList())); + } + + /** + * @see DATAREDIS-268 + */ + @Test + public void convertingNullToListOfRedisClientInfoShouldReturnEmptyList() { + assertThat(SrpConverters.toListOfRedisClientInformation(null), equalTo(Collections. emptyList())); + } + + /** + * @see DATAREDIS-268 + */ + @Test + public void convertingMultipleLiesToListOfRedisClientInfoReturnsListCorrectly() { + + StringBuilder sb = new StringBuilder(); + sb.append(CLIENT_ALL_SINGLE_LINE_RESPONSE); + sb.append("\r\n"); + sb.append(CLIENT_ALL_SINGLE_LINE_RESPONSE); + + assertThat(SrpConverters.toListOfRedisClientInformation(new BulkReply(sb.toString().getBytes())).size(), equalTo(2)); + } + + /** + * @see DATAREDIS-268 + */ + @Test(expected = IllegalArgumentException.class) + public void expectExcptionWhenProvidingInvalidDataInReply() { + SrpConverters.toListOfRedisClientInformation(new Reply() { + + @Override + public String data() { + return "foo"; + } + + @Override + public void write(OutputStream os) throws IOException { + // just do nothing; + } + }); + } +} diff --git a/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java b/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java index 57f4e8b73..82f9cb4b3 100644 --- a/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java +++ b/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -15,15 +15,10 @@ */ package org.springframework.data.redis.core; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeTrue; -import static org.springframework.data.redis.SpinBarrier.waitFor; -import static org.springframework.data.redis.matcher.RedisTestMatchers.isEqual; +import static org.junit.Assert.*; +import static org.junit.Assume.*; +import static org.springframework.data.redis.SpinBarrier.*; +import static org.springframework.data.redis.matcher.RedisTestMatchers.*; import java.util.ArrayList; import java.util.Arrays; @@ -38,6 +33,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; +import org.hamcrest.core.IsNot; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -67,6 +63,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; * Integration test of {@link RedisTemplate} * * @author Jennifer Hickey + * @author Christoph Strobl */ @RunWith(Parameterized.class) public class RedisTemplateTests { @@ -703,6 +700,11 @@ public class RedisTemplateTests { Collections.singletonList(key1))); } + @Test + public void clientListShouldReturnCorrectly() { + assertThat(redisTemplate.getClientList().size(), IsNot.not(0)); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) private byte[] serialize(Object value, RedisSerializer serializer) { if (serializer == null && value instanceof byte[]) { diff --git a/src/test/java/org/springframework/data/redis/core/types/RedisClientInfoUnitTests.java b/src/test/java/org/springframework/data/redis/core/types/RedisClientInfoUnitTests.java new file mode 100644 index 000000000..5f7bbc3b7 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/core/types/RedisClientInfoUnitTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.core.types; + +import org.hamcrest.core.Is; +import org.hamcrest.core.IsEqual; +import org.hamcrest.core.IsNot; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.redis.core.types.RedisClientInfo.RedisClientInfoBuilder; + +/** + * @author Christoph Strobl + */ +public class RedisClientInfoUnitTests { + + private final String SOURCE_WITH_PLACEHOLDER = "addr=127.0.0.1:57013#fd=6#name=client-1#age=16#idle=0#flags=N#db=0#sub=0#psub=0#multi=-1#qbuf=0#qbuf-free=32768#obl=0#oll=0#omem=0#events=r#cmd=client"; + private final String SINGLE_LINE = SOURCE_WITH_PLACEHOLDER.replace('#', ' '); + private final String[] VALUES = SOURCE_WITH_PLACEHOLDER.split("#"); + + private RedisClientInfo info; + + @Before + public void setUp() { + info = RedisClientInfoBuilder.fromString(SINGLE_LINE); + } + + @Test + public void testBuilderShouldReadsInfoCorrectlyFromSingleLineString() { + assertValues(info, VALUES); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetRequiresNonNullKey() { + info.get((String) null); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetRequiresNonBlankKey() { + info.get(""); + } + + @Test + public void testGetReturnsNullForPropertiesNotAvailable() { + Assert.assertThat(info.get("foo-bar"), IsEqual.equalTo(null)); + } + + private void assertValues(RedisClientInfo info, String[] values) { + for (String potentialValue : values) { + if (potentialValue.contains("=")) { + String[] keyValuePair = potentialValue.split("="); + Assert.assertThat(info.get(keyValuePair[0]), Is.is(keyValuePair[1])); + } else { + Assert.assertThat(info.get(potentialValue), IsNot.not(null)); + } + } + + } + +}