From ea8806aa6158835b541ca31d9cbdd4417bd6be75 Mon Sep 17 00:00:00 2001 From: Costin Leau Date: Mon, 7 Mar 2011 19:34:10 +0200 Subject: [PATCH] DATAKV-36 add current sort-and-get draft --- .../keyvalue/redis/core/BulkIterable.java | 59 ++++++++ .../data/keyvalue/redis/core/BulkMapper.java | 31 ++++ .../keyvalue/redis/core/RedisTemplate.java | 137 +++++++++++++----- .../core/query/DefaultSortCriterion.java | 70 +++++++++ .../redis/core/query/DefaultSortQuery.java | 66 +++++++++ .../redis/core/query/SortCriterion.java | 35 +++++ .../keyvalue/redis/core/query/SortQuery.java | 63 ++++++++ .../redis/core/query/SortQueryBuilder.java | 43 ++++++ 8 files changed, 470 insertions(+), 34 deletions(-) create mode 100644 spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BulkIterable.java create mode 100644 spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BulkMapper.java create mode 100644 spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/DefaultSortCriterion.java create mode 100644 spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/DefaultSortQuery.java create mode 100644 spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortCriterion.java create mode 100644 spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortQuery.java create mode 100644 spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortQueryBuilder.java diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BulkIterable.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BulkIterable.java new file mode 100644 index 000000000..152f36ce2 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BulkIterable.java @@ -0,0 +1,59 @@ +/* + * Copyright 2011 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.keyvalue.redis.core; + +import java.util.Iterator; +import java.util.List; + +/** + * Wrapper class allowing for stream-like access across a list of values. + * + * @author Costin Leau + */ +class BulkIterable implements Iterable { + + private final List list; + private volatile int index = 0; + + public BulkIterable(List list) { + this.list = list; + } + + public boolean hasMore() { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + return new Iterator() { + + @Override + public boolean hasNext() { + return index < list.size(); + } + + @Override + public T next() { + return list.get(index++); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BulkMapper.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BulkMapper.java new file mode 100644 index 000000000..d1388b062 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/BulkMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2011 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.keyvalue.redis.core; + +import java.util.Iterator; + +/** + * Mapper translating Redis bulk value responses (typically returned by a sort query) to actual objects. Implementations of this interface do not have to worry + * about exception or connection handling. + *

+ * Typically used by {@link RedisTemplate} sortAndGet methods. + * + * @author Costin Leau + */ +public interface BulkMapper { + + T mapBulk(Iterator valueStream); +} diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisTemplate.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisTemplate.java index aad80f6c0..e28643db6 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisTemplate.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/RedisTemplate.java @@ -32,10 +32,12 @@ import java.util.concurrent.TimeUnit; import org.springframework.dao.DataAccessException; import org.springframework.data.keyvalue.redis.connection.DataType; +import org.springframework.data.keyvalue.redis.connection.DefaultSortParameters; import org.springframework.data.keyvalue.redis.connection.RedisConnection; import org.springframework.data.keyvalue.redis.connection.RedisConnectionFactory; import org.springframework.data.keyvalue.redis.connection.SortParameters; import org.springframework.data.keyvalue.redis.connection.RedisListCommands.Position; +import org.springframework.data.keyvalue.redis.core.query.SortQuery; import org.springframework.data.keyvalue.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.keyvalue.redis.serializer.RedisSerializer; import org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer; @@ -145,7 +147,7 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation * @return object returned by the action */ public T execute(RedisCallback action, boolean exposeConnection) { - return execute(action, exposeConnection, valueSerializer); + return execute(action, exposeConnection, false); } /** @@ -158,35 +160,6 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation * @return object returned by the action */ public T execute(RedisCallback action, boolean exposeConnection, boolean pipeline) { - return execute(action, exposeConnection, pipeline, valueSerializer); - } - - /** - * Executes the given action object within a connection, which can be exposed or not. Allows a custom serializer - * to be specified for the returned object. - * - * @param return type - * @param action action callback object that specifies the Redis action - * @param exposeConnection whether to enforce exposure of the native Redis Connection to callback code - * @param returnSerializer serializer used for converting the binary data to the custom return type - * @return returned by the action - */ - public T execute(RedisCallback action, boolean exposeConnection, RedisSerializer returnSerializer) { - return execute(action, exposeConnection, false, returnSerializer); - } - - /** - * Executes the given action object within a connection, which can be exposed or not. Allows a custom serializer - * to be specified for the returned object. - * - * @param return type - * @param action action callback object that specifies the Redis action - * @param exposeConnection whether to enforce exposure of the native Redis Connection to callback code - * @param pipeline whether to pipeline or not the connection for the execution duration - * @param returnSerializer serializer used for converting the binary data to the custom return type - * @return returned by the action - */ - public T execute(RedisCallback action, boolean exposeConnection, boolean pipeline, RedisSerializer returnSerializer) { Assert.notNull(action, "Callback object must not be null"); RedisConnectionFactory factory = getConnectionFactory(); @@ -203,7 +176,7 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation try { RedisConnection connToExpose = (exposeConnection ? conn : createRedisConnectionProxy(conn)); T result = action.doInRedis(connToExpose); - // TODO: should do flush? + // TODO: any other connection processing? return postProcessResult(result, conn, existingConnection); } finally { try { @@ -450,11 +423,15 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation @SuppressWarnings("unchecked") private > T deserializeValues(Collection rawValues, Class type) { - Collection values = (List.class.isAssignableFrom(type) ? new ArrayList(rawValues.size()) - : new LinkedHashSet(rawValues.size())); + return deserializeValues(rawValues, type, valueSerializer); + } + + private > T deserializeValues(Collection rawValues, Class type, RedisSerializer redisSerializer) { + Collection values = (List.class.isAssignableFrom(type) ? new ArrayList(rawValues.size()) + : new LinkedHashSet(rawValues.size())); for (byte[] bs : rawValues) { if (bs != null) { - values.add((V) valueSerializer.deserialize(bs)); + values.add((X) redisSerializer.deserialize(bs)); } } @@ -1975,4 +1952,96 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation return deserializeHashMap(entries); } } + + // Sort operations + public List sort(SortQuery query) { + return sort(query, null); + } + + public List sort(SortQuery query, String getKeyPattern) { + return sort(query, getKeyPattern, valueSerializer); + } + + @SuppressWarnings("unchecked") + public List sort(SortQuery query, String getKeyPattern, RedisSerializer resultSerializer) { + final byte[] rawKey = rawKey(query.getKey()); + final SortParameters params = convertQuery(query, + (getKeyPattern != null ? Collections.singletonList(getKeyPattern) : null), stringSerializer); + + List vals = execute(new RedisCallback>() { + @Override + public List doInRedis(RedisConnection connection) throws DataAccessException { + return connection.sort(rawKey, params); + } + }, true); + + return (List) deserializeValues(vals, List.class, resultSerializer); + } + + public List sort(SortQuery query, List getKeyPattern, BulkMapper bulkMapper) { + final byte[] rawKey = rawKey(query.getKey()); + final SortParameters params = convertQuery(query, getKeyPattern, stringSerializer); + + List vals = execute(new RedisCallback>() { + @Override + public List doInRedis(RedisConnection connection) throws DataAccessException { + return connection.sort(rawKey, params); + } + }, true); + + int bulkSize = getKeyPattern.size(); + List result = new ArrayList(vals.size() / bulkSize + 1); + + final List bulk = new ArrayList(bulkSize); + final List listView = Collections.unmodifiableList(bulk); + + for (byte[] bs : vals) { + bulk.add(bs); + if (bulk.size() == bulkSize) { + bulkMapper.mapBulk(listView.iterator()); + bulk.clear(); + } + } + + return result; + } + + public void sortAndStore(SortQuery query, K storeKey) { + sortAndStore(query, null, storeKey); + } + + public void sortAndStore(SortQuery query, List getKeyPattern, K storeKey) { + final byte[] rawStoreKey = rawKey(storeKey); + final byte[] rawKey = rawKey(query.getKey()); + final SortParameters params = convertQuery(query, getKeyPattern, stringSerializer); + + execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) throws DataAccessException { + connection.sort(rawKey, params, rawStoreKey); + return null; + } + }, true); + } + + private static SortParameters convertQuery(SortQuery query, List getKeyPattern, RedisSerializer stringSerializer) { + + return new DefaultSortParameters(stringSerializer.serialize(query.getBy()), query.getLimit(), serialize( + getKeyPattern, stringSerializer), query.getOrder(), query.isAlphabetic()); + } + + private static byte[][] serialize(List strings, RedisSerializer stringSerializer) { + List raw = null; + + if (strings == null) { + raw = Collections.emptyList(); + } + else { + raw = new ArrayList(strings.size()); + for (String key : strings) { + raw.add(stringSerializer.serialize(key)); + } + } + return raw.toArray(new byte[raw.size()][]); + } } \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/DefaultSortCriterion.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/DefaultSortCriterion.java new file mode 100644 index 000000000..781105687 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/DefaultSortCriterion.java @@ -0,0 +1,70 @@ +/* + * Copyright 2011 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.keyvalue.redis.core.query; + +import org.springframework.data.keyvalue.redis.connection.SortParameters.Order; +import org.springframework.data.keyvalue.redis.connection.SortParameters.Range; + +/** + * @author Costin Leau + */ +class DefaultSortCriterion implements SortCriterion { + + private final K key; + private String by; + + private Range limit; + private Order order; + private Boolean alpha; + + DefaultSortCriterion(K key) { + this.key = key; + } + + @Override + public SortCriterion alphabetical(boolean alpha) { + this.alpha = Boolean.valueOf(alpha); + return this; + } + + @Override + public SortQuery build() { + return new DefaultSortQuery(key, by, limit, order, alpha); + } + + @Override + public SortCriterion limit(long offset, long count) { + this.limit = new Range(offset, count); + return this; + } + + @Override + public SortCriterion limit(Range range) { + this.limit = range; + return this; + } + + @Override + public SortCriterion order(Order order) { + this.order = order; + return this; + } + + SortCriterion addBy(String keyPattern) { + this.by = keyPattern; + return this; + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/DefaultSortQuery.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/DefaultSortQuery.java new file mode 100644 index 000000000..7d01ab8b3 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/DefaultSortQuery.java @@ -0,0 +1,66 @@ +/* + * Copyright 2011 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.keyvalue.redis.core.query; + +import org.springframework.data.keyvalue.redis.connection.SortParameters.Order; +import org.springframework.data.keyvalue.redis.connection.SortParameters.Range; + +/** + * Default SortQuery implementation. + * + * @author Costin Leau + */ +class DefaultSortQuery implements SortQuery { + + private final K key; + private final Boolean alpha; + private final Order order; + private final Range limit; + private final String by; + + DefaultSortQuery(K key, String by, Range limit, Order order, Boolean alpha) { + this.key = key; + this.by = by; + this.limit = limit; + this.order = order; + this.alpha = alpha; + } + + @Override + public String getBy() { + return by; + } + + @Override + public Range getLimit() { + return limit; + } + + @Override + public Order getOrder() { + return order; + } + + @Override + public Boolean isAlphabetic() { + return alpha; + } + + @Override + public K getKey() { + return key; + } +} diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortCriterion.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortCriterion.java new file mode 100644 index 000000000..2a27d3d66 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortCriterion.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011 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.keyvalue.redis.core.query; + +import org.springframework.data.keyvalue.redis.connection.SortParameters.Order; +import org.springframework.data.keyvalue.redis.connection.SortParameters.Range; + +/** + * @author Costin Leau + */ +public interface SortCriterion { + + SortCriterion limit(long offset, long count); + + SortCriterion limit(Range range); + + SortCriterion order(Order order); + + SortCriterion alphabetical(boolean alpha); + + SortQuery build(); +} diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortQuery.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortQuery.java new file mode 100644 index 000000000..2399f2ba0 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortQuery.java @@ -0,0 +1,63 @@ +/* + * Copyright 2011 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.keyvalue.redis.core.query; + +import org.springframework.data.keyvalue.redis.connection.SortParameters.Order; +import org.springframework.data.keyvalue.redis.connection.SortParameters.Range; + +/** + * @author Costin Leau + */ +public interface SortQuery { + + /** + * Returns the sorting order. Can be null if nothing is specified. + * + * @return sorting order + */ + Order getOrder(); + + /** + * Indicates if the sorting is numeric (default) or alphabetical (lexicographical). + * Can be null if nothing is specified. + * + * @return the type of sorting + */ + Boolean isAlphabetic(); + + + /** + * Returns the sorting limit (range or pagination). + * Can be null if nothing is specified. + * + * @return sorting limit/range + */ + Range getLimit(); + + /** + * Target key for sorting. + * + * @return + */ + K getKey(); + + /** + * Pattern of external key used for sorting. + * + * @return + */ + String getBy(); +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortQueryBuilder.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortQueryBuilder.java new file mode 100644 index 000000000..5c4969e08 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/core/query/SortQueryBuilder.java @@ -0,0 +1,43 @@ +/* + * Copyright 2011 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.keyvalue.redis.core.query; + + +/** + * Builder class for constructing {@link SortQuery}. + * + * @author Costin Leau + */ +public class SortQueryBuilder extends DefaultSortCriterion { + + private static final String NO_SORT_KEY = "~"; + + private SortQueryBuilder(K key) { + super(key); + } + + public static SortQueryBuilder sort(K key) { + return new SortQueryBuilder(key); + } + + public SortCriterion by(String keyPattern) { + return addBy(keyPattern); + } + + public SortCriterion noSort() { + return by(NO_SORT_KEY); + } +}