DATAREDIS-304 - Add support for SSCAN command.
SSCAN command is natively supported by jedis and can be emulated for lettuce. JRedis and SRP will throw UnsupportedOperationException. sScan is available on RedisConneciton and RedisSetOperations returning basically a Cursor that allows iteration over the defined values for a given key. Currently the jedis driver does not directly expose the binary version of sscan which leads to invalid results when using a non String compatible converter along with the RedisTemplate. We’ll change this as soon as a newer version of jedis is available. Along the way loading behavior of ScanCursor has been fixed, preventing it from indicating next values available when there effectively are no more. Orignal pull request: #76.
This commit is contained in:
committed by
Thomas Darimont
parent
5f4e9db01a
commit
34d1759b4e
@@ -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.ConvertingCursor;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.types.RedisClientInfo;
|
||||
@@ -2226,6 +2227,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection {
|
||||
return this.delegate.scan(options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisSetCommands#scan(byte[], org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Cursor<byte[]> sScan(byte[] key, ScanOptions options) {
|
||||
return this.delegate.sScan(key, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -2299,4 +2309,23 @@ public class DefaultStringRedisConnection implements StringRedisConnection {
|
||||
public String getClientName() {
|
||||
return this.delegate.getClientName();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection#sScan(java.lang.String, org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Cursor<String> sScan(String key, ScanOptions options) {
|
||||
|
||||
return new ConvertingCursor<byte[], String>(this.delegate.sScan(this.serialize(key), options),
|
||||
new Converter<byte[], String>() {
|
||||
|
||||
@Override
|
||||
public String convert(byte[] source) {
|
||||
return serializer.deserialize(source);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ package org.springframework.data.redis.connection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
|
||||
/**
|
||||
* Set-specific commands supported by Redis.
|
||||
*
|
||||
@@ -166,4 +169,15 @@ public interface RedisSetCommands {
|
||||
* @return
|
||||
*/
|
||||
List<byte[]> sRandMember(byte[] key, long count);
|
||||
|
||||
/**
|
||||
* Use a {@link Cursor} to iterate over elements in set at {@code key}.
|
||||
*
|
||||
* @since 1.4
|
||||
* @see http://redis.io/commands/scan
|
||||
* @param key
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
Cursor<byte[]> sScan(byte[] key, ScanOptions options);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.types.RedisClientInfo;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
@@ -316,4 +318,13 @@ public interface StringRedisConnection extends RedisConnection {
|
||||
* @since 1.3
|
||||
*/
|
||||
List<RedisClientInfo> getClientList();
|
||||
|
||||
/**
|
||||
* @see RedisSetCommands#sScan(byte[], ScanOptions)
|
||||
* @param key
|
||||
* @param options
|
||||
* @return
|
||||
* @since 1.4
|
||||
*/
|
||||
Cursor<String> sScan(String key, ScanOptions options);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ 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.Cursor;
|
||||
import org.springframework.data.redis.core.KeyBoundCursor;
|
||||
import org.springframework.data.redis.core.ScanCursor;
|
||||
import org.springframework.data.redis.core.ScanIteration;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
@@ -2918,6 +2919,12 @@ public class JedisConnection implements RedisConnection {
|
||||
return scan(0, options != null ? options : ScanOptions.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.4
|
||||
* @param cursorId
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
public Cursor<byte[]> scan(long cursorId, ScanOptions options) {
|
||||
|
||||
return new ScanCursor<byte[]>(cursorId, options) {
|
||||
@@ -2929,22 +2936,65 @@ public class JedisConnection implements RedisConnection {
|
||||
throw new UnsupportedOperationException("'SCAN' cannot be called in pipeline / transaction mode.");
|
||||
}
|
||||
|
||||
ScanParams sp = new ScanParams();
|
||||
if (!options.equals(ScanOptions.NONE)) {
|
||||
if (options.getCount() != null) {
|
||||
sp.count(options.getCount().intValue());
|
||||
}
|
||||
if (StringUtils.hasText(options.getPattern())) {
|
||||
sp.match(options.getPattern());
|
||||
}
|
||||
ScanParams params = prepareScanParams(options);
|
||||
redis.clients.jedis.ScanResult<String> result = jedis.scan(Long.toString(cursorId), params);
|
||||
return new ScanIteration<byte[]>(Long.valueOf(result.getStringCursor()), JedisConverters.stringListToByteList()
|
||||
.convert(result.getResult()));
|
||||
}
|
||||
|
||||
}.open();
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisSetCommands#sScan(byte[], org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Cursor<byte[]> sScan(byte[] key, ScanOptions options) {
|
||||
return sScan(key, 0, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.4
|
||||
* @param key
|
||||
* @param cursorId
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
public Cursor<byte[]> sScan(byte[] key, long cursorId, ScanOptions options) {
|
||||
|
||||
return new KeyBoundCursor<byte[]>(key, cursorId, options) {
|
||||
|
||||
@Override
|
||||
protected ScanIteration<byte[]> doScan(byte[] key, long cursorId, ScanOptions options) {
|
||||
|
||||
if (isQueueing() || isPipelined()) {
|
||||
throw new UnsupportedOperationException("'SSCAN' cannot be called in pipeline / transaction mode.");
|
||||
}
|
||||
|
||||
redis.clients.jedis.ScanResult<String> result = jedis.scan(Long.toString(cursorId), sp);
|
||||
ScanParams params = prepareScanParams(options);
|
||||
|
||||
// TODO: use binary version of jedis.sscan (in v.2.4.3) to avoid potentially invalid representations.
|
||||
redis.clients.jedis.ScanResult<String> result = jedis.sscan(JedisConverters.toString(key),
|
||||
Long.toString(cursorId), params);
|
||||
return new ScanIteration<byte[]>(Long.valueOf(result.getStringCursor()), JedisConverters.stringListToByteList()
|
||||
.convert(result.getResult()));
|
||||
}
|
||||
}.open();
|
||||
}
|
||||
|
||||
private ScanParams prepareScanParams(ScanOptions options) {
|
||||
ScanParams sp = new ScanParams();
|
||||
if (!options.equals(ScanOptions.NONE)) {
|
||||
if (options.getCount() != null) {
|
||||
sp.count(options.getCount().intValue());
|
||||
}
|
||||
if (StringUtils.hasText(options.getPattern())) {
|
||||
sp.match(options.getPattern());
|
||||
}
|
||||
}
|
||||
return sp;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1247,4 +1247,13 @@ public class JredisConnection implements RedisConnection {
|
||||
public Cursor<byte[]> scan(ScanOptions options) {
|
||||
throw new UnsupportedOperationException("'SCAN' command is not supported for jredis.");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisSetCommands#sScan(byte[], org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Cursor<byte[]> sScan(byte[] key, ScanOptions options) {
|
||||
throw new UnsupportedOperationException("'SSCAN' command is not uspported for jredis");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ 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.Cursor;
|
||||
import org.springframework.data.redis.core.KeyBoundCursor;
|
||||
import org.springframework.data.redis.core.RedisCommand;
|
||||
import org.springframework.data.redis.core.ScanCursor;
|
||||
import org.springframework.data.redis.core.ScanIteration;
|
||||
@@ -3044,6 +3045,12 @@ public class LettuceConnection implements RedisConnection {
|
||||
return scan(0, options != null ? options : ScanOptions.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.4
|
||||
* @param cursorId
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
public Cursor<byte[]> scan(long cursorId, ScanOptions options) {
|
||||
|
||||
return new ScanCursor<byte[]>(cursorId, options) {
|
||||
@@ -3056,16 +3063,7 @@ public class LettuceConnection implements RedisConnection {
|
||||
throw new UnsupportedOperationException("'SCAN' cannot be called in pipeline / transaction mode.");
|
||||
}
|
||||
|
||||
String params = " ," + cursorId;
|
||||
if (!options.equals(ScanOptions.NONE)) {
|
||||
if (options.getCount() != null) {
|
||||
params += (", 'count', " + options.getCount());
|
||||
}
|
||||
if (StringUtils.hasText(options.getPattern())) {
|
||||
params += (", 'match' , '" + options.getPattern() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
String params = " ," + cursorId + prepareScanParams(options);
|
||||
String script = "return redis.call('SCAN'" + params + ")";
|
||||
|
||||
List<?> result = eval(script.getBytes(), ReturnType.MULTI, 0);
|
||||
@@ -3077,6 +3075,60 @@ public class LettuceConnection implements RedisConnection {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisSetCommands#sScan(byte[], org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Cursor<byte[]> sScan(byte[] key, ScanOptions options) {
|
||||
return sScan(key, 0, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.4
|
||||
* @param key
|
||||
* @param cursorId
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
public Cursor<byte[]> sScan(byte[] key, long cursorId, ScanOptions options) {
|
||||
|
||||
return new KeyBoundCursor<byte[]>(key, cursorId, options) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected ScanIteration<byte[]> doScan(byte[] key, long cursorId, ScanOptions options) {
|
||||
|
||||
if (isQueueing() || isPipelined()) {
|
||||
throw new UnsupportedOperationException("'SSCAN' cannot be called in pipeline / transaction mode.");
|
||||
}
|
||||
|
||||
String params = " ,'" + LettuceConverters.bytesToString().convert(key) + "', " + cursorId
|
||||
+ prepareScanParams(options);
|
||||
String script = "return redis.call('SSCAN'" + params + ")";
|
||||
|
||||
List<?> result = eval(script.getBytes(), ReturnType.MULTI, 0);
|
||||
String nextCursorId = LettuceConverters.bytesToString().convert((byte[]) result.get(0));
|
||||
|
||||
return new ScanIteration<byte[]>(Long.valueOf(nextCursorId), ((ArrayList<byte[]>) result.get(1)));
|
||||
}
|
||||
}.open();
|
||||
}
|
||||
|
||||
private String prepareScanParams(ScanOptions options) {
|
||||
|
||||
String params = "";
|
||||
if (!options.equals(ScanOptions.NONE)) {
|
||||
if (options.getCount() != null) {
|
||||
params += (", 'count', " + options.getCount());
|
||||
}
|
||||
if (StringUtils.hasText(options.getPattern())) {
|
||||
params += (", 'match' , '" + options.getPattern() + "'");
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -2342,6 +2342,15 @@ public class SrpConnection implements RedisConnection {
|
||||
throw new UnsupportedOperationException("'SCAN' command is not supported for Srp.");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisSetCommands#sScan(byte[], org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Cursor<byte[]> sScan(byte[] key, ScanOptions options) {
|
||||
throw new UnsupportedOperationException("'SSCAN' command is not supported for Srp.");
|
||||
}
|
||||
|
||||
private List<Object> closeTransaction() {
|
||||
List<Object> results = Collections.emptyList();
|
||||
if (txTracker != null) {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.data.redis.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -72,4 +73,11 @@ public interface BoundSetOperations<K, V> extends BoundKeyOperations<K> {
|
||||
V pop();
|
||||
|
||||
Long size();
|
||||
|
||||
/**
|
||||
* @param options
|
||||
* @return
|
||||
* @since 1.4
|
||||
*/
|
||||
Iterator<V> sScan(ScanOptions options);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ConvertingCursor} wraps a given cursor and applies given {@link Converter} to items prior to returning them.
|
||||
* This allows to easily perform required conversion whereas the underlying implementation may still work with its
|
||||
* native types.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @param <S>
|
||||
* @param <T>
|
||||
* @since 1.4
|
||||
*/
|
||||
public class ConvertingCursor<S, T> implements Cursor<T> {
|
||||
|
||||
private Cursor<S> delegate;
|
||||
private Converter<S, T> converter;
|
||||
|
||||
/**
|
||||
* @param cursor Cursor must not be {@literal null}.
|
||||
* @param converter Converter must not be {@literal null}.
|
||||
*/
|
||||
public ConvertingCursor(Cursor<S> cursor, Converter<S, T> converter) {
|
||||
|
||||
Assert.notNull(cursor, "Cursor delegate must not be 'null'.");
|
||||
Assert.notNull(cursor, "Converter must not be 'null'.");
|
||||
this.delegate = cursor;
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Iterator#hasNext()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return delegate.hasNext();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Iterator#next()
|
||||
*/
|
||||
@Override
|
||||
public T next() {
|
||||
return converter.convert(delegate.next());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Iterator#remove()
|
||||
*/
|
||||
@Override
|
||||
public void remove() {
|
||||
delegate.remove();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.io.Closeable#close()
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
delegate.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.Cursor#getCursorId()
|
||||
*/
|
||||
@Override
|
||||
public long getCursorId() {
|
||||
return delegate.getCursorId();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.Cursor#isClosed()
|
||||
*/
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return delegate.isClosed();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.Cursor#open()
|
||||
*/
|
||||
@Override
|
||||
public Cursor<T> open() {
|
||||
this.delegate = delegate.open();
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.Cursor#getPosition()
|
||||
*/
|
||||
@Override
|
||||
public long getPosition() {
|
||||
return delegate.getPosition();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.data.redis.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -26,6 +27,7 @@ import org.springframework.data.redis.connection.DataType;
|
||||
* Default implementation for {@link BoundSetOperations}.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class DefaultBoundSetOperations<K, V> extends DefaultBoundKeyOperations<K> implements BoundSetOperations<K, V> {
|
||||
|
||||
@@ -137,4 +139,13 @@ class DefaultBoundSetOperations<K, V> extends DefaultBoundKeyOperations<K> imple
|
||||
public DataType getType() {
|
||||
return DataType.SET;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.BoundSetOperations#sScan(org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Iterator<V> sScan(ScanOptions options) {
|
||||
return ops.sScan(getKey(), options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -21,12 +21,15 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link SetOperations}.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class DefaultSetOperations<K, V> extends AbstractOperations<K, V> implements SetOperations<K, V> {
|
||||
|
||||
@@ -244,4 +247,28 @@ class DefaultSetOperations<K, V> extends AbstractOperations<K, V> implements Set
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.SetOperations#sScan(java.lang.Object, org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Cursor<V> sScan(K key, final ScanOptions options) {
|
||||
|
||||
final byte[] rawKey = rawKey(key);
|
||||
return execute(new RedisCallback<Cursor<V>>() {
|
||||
|
||||
@Override
|
||||
public Cursor<V> doInRedis(RedisConnection connection) throws DataAccessException {
|
||||
return new ConvertingCursor<byte[], V>(connection.sScan(rawKey, options), new Converter<byte[], V>() {
|
||||
|
||||
@Override
|
||||
public V convert(byte[] source) {
|
||||
return deserializeValue(source);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @param <T>
|
||||
* @since 1.4
|
||||
*/
|
||||
public abstract class KeyBoundCursor<T> extends ScanCursor<T> {
|
||||
|
||||
private byte[] key;
|
||||
|
||||
/**
|
||||
* Crates new {@link ScanCursor}
|
||||
*
|
||||
* @param cursorId
|
||||
* @param options Defaulted to {@link ScanOptions#NONE} if nulled.
|
||||
*/
|
||||
public KeyBoundCursor(byte[] key, long cursorId, ScanOptions options) {
|
||||
super(cursorId, options != null ? options : ScanOptions.NONE);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
protected ScanIteration<T> doScan(long cursorId, ScanOptions options) {
|
||||
return doScan(this.key, cursorId, options);
|
||||
}
|
||||
|
||||
protected abstract ScanIteration<T> doScan(byte[] key, long cursorId, ScanOptions options);
|
||||
|
||||
public byte[] getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -76,7 +76,7 @@ public abstract class ScanCursor<T> implements Cursor<T> {
|
||||
|
||||
this.scanOptions = options != null ? options : ScanOptions.NONE;
|
||||
this.cursorId = cursorId;
|
||||
this.state = CursorState.CLOSED;
|
||||
this.state = CursorState.READY;
|
||||
this.delegate = Collections.emptyIterator();
|
||||
}
|
||||
|
||||
@@ -101,11 +101,13 @@ public abstract class ScanCursor<T> implements Cursor<T> {
|
||||
*/
|
||||
public final ScanCursor<T> open() {
|
||||
|
||||
if (isClosed()) {
|
||||
doOpen(cursorId);
|
||||
state = CursorState.OPEN;
|
||||
if (!isReady()) {
|
||||
throw new InvalidDataAccessApiUsageException("Cursor already " + state + ". Cannot (re)open it.");
|
||||
}
|
||||
|
||||
state = CursorState.OPEN;
|
||||
doOpen(cursorId);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -162,6 +164,10 @@ public abstract class ScanCursor<T> implements Cursor<T> {
|
||||
|
||||
assertCursorIsOpen();
|
||||
|
||||
if (!delegate.hasNext() && !CursorState.FINISHED.equals(state)) {
|
||||
scan(cursorId);
|
||||
}
|
||||
|
||||
if (delegate.hasNext()) {
|
||||
return true;
|
||||
}
|
||||
@@ -175,7 +181,7 @@ public abstract class ScanCursor<T> implements Cursor<T> {
|
||||
|
||||
private void assertCursorIsOpen() {
|
||||
|
||||
if (isClosed()) {
|
||||
if (isReady() || isClosed()) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot access closed cursor. Did you forget to call open()?");
|
||||
}
|
||||
}
|
||||
@@ -189,11 +195,7 @@ public abstract class ScanCursor<T> implements Cursor<T> {
|
||||
|
||||
assertCursorIsOpen();
|
||||
|
||||
if (state != CursorState.FINISHED && !delegate.hasNext()) {
|
||||
scan(cursorId);
|
||||
}
|
||||
|
||||
if (!delegate.hasNext()) {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException("No more elements available for cursor " + cursorId + ".");
|
||||
}
|
||||
|
||||
@@ -231,7 +233,7 @@ public abstract class ScanCursor<T> implements Cursor<T> {
|
||||
|
||||
try {
|
||||
doClose();
|
||||
}finally {
|
||||
} finally {
|
||||
state = CursorState.CLOSED;
|
||||
}
|
||||
}
|
||||
@@ -239,9 +241,7 @@ public abstract class ScanCursor<T> implements Cursor<T> {
|
||||
/**
|
||||
* Customization hook for cleaning up resources on when calling {@link #close()}.
|
||||
*/
|
||||
protected void doClose() {
|
||||
resetDelegate();
|
||||
}
|
||||
protected void doClose() {}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -252,6 +252,14 @@ public abstract class ScanCursor<T> implements Cursor<T> {
|
||||
return state == CursorState.CLOSED;
|
||||
}
|
||||
|
||||
protected final boolean isReady() {
|
||||
return state == CursorState.READY;
|
||||
}
|
||||
|
||||
protected final boolean isOpen() {
|
||||
return state == CursorState.OPEN;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.Cursor#getPosition()
|
||||
@@ -264,7 +272,7 @@ public abstract class ScanCursor<T> implements Cursor<T> {
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
enum CursorState{
|
||||
OPEN, FINISHED, CLOSED;
|
||||
enum CursorState {
|
||||
READY, OPEN, FINISHED, CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.data.redis.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -24,6 +25,7 @@ import java.util.Set;
|
||||
* Redis set specific operations.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface SetOperations<K, V> {
|
||||
|
||||
@@ -72,4 +74,14 @@ public interface SetOperations<K, V> {
|
||||
Long size(K key);
|
||||
|
||||
RedisOperations<K, V> getOperations();
|
||||
|
||||
/**
|
||||
* Iterate over elements in set at {@code key}.
|
||||
*
|
||||
* @param key
|
||||
* @param options
|
||||
* @return
|
||||
* @since 1.4
|
||||
*/
|
||||
Iterator<V> sScan(K key, ScanOptions options);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user