DATAREDIS-425 - CRUD operation and index resolution refinements.

- Add a composite IndexResolver implementation that iterates over a given collection of delegate IndexResolver instances and collects IndexedData from those.
- Break up cycle involving ReferenceResolver and let the resolver just returns the raw hash.
- Remove IndexType and use dedicated classes for index definitions.
- Fix pagination error and follow up to changes introduced via DATAKV-123.

Original Pull Request: #156
This commit is contained in:
Christoph Strobl
2016-01-07 12:23:07 +01:00
parent 059ac9fabb
commit 4362c58a8a
51 changed files with 2330 additions and 478 deletions

View File

@@ -35,14 +35,13 @@ import org.springframework.data.redis.connection.RedisClusterNode.RedisClusterNo
import org.springframework.data.redis.connection.RedisClusterNode.SlotRange;
import org.springframework.data.redis.connection.RedisNode.NodeType;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.NumberUtils;
import org.springframework.util.StringUtils;
/**
* Common type converters
*
*
* @author Jennifer Hickey
* @author Thomas Darimont
* @author Mark Paluch
@@ -85,7 +84,7 @@ abstract public class Converters {
Set<Flag> flags = parseFlags(args);
String portPart = hostAndPort[1];
if(portPart.contains("@")){
if (portPart.contains("@")) {
portPart = portPart.substring(0, portPart.indexOf('@'));
}
@@ -195,7 +194,7 @@ abstract public class Converters {
/**
* Converts the result of a single line of {@code CLUSTER NODES} into a {@link RedisClusterNode}.
*
*
* @param clusterNodesLine
* @return
* @since 1.7
@@ -206,7 +205,7 @@ abstract public class Converters {
/**
* Converts lines from the result of {@code CLUSTER NODES} into {@link RedisClusterNode}s.
*
*
* @param clusterNodes
* @return
* @since 1.7
@@ -228,6 +227,7 @@ abstract public class Converters {
/**
* Converts the result of {@code CLUSTER NODES} into {@link RedisClusterNode}s.
*
* @param clusterNodes
* @return
* @since 1.7
@@ -259,24 +259,8 @@ abstract public class Converters {
* @return
*/
public static Long toTimeMillis(String seconds, String microseconds) {
return NumberUtils.parseNumber(seconds, Long.class) * 1000L + NumberUtils.parseNumber(microseconds, Long.class)
/ 1000L;
return NumberUtils.parseNumber(seconds, Long.class) * 1000L
+ NumberUtils.parseNumber(microseconds, Long.class) / 1000L;
}
/**
* Merge multiple {@code byte} arrays into one array
* @param firstArray must not be {@literal null}
* @param additionalArrays must not be {@literal null}
* @return
*/
public static byte[][] mergeArrays(byte[] firstArray, byte[]... additionalArrays){
Assert.notNull(firstArray, "first array must not be null");
Assert.notNull(additionalArrays, "additional arrays must not be null");
byte[][] result = new byte[additionalArrays.length + 1][];
result[0] = firstArray;
System.arraycopy(additionalArrays, 0, result, 1, additionalArrays.length);
return result;
}
}

View File

@@ -65,6 +65,7 @@ import org.springframework.data.redis.core.ScanIteration;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.util.ByteUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
@@ -79,7 +80,7 @@ import redis.clients.jedis.ZParams;
* {@link RedisClusterConnection} implementation on top of {@link JedisCluster}.<br/>
* Uses the native {@link JedisCluster} api where possible and falls back to direct node communication using
* {@link Jedis} where needed.
*
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 1.7
@@ -126,7 +127,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
/**
* Create new {@link JedisClusterConnection} utilizing native connections via {@link JedisCluster} running commands
* across the cluster via given {@link ClusterCommandExecutor}.
*
*
* @param cluster must not be {@literal null}.
* @param executor must not be {@literal null}.
*/
@@ -894,7 +895,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public Long bitOp(BitOperation op, byte[] destination, byte[]... keys) {
byte[][] allKeys = Converters.mergeArrays(destination, keys);
byte[][] allKeys = ByteUtils.mergeArrays(destination, keys);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
try {
@@ -1332,7 +1333,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public Long sInterStore(byte[] destKey, byte[]... keys) {
byte[][] allKeys = Converters.mergeArrays(destKey, keys);
byte[][] allKeys = ByteUtils.mergeArrays(destKey, keys);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
try {
@@ -1392,7 +1393,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public Long sUnionStore(byte[] destKey, byte[]... keys) {
byte[][] allKeys = Converters.mergeArrays(destKey, keys);
byte[][] allKeys = ByteUtils.mergeArrays(destKey, keys);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
try {
@@ -1455,7 +1456,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public Long sDiffStore(byte[] destKey, byte[]... keys) {
byte[][] allKeys = Converters.mergeArrays(destKey, keys);
byte[][] allKeys = ByteUtils.mergeArrays(destKey, keys);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
try {
@@ -2083,7 +2084,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public Long zUnionStore(byte[] destKey, byte[]... sets) {
byte[][] allKeys = Converters.mergeArrays(destKey, sets);
byte[][] allKeys = ByteUtils.mergeArrays(destKey, sets);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
@@ -2104,7 +2105,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
byte[][] allKeys = Converters.mergeArrays(destKey, sets);
byte[][] allKeys = ByteUtils.mergeArrays(destKey, sets);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
@@ -2123,7 +2124,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public Long zInterStore(byte[] destKey, byte[]... sets) {
byte[][] allKeys = Converters.mergeArrays(destKey, sets);
byte[][] allKeys = ByteUtils.mergeArrays(destKey, sets);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
@@ -2140,7 +2141,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
byte[][] allKeys = Converters.mergeArrays(destKey, sets);
byte[][] allKeys = ByteUtils.mergeArrays(destKey, sets);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
@@ -3298,7 +3299,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public void pfMerge(byte[] destinationKey, byte[]... sourceKeys) {
byte[][] allKeys = Converters.mergeArrays(destinationKey, sourceKeys);
byte[][] allKeys = ByteUtils.mergeArrays(destinationKey, sourceKeys);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
try {
@@ -3592,6 +3593,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisClusterCommands#clusterGetMasterSlaveMap()
*/
@Override
public Map<RedisClusterNode, Collection<RedisClusterNode>> clusterGetMasterSlaveMap() {
List<NodeResult<Collection<RedisClusterNode>>> nodeResults = clusterCommandExecutor
@@ -3753,7 +3755,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
/**
* {@link Jedis} specific {@link ClusterCommandCallback}.
*
*
* @author Christoph Strobl
* @param <T>
* @since 1.7
@@ -3771,7 +3773,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
/**
* Jedis specific implementation of {@link ClusterNodeResourceProvider}.
*
*
* @author Christoph Strobl
* @since 1.7
*/
@@ -3781,7 +3783,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
/**
* Creates new {@link JedisClusterNodeResourceProvider}.
*
*
* @param cluster must not be {@literal null}.
*/
public JedisClusterNodeResourceProvider(JedisCluster cluster) {
@@ -3829,20 +3831,20 @@ public class JedisClusterConnection implements RedisClusterConnection {
/**
* Jedis specific implementation of {@link ClusterTopologyProvider}.
*
*
* @author Christoph Strobl
* @since 1.7
*/
static class JedisClusterTopologyProvider implements ClusterTopologyProvider {
private Object lock = new Object();
private final Object lock = new Object();
private final JedisCluster cluster;
private long time = 0;
private ClusterTopology cached;
/**
* Create new {@link JedisClusterTopologyProvider}.s
*
*
* @param cluster
*/
public JedisClusterTopologyProvider(JedisCluster cluster) {

View File

@@ -51,6 +51,7 @@ import org.springframework.data.redis.connection.util.ByteArraySet;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.util.ByteUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
@@ -102,7 +103,7 @@ public class LettuceClusterConnection extends LettuceConnection
/**
* Creates new {@link LettuceClusterConnection} using {@link RedisClusterClient} running commands across the cluster
* via given {@link ClusterCommandExecutor}.
*
*
* @param clusterClient must not be {@literal null}.
* @param executor must not be {@literal null}.
*/
@@ -131,6 +132,7 @@ public class LettuceClusterConnection extends LettuceConnection
* (non-Javadoc)
* @see org.springframework.data.redis.connection.lettuce.LettuceConnection#keys(byte[])
*/
@Override
public Set<byte[]> keys(final byte[] pattern) {
Assert.notNull(pattern, "Pattern must not be null!");
@@ -156,6 +158,7 @@ public class LettuceClusterConnection extends LettuceConnection
* (non-Javadoc)
* @see org.springframework.data.redis.connection.lettuce.LettuceConnection#flushAll()
*/
@Override
public void flushAll() {
clusterCommandExecutor.executeCommandOnAllNodes(new LettuceClusterCommandCallback<String>() {
@@ -1109,7 +1112,7 @@ public class LettuceClusterConnection extends LettuceConnection
@Override
public Long sInterStore(byte[] destKey, byte[]... keys) {
byte[][] allKeys = Converters.mergeArrays(destKey, keys);
byte[][] allKeys = ByteUtils.mergeArrays(destKey, keys);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
return super.sInterStore(destKey, keys);
@@ -1161,7 +1164,7 @@ public class LettuceClusterConnection extends LettuceConnection
@Override
public Long sUnionStore(byte[] destKey, byte[]... keys) {
byte[][] allKeys = Converters.mergeArrays(destKey, keys);
byte[][] allKeys = ByteUtils.mergeArrays(destKey, keys);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
return super.sUnionStore(destKey, keys);
@@ -1216,7 +1219,7 @@ public class LettuceClusterConnection extends LettuceConnection
@Override
public Long sDiffStore(byte[] destKey, byte[]... keys) {
byte[][] allKeys = Converters.mergeArrays(destKey, keys);
byte[][] allKeys = ByteUtils.mergeArrays(destKey, keys);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
return super.sDiffStore(destKey, keys);
@@ -1276,7 +1279,7 @@ public class LettuceClusterConnection extends LettuceConnection
@Override
public void pfMerge(byte[] destinationKey, byte[]... sourceKeys) {
byte[][] allKeys = Converters.mergeArrays(destinationKey, sourceKeys);
byte[][] allKeys = ByteUtils.mergeArrays(destinationKey, sourceKeys);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
try {
@@ -1318,7 +1321,7 @@ public class LettuceClusterConnection extends LettuceConnection
}
/*
*
*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.lettuce.LettuceConnection#getConfig(java.lang.String)
*/
@@ -1538,7 +1541,7 @@ public class LettuceClusterConnection extends LettuceConnection
/**
* Lettuce specific implementation of {@link ClusterCommandCallback}.
*
*
* @author Christoph Strobl
* @param <T>
* @since 1.7
@@ -1548,7 +1551,7 @@ public class LettuceClusterConnection extends LettuceConnection
/**
* Lettuce specific implementation of {@link MultiKeyClusterCommandCallback}.
*
*
* @author Christoph Strobl
* @param <T>
* @since 1.7
@@ -1560,7 +1563,7 @@ public class LettuceClusterConnection extends LettuceConnection
/**
* Lettuce specific implementation of {@link ClusterNodeResourceProvider}.
*
*
* @author Christoph Strobl
* @since 1.7
*/
@@ -1605,7 +1608,7 @@ public class LettuceClusterConnection extends LettuceConnection
/**
* Lettuce specific implementation of {@link ClusterTopologyProvider}.
*
*
* @author Christoph Strobl
* @since 1.7
*/

View File

@@ -17,6 +17,7 @@ package org.springframework.data.redis.core;
import java.util.Set;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.convert.IndexedData;
import org.springframework.data.redis.core.convert.RedisConverter;
@@ -122,7 +123,7 @@ class IndexWriter {
protected void removeKeyFromExistingIndexes(byte[] key, IndexedData indexedData) {
Assert.notNull(indexedData, "IndexedData must not be null!");
Set<byte[]> existingKeys = connection.keys(toBytes(indexedData.getKeySpace() + ":" + indexedData.getIndexName()
Set<byte[]> existingKeys = connection.keys(toBytes(indexedData.getKeyspace() + ":" + indexedData.getIndexName()
+ ":*"));
if (!CollectionUtils.isEmpty(existingKeys)) {
@@ -158,12 +159,12 @@ class IndexWriter {
return;
}
byte[] indexKey = toBytes(indexedData.getKeySpace() + ":" + indexedData.getIndexName() + ":");
byte[] indexKey = toBytes(indexedData.getKeyspace() + ":" + indexedData.getIndexName() + ":");
indexKey = ByteUtils.concat(indexKey, toBytes(value));
connection.sAdd(indexKey, key);
// keep track of indexes used for the object
connection.sAdd(ByteUtils.concatAll(toBytes(indexedData.getKeySpace() + ":"), key, toBytes(":idx")), indexKey);
connection.sAdd(ByteUtils.concatAll(toBytes(indexedData.getKeyspace() + ":"), key, toBytes(":idx")), indexKey);
} else {
throw new IllegalArgumentException(String.format("Cannot write index data for unknown index type %s",
indexedData.getClass()));
@@ -180,6 +181,14 @@ class IndexWriter {
return (byte[]) source;
}
return converter.getConversionService().convert(source, byte[].class);
if (converter.getConversionService().canConvert(source.getClass(), byte[].class)) {
return converter.getConversionService().convert(source, byte[].class);
}
throw new InvalidDataAccessApiUsageException(
String
.format(
"Cannot convert %s to binary representation for index key generation. Are you missing a Converter? Did you register a non PathBasedRedisIndexDefinition that might apply to a complex type?",
source.getClass()));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -40,12 +40,12 @@ import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.convert.CustomConversions;
import org.springframework.data.redis.core.convert.IndexResolverImpl;
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
import org.springframework.data.redis.core.convert.MappingRedisConverter;
import org.springframework.data.redis.core.convert.PathIndexResolver;
import org.springframework.data.redis.core.convert.RedisConverter;
import org.springframework.data.redis.core.convert.RedisData;
import org.springframework.data.redis.core.convert.ReferenceResolver;
import org.springframework.data.redis.core.convert.ReferenceResolverImpl;
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
@@ -86,8 +86,8 @@ import org.springframework.util.Assert;
* @author Christoph Strobl
* @since 1.7
*/
public class RedisKeyValueAdapter extends AbstractKeyValueAdapter implements ApplicationContextAware,
ApplicationListener<RedisKeyspaceEvent> {
public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
implements ApplicationContextAware, ApplicationListener<RedisKeyspaceEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisKeyValueAdapter.class);
@@ -131,8 +131,8 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter implements App
Assert.notNull(redisOps, "RedisOperations must not be null!");
Assert.notNull(mappingContext, "RedisMappingContext must not be null!");
MappingRedisConverter mappingConverter = new MappingRedisConverter(mappingContext, new IndexResolverImpl(
mappingContext), new ReferenceResolverImpl(this));
MappingRedisConverter mappingConverter = new MappingRedisConverter(mappingContext,
new PathIndexResolver(mappingContext), new ReferenceResolverImpl(redisOps));
mappingConverter.setCustomConversions(customConversions == null ? new CustomConversions() : customConversions);
mappingConverter.afterPropertiesSet();
@@ -173,7 +173,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter implements App
if (rdo.getId() == null) {
rdo.setId(id);
rdo.setId(converter.getConversionService().convert(id, String.class));
if (!(item instanceof RedisData)) {
KeyValuePersistentProperty idProperty = converter.getMappingContext().getPersistentEntity(item.getClass())
@@ -249,7 +249,10 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter implements App
*/
public <T> T get(Serializable id, Serializable keyspace, Class<T> type) {
final byte[] binId = createKey(keyspace, id);
String stringId = asString(id);
String stringKeyspace = asString(keyspace);
final byte[] binId = createKey(stringKeyspace, stringId);
Map<byte[], byte[]> raw = redisOps.execute(new RedisCallback<Map<byte[], byte[]>>() {
@@ -260,8 +263,8 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter implements App
});
RedisData data = new RedisData(raw);
data.setId(id);
data.setKeyspace(keyspace.toString());
data.setId(stringId);
data.setKeyspace(stringKeyspace);
return converter.read(type, data);
}
@@ -287,15 +290,17 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter implements App
if (o != null) {
final byte[] keyToDelete = createKey(asString(keyspace), asString(id));
redisOps.execute(new RedisCallback<Void>() {
@Override
public Void doInRedis(RedisConnection connection) throws DataAccessException {
connection.del(createKey(keyspace, id));
connection.del(keyToDelete);
connection.sRem(binKeyspace, binId);
new IndexWriter(connection, converter).removeKeyFromIndexes(keyspace.toString(), binId);
new IndexWriter(connection, converter).removeKeyFromIndexes(asString(keyspace), binId);
return null;
}
});
@@ -322,7 +327,8 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter implements App
Set<byte[]> members = connection.sMembers(binKeyspace);
for (byte[] id : members) {
rawData.add(connection.hGetAll(createKey(binKeyspace, id)));
rawData.add(connection
.hGetAll(createKey(asString(keyspace), getConverter().getConversionService().convert(id, String.class))));
}
return rawData;
@@ -349,7 +355,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter implements App
public Void doInRedis(RedisConnection connection) throws DataAccessException {
connection.del(toBytes(keyspace));
new IndexWriter(connection, converter).removeAllIndexes(keyspace.toString());
new IndexWriter(connection, converter).removeAllIndexes(asString(keyspace));
return null;
}
});
@@ -404,7 +410,12 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter implements App
// nothing to do
}
public byte[] createKey(Serializable keyspace, Serializable id) {
private String asString(Serializable value) {
return value instanceof String ? (String) value
: getConverter().getConversionService().convert(value, String.class);
}
public byte[] createKey(String keyspace, String id) {
return toBytes(keyspace + ":" + id);
}
@@ -533,8 +544,8 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter implements App
byte[] key = message.getBody();
final byte[] phantomKey = ByteUtils.concat(key, converter.getConversionService()
.convert(":phantom", byte[].class));
final byte[] phantomKey = ByteUtils.concat(key,
converter.getConversionService().convert(":phantom", byte[].class));
Map<byte[], byte[]> hash = ops.execute(new RedisCallback<Map<byte[], byte[]>>() {
@@ -569,40 +580,4 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter implements App
}
}
/**
* {@link ReferenceResolver} using {@link RedisKeyValueAdapter} to read and convert referenced entities.
*
* @author Christoph Strobl
* @since 1.7
*/
static class ReferenceResolverImpl implements ReferenceResolver {
private RedisKeyValueAdapter adapter;
ReferenceResolverImpl() {}
/**
* @param adapter must not be {@literal null}.
*/
public ReferenceResolverImpl(RedisKeyValueAdapter adapter) {
this.adapter = adapter;
}
/**
* @param adapter
*/
public void setAdapter(RedisKeyValueAdapter adapter) {
this.adapter = adapter;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.convert.ReferenceResolver#resolveReference(java.io.Serializable, java.io.Serializable, java.lang.Class)
*/
@Override
public <T> T resolveReference(Serializable id, Serializable keyspace, Class<T> type) {
return (T) adapter.get(id, keyspace, type);
}
}
}

View File

@@ -18,11 +18,11 @@ package org.springframework.data.redis.core;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.dao.DataAccessException;
import org.springframework.data.keyvalue.core.CriteriaAccessor;
@@ -32,6 +32,7 @@ import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.convert.RedisData;
import org.springframework.data.redis.repository.query.RedisOperationChain;
import org.springframework.data.redis.repository.query.RedisOperationChain.PathAndValue;
import org.springframework.data.redis.util.ByteUtils;
/**
@@ -65,8 +66,8 @@ class RedisQueryEngine extends QueryEngine<RedisKeyValueAdapter, RedisOperationC
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.QueryEngine#execute(java.lang.Object, java.lang.Object, int, int, java.io.Serializable, java.lang.Class)
*/
public <T> Collection<T> execute(final RedisOperationChain criteria, final Comparator<?> sort, int offset, int rows,
final Serializable keyspace, Class<T> type) {
public <T> Collection<T> execute(final RedisOperationChain criteria, final Comparator<?> sort, final int offset,
final int rows, final Serializable keyspace, Class<T> type) {
RedisCallback<Map<byte[], Map<byte[], byte[]>>> callback = new RedisCallback<Map<byte[], Map<byte[], byte[]>>>() {
@@ -81,12 +82,25 @@ class RedisQueryEngine extends QueryEngine<RedisKeyValueAdapter, RedisOperationC
i++;
}
Set<byte[]> allKeys = connection.sInter(keys);
List<byte[]> allKeys = new ArrayList<byte[]>();
if (!criteria.getSismember().isEmpty()) {
allKeys.addAll(connection.sInter(keys(keyspace + ":", criteria.getSismember())));
}
if (!criteria.getOrSismember().isEmpty()) {
allKeys.addAll(connection.sUnion(keys(keyspace + ":", criteria.getOrSismember())));
}
byte[] keyspaceBin = getAdapter().getConverter().getConversionService().convert(keyspace + ":", byte[].class);
final Map<byte[], Map<byte[], byte[]>> rawData = new LinkedHashMap<byte[], Map<byte[], byte[]>>();
if (allKeys.size() == 0 || allKeys.size() < offset) {
return Collections.emptyMap();
}
if (offset >= 0 && rows > 0) {
allKeys = allKeys.subList(Math.max(0, offset), Math.min(offset + rows, allKeys.size()));
}
for (byte[] id : allKeys) {
byte[] singleKey = ByteUtils.concat(keyspaceBin, id);
@@ -150,6 +164,23 @@ class RedisQueryEngine extends QueryEngine<RedisKeyValueAdapter, RedisOperationC
});
}
private byte[][] keys(String prefix, Collection<PathAndValue> source) {
byte[][] keys = new byte[source.size()][];
int i = 0;
for (PathAndValue pathAndValue : source) {
byte[] convertedValue = getAdapter().getConverter().getConversionService()
.convert(pathAndValue.getFirstValue(), byte[].class);
byte[] fullPath = getAdapter().getConverter().getConversionService()
.convert(prefix + pathAndValue.getPath() + ":", byte[].class);
keys[i] = ByteUtils.concat(fullPath, convertedValue);
i++;
}
return keys;
}
/**
* @author Christoph Strobl
* @since 1.7

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2016 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.convert;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Composite {@link IndexResolver} implementation that iterates over a given collection of delegate
* {@link IndexResolver} instances. <br />
* <br />
* <strong>NOTE</strong> {@link IndexedData} created by an {@link IndexResolver} can be overwritten by subsequent
* {@link IndexResolver}.
*
* @author Christoph Strobl
* @since 1.7
*/
public class CompositeIndexResolver implements IndexResolver {
private final List<IndexResolver> resolvers;
/**
* Create new {@link CompositeIndexResolver}.
*
* @param resolvers must not be {@literal null}.
*/
public CompositeIndexResolver(Collection<IndexResolver> resolvers) {
Assert.notNull(resolvers, "Resolvers must not be null!");
if (CollectionUtils.contains(resolvers.iterator(), null)) {
throw new IllegalArgumentException("Resolvers must no contain null values");
}
this.resolvers = new ArrayList<IndexResolver>(resolvers);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.convert.IndexResolver#resolveIndexesFor(org.springframework.data.util.TypeInformation, java.lang.Object)
*/
@Override
public Set<IndexedData> resolveIndexesFor(TypeInformation<?> typeInformation, Object value) {
if (resolvers.isEmpty()) {
return Collections.emptySet();
}
Set<IndexedData> data = new LinkedHashSet<IndexedData>();
for (IndexResolver resolver : resolvers) {
data.addAll(resolver.resolveIndexesFor(typeInformation, value));
}
return data;
}
}

View File

@@ -36,6 +36,6 @@ public interface IndexedData {
*
* @return
*/
String getKeySpace();
String getKeyspace();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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,7 +15,7 @@
*/
package org.springframework.data.redis.core.convert;
import org.springframework.data.redis.core.index.IndexConfiguration;
import org.springframework.data.redis.core.index.ConfigurableIndexDefinitionProvider;
/**
* {@link MappingConfiguration} is used for programmatic configuration of secondary indexes, key prefixes, expirations
@@ -26,7 +26,7 @@ import org.springframework.data.redis.core.index.IndexConfiguration;
*/
public class MappingConfiguration {
private final IndexConfiguration indexConfiguration;
private final ConfigurableIndexDefinitionProvider indexConfiguration;
private final KeyspaceConfiguration keyspaceConfiguration;
/**
@@ -35,7 +35,8 @@ public class MappingConfiguration {
* @param indexConfiguration must not be {@literal null}.
* @param keyspaceConfiguration must not be {@literal null}.
*/
public MappingConfiguration(IndexConfiguration indexConfiguration, KeyspaceConfiguration keyspaceConfiguration) {
public MappingConfiguration(ConfigurableIndexDefinitionProvider indexConfiguration,
KeyspaceConfiguration keyspaceConfiguration) {
this.indexConfiguration = indexConfiguration;
this.keyspaceConfiguration = keyspaceConfiguration;
@@ -44,7 +45,7 @@ public class MappingConfiguration {
/**
* @return never {@literal null}.
*/
public IndexConfiguration getIndexConfiguration() {
public ConfigurableIndexDefinitionProvider getIndexConfiguration() {
return indexConfiguration;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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,7 +15,6 @@
*/
package org.springframework.data.redis.core.convert;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -138,7 +137,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
this.referenceResolver = referenceResolver;
this.indexResolver = indexResolver != null ? indexResolver : new IndexResolverImpl(this.mappingContext);
this.indexResolver = indexResolver != null ? indexResolver : new PathIndexResolver(this.mappingContext);
}
/*
@@ -210,35 +209,29 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
if (persistentProperty.isMap()) {
if (conversionService.canConvert(byte[].class, persistentProperty.getMapValueType())) {
accessor.setProperty(
persistentProperty,
readMapOfSimpleTypes(currentPath, persistentProperty.getType(), persistentProperty.getComponentType(),
persistentProperty.getMapValueType(), source));
accessor.setProperty(persistentProperty, readMapOfSimpleTypes(currentPath, persistentProperty.getType(),
persistentProperty.getComponentType(), persistentProperty.getMapValueType(), source));
} else {
accessor.setProperty(
persistentProperty,
readMapOfComplexTypes(currentPath, persistentProperty.getType(), persistentProperty.getComponentType(),
persistentProperty.getMapValueType(), source));
accessor.setProperty(persistentProperty, readMapOfComplexTypes(currentPath, persistentProperty.getType(),
persistentProperty.getComponentType(), persistentProperty.getMapValueType(), source));
}
}
else if (persistentProperty.isCollectionLike()) {
if (conversionService.canConvert(byte[].class, persistentProperty.getComponentType())) {
accessor.setProperty(
persistentProperty,
readCollectionOfSimpleTypes(currentPath, persistentProperty.getType(), persistentProperty
.getTypeInformation().getComponentType().getActualType().getType(), source));
accessor.setProperty(persistentProperty,
readCollectionOfSimpleTypes(currentPath, persistentProperty.getType(),
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(), source));
} else {
accessor.setProperty(
persistentProperty,
readCollectionOfComplexTypes(currentPath, persistentProperty.getType(), persistentProperty
.getTypeInformation().getComponentType().getActualType().getType(), source.getBucket()));
accessor.setProperty(persistentProperty,
readCollectionOfComplexTypes(currentPath, persistentProperty.getType(),
persistentProperty.getTypeInformation().getComponentType().getActualType().getType(),
source.getBucket()));
}
} else if (persistentProperty.isEntity()
&& !conversionService.canConvert(byte[].class, persistentProperty.getTypeInformation().getActualType()
.getType())) {
} else if (persistentProperty.isEntity() && !conversionService.canConvert(byte[].class,
persistentProperty.getTypeInformation().getActualType().getType())) {
Class<?> targetType = persistentProperty.getTypeInformation().getActualType().getType();
@@ -284,8 +277,8 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
@Override
public void doWithAssociation(Association<KeyValuePersistentProperty> association) {
String currentPath = !path.isEmpty() ? path + "." + association.getInverse().getName() : association
.getInverse().getName();
String currentPath = !path.isEmpty() ? path + "." + association.getInverse().getName()
: association.getInverse().getName();
if (association.getInverse().isCollectionLike()) {
@@ -299,11 +292,10 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
String referenceKey = fromBytes(entry.getValue(), String.class);
String[] args = referenceKey.split(":");
Object loadedObject = referenceResolver.resolveReference(args[1], args[0], association.getInverse()
.getActualType());
Map<byte[], byte[]> rawHash = referenceResolver.resolveReference(args[1], args[0]);
if (loadedObject != null) {
target.add(loadedObject);
if (!CollectionUtils.isEmpty(rawHash)) {
target.add(read(association.getInverse().getActualType(), new RedisData(rawHash)));
}
}
@@ -319,11 +311,12 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
String key = fromBytes(binKey, String.class);
String[] args = key.split(":");
Object loadedObject = referenceResolver.resolveReference(args[1], args[0], association.getInverse()
.getActualType());
if (loadedObject != null) {
accessor.setProperty(association.getInverse(), loadedObject);
Map<byte[], byte[]> rawHash = referenceResolver.resolveReference(args[1], args[0]);
if (!CollectionUtils.isEmpty(rawHash)) {
accessor.setProperty(association.getInverse(),
read(association.getInverse().getActualType(), new RedisData(rawHash)));
}
}
}
@@ -345,7 +338,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
sink.setKeyspace(entity.getKeySpace());
writeInternal(entity.getKeySpace(), "", source, entity.getTypeInformation(), sink);
sink.setId((Serializable) entity.getIdentifierAccessor(source).getIdentifier());
sink.setId(getConversionService().convert(entity.getIdentifierAccessor(source).getIdentifier(), String.class));
Long ttl = entity.getTimeToLiveAccessor().getTimeToLive(source);
if (ttl != null && ttl > 0) {
@@ -748,8 +741,8 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
/**
* @author Christoph Strobl
*/
private static class ConverterAwareParameterValueProvider implements
PropertyValueProvider<KeyValuePersistentProperty> {
private static class ConverterAwareParameterValueProvider
implements PropertyValueProvider<KeyValuePersistentProperty> {
private final RedisData source;
private final ConversionService conversionService;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -25,9 +25,13 @@ import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.redis.core.index.ConfigurableIndexDefinitionProvider;
import org.springframework.data.redis.core.index.IndexConfiguration;
import org.springframework.data.redis.core.index.IndexConfiguration.RedisIndexSetting;
import org.springframework.data.redis.core.index.IndexDefinition;
import org.springframework.data.redis.core.index.IndexDefinition.Condition;
import org.springframework.data.redis.core.index.IndexDefinition.IndexingContext;
import org.springframework.data.redis.core.index.Indexed;
import org.springframework.data.redis.core.index.SimpleIndexDefinition;
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
import org.springframework.data.util.ClassTypeInformation;
@@ -41,24 +45,24 @@ import org.springframework.util.Assert;
* @author Christoph Strobl
* @since 1.7
*/
public class IndexResolverImpl implements IndexResolver {
public class PathIndexResolver implements IndexResolver {
private IndexConfiguration indexConfiguration;
private ConfigurableIndexDefinitionProvider indexConfiguration;
private RedisMappingContext mappingContext;
/**
* Creates new {@link IndexResolverImpl} with empty {@link IndexConfiguration}.
* Creates new {@link PathIndexResolver} with empty {@link IndexConfiguration}.
*/
public IndexResolverImpl() {
public PathIndexResolver() {
this(new RedisMappingContext());
}
/**
* Creates new {@link IndexResolverImpl} with given {@link IndexConfiguration}.
* Creates new {@link PathIndexResolver} with given {@link IndexConfiguration}.
*
* @param mapppingContext must not be {@literal null}.
*/
public IndexResolverImpl(RedisMappingContext mappingContext) {
public PathIndexResolver(RedisMappingContext mappingContext) {
Assert.notNull(mappingContext, "MappingContext must not be null!");
this.mappingContext = mappingContext;
@@ -71,21 +75,16 @@ public class IndexResolverImpl implements IndexResolver {
*/
public Set<IndexedData> resolveIndexesFor(TypeInformation<?> typeInformation, Object value) {
return doResolveIndexesFor(mappingContext.getPersistentEntity(typeInformation).getKeySpace(), "", typeInformation,
value);
null, value);
}
private Set<IndexedData> doResolveIndexesFor(final String keyspace, final String path,
TypeInformation<?> typeInformation, Object value) {
TypeInformation<?> typeInformation, PersistentProperty<?> fallback, Object value) {
RedisPersistentEntity<?> entity = mappingContext.getPersistentEntity(typeInformation);
if (entity == null) {
IndexedData index = resolveIndex(keyspace, path, null, value);
if (index != null) {
return Collections.singleton(index);
}
return Collections.emptySet();
return resolveIndex(keyspace, path, fallback, value);
}
final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(value);
@@ -102,14 +101,9 @@ public class IndexResolverImpl implements IndexResolver {
if (propertyValue != null) {
TypeInformation<?> typeHint = persistentProperty.isMap() ? persistentProperty.getTypeInformation()
.getMapValueType() : persistentProperty.getTypeInformation().getActualType();
IndexedData index = resolveIndex(keyspace, currentPath, persistentProperty, propertyValue);
if (index != null) {
indexes.add(index);
}
TypeInformation<?> typeHint = persistentProperty.isMap()
? persistentProperty.getTypeInformation().getMapValueType()
: persistentProperty.getTypeInformation().getActualType();
if (persistentProperty.isMap()) {
@@ -117,7 +111,7 @@ public class IndexResolverImpl implements IndexResolver {
TypeInformation<?> typeToUse = updateTypeHintForActualValue(typeHint, entry.getValue());
indexes.addAll(doResolveIndexesFor(keyspace, currentPath + "." + entry.getKey(),
typeToUse.getActualType(), entry.getValue()));
typeToUse.getActualType(), persistentProperty, entry.getValue()));
}
} else if (persistentProperty.isCollectionLike()) {
@@ -125,7 +119,8 @@ public class IndexResolverImpl implements IndexResolver {
for (Object listValue : (Iterable<?>) propertyValue) {
TypeInformation<?> typeToUse = updateTypeHintForActualValue(typeHint, listValue);
indexes.addAll(doResolveIndexesFor(keyspace, currentPath, typeToUse.getActualType(), listValue));
indexes.addAll(
doResolveIndexesFor(keyspace, currentPath, typeToUse.getActualType(), persistentProperty, listValue));
}
}
@@ -133,7 +128,10 @@ public class IndexResolverImpl implements IndexResolver {
|| persistentProperty.getTypeInformation().getActualType().equals(ClassTypeInformation.OBJECT)) {
typeHint = updateTypeHintForActualValue(typeHint, propertyValue);
indexes.addAll(doResolveIndexesFor(keyspace, currentPath, typeHint.getActualType(), propertyValue));
indexes.addAll(doResolveIndexesFor(keyspace, currentPath, typeHint.getActualType(), persistentProperty,
propertyValue));
} else {
indexes.addAll(resolveIndex(keyspace, currentPath, persistentProperty, propertyValue));
}
}
@@ -157,37 +155,55 @@ public class IndexResolverImpl implements IndexResolver {
return indexes;
}
protected IndexedData resolveIndex(String keyspace, String propertyPath, PersistentProperty<?> property, Object value) {
protected Set<IndexedData> resolveIndex(String keyspace, String propertyPath, PersistentProperty<?> property,
Object value) {
if (value == null) {
return null;
return Collections.emptySet();
}
String path = normalizeIndexPath(propertyPath, property);
Set<IndexedData> data = new LinkedHashSet<IndexedData>();
if (indexConfiguration.hasIndexFor(keyspace, path)) {
// FIXME it seems there is a mis-match between IndexConfiguration
// resolving many RedisIndexSetting objects to resolving a single
// IndexData in this method.
RedisIndexSetting indexSetting = indexConfiguration.getIndexDefinitionsFor(keyspace, path).iterator().next();
return new SimpleIndexedPropertyValue(keyspace, indexSetting.getIndexName(), value);
IndexingContext context = new IndexingContext(keyspace, path,
property != null ? property.getTypeInformation() : ClassTypeInformation.OBJECT);
for (IndexDefinition indexDefinition : indexConfiguration.getIndexDefinitionsFor(keyspace, path)) {
if (!verifyConditions(indexDefinition.getConditions(), value, context)) {
continue;
}
data.add(new SimpleIndexedPropertyValue(keyspace, indexDefinition.getIndexName(),
indexDefinition.valueTransformer().convert(value)));
}
}
else if (property != null && property.isAnnotationPresent(Indexed.class)) {
Indexed indexed = property.findAnnotation(Indexed.class);
SimpleIndexDefinition indexDefinition = new SimpleIndexDefinition(keyspace, path);
indexConfiguration.addIndexDefinition(indexDefinition);
indexConfiguration.addIndexDefinition(new RedisIndexSetting(keyspace, path, indexed.type()));
data.add(new SimpleIndexedPropertyValue(keyspace, path, indexDefinition.valueTransformer().convert(value)));
}
return data;
}
switch (indexed.type()) {
case SIMPLE:
return new SimpleIndexedPropertyValue(keyspace, path, value);
default:
throw new IllegalArgumentException(String.format("Unsupported index type '%s' for path '%s'.",
indexed.type(), path));
@SuppressWarnings({ "rawtypes", "unchecked" })
private boolean verifyConditions(Iterable<Condition<?>> conditions, Object value, IndexingContext context) {
for (Condition condition : conditions) {
// TODO: generics lookup
if (!condition.matches(value, context)) {
return false;
}
}
return null;
return true;
}
private String normalizeIndexPath(String path, PersistentProperty<?> property) {

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.data.redis.core.convert;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
@@ -34,7 +33,7 @@ import org.springframework.util.Assert;
public class RedisData {
private String keyspace;
private Serializable id;
private String id;
private Bucket bucket;
private Set<IndexedData> indexedData;
@@ -74,14 +73,14 @@ public class RedisData {
*
* @param id
*/
public void setId(Serializable id) {
public void setId(String id) {
this.id = id;
}
/**
* @return
*/
public Serializable getId() {
public String getId() {
return this.id;
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.redis.core.convert;
import java.io.Serializable;
import java.util.Map;
import org.springframework.data.annotation.Reference;
@@ -30,8 +31,7 @@ public interface ReferenceResolver {
/**
* @param id must not be {@literal null}.
* @param keyspace must not be {@literal null}.
* @param type must not be {@literal null}.
* @return {@literal null} if referenced object does not exist.
*/
<T> T resolveReference(Serializable id, Serializable keyspace, Class<T> type);
Map<byte[], byte[]> resolveReference(Serializable id, String keyspace);
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2016 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.convert;
import java.io.Serializable;
import java.util.Map;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisKeyValueAdapter;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.convert.BinaryConverters.StringToBytesConverter;
import org.springframework.util.Assert;
/**
* {@link ReferenceResolver} using {@link RedisKeyValueAdapter} to read raw data.
*
* @author Christoph Strobl
* @since 1.7
*/
public class ReferenceResolverImpl implements ReferenceResolver {
private final RedisOperations<?, ?> redisOps;
private final StringToBytesConverter converter;
/**
* @param redisOperations must not be {@literal null}.
*/
public ReferenceResolverImpl(RedisOperations<?, ?> redisOperations) {
Assert.notNull(redisOperations);
this.redisOps = redisOperations;
this.converter = new StringToBytesConverter();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.convert.ReferenceResolver#resolveReference(java.io.Serializable, java.io.Serializable, java.lang.Class)
*/
@Override
public Map<byte[], byte[]> resolveReference(Serializable id, String keyspace) {
final byte[] key = converter.convert(keyspace + ":" + id);
return redisOps.execute(new RedisCallback<Map<byte[], byte[]>>() {
@Override
public Map<byte[], byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
return connection.hGetAll(key);
}
});
}
}

View File

@@ -67,7 +67,7 @@ public class SimpleIndexedPropertyValue implements IndexedData {
* @see org.springframework.data.redis.core.convert.IndexedData#getKeySpace()
*/
@Override
public String getKeySpace() {
public String getKeyspace() {
return this.keyspace;
}

View File

@@ -16,13 +16,16 @@
package org.springframework.data.redis.core.convert;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
import org.springframework.data.redis.core.index.IndexConfiguration;
import org.springframework.data.redis.core.index.IndexConfiguration.RedisIndexSetting;
import org.springframework.data.redis.core.index.ConfigurableIndexDefinitionProvider;
import org.springframework.data.redis.core.index.IndexDefinition;
import org.springframework.data.redis.core.index.SpelIndexDefinition;
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.BeanResolver;
@@ -35,16 +38,19 @@ import org.springframework.util.Assert;
* An {@link IndexResolver} that resolves {@link IndexedData} using a {@link SpelExpressionParser}.
*
* @author Rob Winch
* @author Christoph Strobl
* @since 1.7
*/
public class SpelIndexResolver implements IndexResolver {
private final IndexConfiguration settings;
private final ConfigurableIndexDefinitionProvider settings;
private final SpelExpressionParser parser;
private final RedisMappingContext mappingContext;
private BeanResolver beanResolver;
private Map<SpelIndexDefinition, Expression> expressionCache;
/**
* Creates a new instance using a default {@link SpelExpressionParser}.
*
@@ -66,6 +72,7 @@ public class SpelIndexResolver implements IndexResolver {
Assert.notNull(parser, "SpelExpressionParser must not be null!");
this.mappingContext = mappingContext;
this.settings = mappingContext.getMappingConfiguration().getIndexConfiguration();
this.expressionCache = new HashMap<SpelIndexDefinition, Expression>();
this.parser = parser;
}
@@ -88,26 +95,41 @@ public class SpelIndexResolver implements IndexResolver {
Set<IndexedData> indexes = new HashSet<IndexedData>();
for (RedisIndexSetting setting : settings.getIndexDefinitionsFor(keyspace)) {
for (IndexDefinition setting : settings.getIndexDefinitionsFor(keyspace)) {
Expression expression = parser.parseExpression(setting.getPath());
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(value);
context.setVariable("this", value);
if (setting instanceof SpelIndexDefinition) {
if (beanResolver != null) {
context.setBeanResolver(beanResolver);
}
Expression expression = getAndCacheIfAbsent((SpelIndexDefinition) setting);
Object index = expression.getValue(context);
if (index != null) {
indexes.add(new SimpleIndexedPropertyValue(keyspace, setting.getIndexName(), index));
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(value);
context.setVariable("this", value);
if (beanResolver != null) {
context.setBeanResolver(beanResolver);
}
Object index = expression.getValue(context);
if (index != null) {
indexes.add(new SimpleIndexedPropertyValue(keyspace, setting.getIndexName(), index));
}
}
}
return indexes;
}
private Expression getAndCacheIfAbsent(SpelIndexDefinition indexDefinition) {
if (expressionCache.containsKey(indexDefinition)) {
return expressionCache.get(indexDefinition);
}
Expression expression = parser.parseExpression(indexDefinition.getExpression());
expressionCache.put(indexDefinition, expression);
return expression;
}
/**
* Allows setting the BeanResolver
*

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2016 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.index;
/**
* {@link IndexDefinitionProvider} that allows registering new {@link IndexDefinition} via
* {@link IndexDefinitionRegistry}.
*
* @author Christoph Strobl
* @since 1.7
*/
public interface ConfigurableIndexDefinitionProvider extends IndexDefinitionProvider, IndexDefinitionRegistry {
}

View File

@@ -16,13 +16,13 @@
package org.springframework.data.redis.core.index;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
@@ -33,70 +33,55 @@ import org.springframework.util.ObjectUtils;
* @author Rob Winch
* @since 1.7
*/
public class IndexConfiguration {
public class IndexConfiguration implements ConfigurableIndexDefinitionProvider {
private final Set<RedisIndexSetting> definitions;
private final Set<IndexDefinition> definitions;
/**
* Creates new empty {@link IndexConfiguration}.
*/
public IndexConfiguration() {
this.definitions = new CopyOnWriteArraySet<RedisIndexSetting>();
for (RedisIndexSetting initial : initialConfiguration()) {
this.definitions = new CopyOnWriteArraySet<IndexDefinition>();
for (IndexDefinition initial : initialConfiguration()) {
addIndexDefinition(initial);
}
}
/**
* Checks if an index is defined for a given keyspace and property path.
*
* @param keyspace
* @param path
* @return true if index is defined.
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.IndexDefinitionProvider#hasIndexFor(java.io.Serializable)
*/
@Override
public boolean hasIndexFor(Serializable keyspace) {
return !getIndexDefinitionsFor(keyspace).isEmpty();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.IndexDefinitionProvider#hasIndexFor(java.io.Serializable, java.lang.String)
*/
public boolean hasIndexFor(Serializable keyspace, String path) {
for (IndexType type : IndexType.values()) {
RedisIndexSetting def = getIndexDefinition(keyspace, path, type);
if (def != null) {
return true;
}
}
return false;
return !getIndexDefinitionsFor(keyspace, path).isEmpty();
}
/**
* Get the list of {@link RedisIndexSetting} for a given keyspace and property path.
*
* @param keyspace
* @param path
* @return never {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.IndexDefinitionProvider#getIndexDefinitionsFor(java.io.Serializable, java.lang.String)
*/
public List<RedisIndexSetting> getIndexDefinitionsFor(Serializable keyspace, String path) {
List<RedisIndexSetting> indexDefinitions = new ArrayList<RedisIndexSetting>();
for (IndexType type : IndexType.values()) {
RedisIndexSetting def = getIndexDefinition(keyspace, path, type);
if (def != null) {
indexDefinitions.add(def);
}
}
return indexDefinitions;
public Set<IndexDefinition> getIndexDefinitionsFor(Serializable keyspace, String path) {
return getIndexDefinitions(keyspace, path, Object.class);
}
/**
* Gets all of the {@link RedisIndexSetting} for a given keyspace.
*
* @param keyspace the keyspace to get
* @return never {@literal null}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.IndexDefinitionProvider#getIndexDefinitionsFor(java.io.Serializable)
*/
public List<RedisIndexSetting> getIndexDefinitionsFor(Serializable keyspace) {
public Set<IndexDefinition> getIndexDefinitionsFor(Serializable keyspace) {
List<RedisIndexSetting> indexDefinitions = new ArrayList<RedisIndexSetting>();
Set<IndexDefinition> indexDefinitions = new LinkedHashSet<IndexDefinition>();
for (RedisIndexSetting indexDef : definitions) {
for (IndexDefinition indexDef : definitions) {
if (indexDef.getKeyspace().equals(keyspace)) {
indexDefinitions.add(indexDef);
}
@@ -105,26 +90,33 @@ public class IndexConfiguration {
return indexDefinitions;
}
/**
* Add given {@link RedisIndexSetting}.
*
* @param indexDefinition must not be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.IndexDefinitionRegistry#addIndexDefinition(org.springframework.data.redis.core.index.IndexDefinition)
*/
public void addIndexDefinition(RedisIndexSetting indexDefinition) {
public void addIndexDefinition(IndexDefinition indexDefinition) {
Assert.notNull(indexDefinition, "RedisIndexDefinition must not be null in order to be added.");
this.definitions.add(indexDefinition);
}
private RedisIndexSetting getIndexDefinition(Serializable keyspace, String path, IndexType type) {
private Set<IndexDefinition> getIndexDefinitions(Serializable keyspace, String path, Class<?> type) {
for (RedisIndexSetting indexDef : definitions) {
if (indexDef.getKeyspace().equals(keyspace) && indexDef.getPath().equals(path) && indexDef.getType().equals(type)) {
return indexDef;
Set<IndexDefinition> def = new LinkedHashSet<IndexDefinition>();
for (IndexDefinition indexDef : definitions) {
if (ClassUtils.isAssignable(type, indexDef.getClass()) && indexDef.getKeyspace().equals(keyspace)) {
if (indexDef instanceof PathBasedRedisIndexDefinition) {
if (ObjectUtils.nullSafeEquals(((PathBasedRedisIndexDefinition) indexDef).getPath(), path)) {
def.add(indexDef);
}
} else {
def.add(indexDef);
}
}
}
return null;
return def;
}
/**
@@ -132,93 +124,8 @@ public class IndexConfiguration {
*
* @return must not return {@literal null}.
*/
protected Iterable<RedisIndexSetting> initialConfiguration() {
protected Iterable<? extends IndexDefinition> initialConfiguration() {
return Collections.emptySet();
}
/**
* @author Christoph Strobl
* @author Rob Winch
* @since 1.7
*/
public static class RedisIndexSetting {
private final Serializable keyspace;
private final String path;
private final String indexName;
private final IndexType type;
public RedisIndexSetting(Serializable keyspace, String path) {
this(keyspace, path, null);
}
public RedisIndexSetting(Serializable keyspace, String path, IndexType type) {
this(keyspace, path, path, type);
}
public RedisIndexSetting(Serializable keyspace, String path, String indexName, IndexType type) {
this.keyspace = keyspace;
this.path = path;
this.indexName = indexName;
this.type = type == null ? IndexType.SIMPLE : type;
}
public String getIndexName() {
return indexName;
}
public Serializable getKeyspace() {
return keyspace;
}
public String getPath() {
return path;
}
public IndexType getType() {
return type;
}
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(keyspace);
result += ObjectUtils.nullSafeHashCode(path);
result += ObjectUtils.nullSafeHashCode(indexName);
result += ObjectUtils.nullSafeHashCode(type);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof RedisIndexSetting)) {
return false;
}
RedisIndexSetting that = (RedisIndexSetting) obj;
if (!ObjectUtils.nullSafeEquals(this.keyspace, that.keyspace)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.path, that.path)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.indexName, that.indexName)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.type, that.type)) {
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2016 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.index;
import java.util.Collection;
import org.springframework.data.util.TypeInformation;
/**
* {@link IndexDefinition} allow to set up a blueprint for creating secondary index structures in Redis. Setting up
* conditions allows to define {@link Condition} that have to be passed in order to add a value to the index. This
* allows to fine grained tune the I index structure. {@link IndexValueTransformer} gets applied to the raw value for
* creating the actual index entry.
*
* @author Christoph Strobl
* @since 1.7
*/
public interface IndexDefinition {
/**
* @return never {@literal null}.
*/
String getKeyspace();
/**
* @return never {@literal null}.
*/
Collection<Condition<?>> getConditions();
/**
* @return never {@literal null}.
*/
IndexValueTransformer valueTransformer();
/**
* @return never {@literal null}.
*/
String getIndexName();
/**
* @author Christoph Strobl
* @since 1.7
* @param <T>
*/
public static interface Condition<T> {
boolean matches(T value, IndexingContext context);
}
/**
* Context in which a particular value is about to get indexed.
*
* @author Christoph Strobl
* @since 1.7
*/
public class IndexingContext {
private final String keyspace;
private final String path;
private final TypeInformation<?> typeInformation;
public IndexingContext(String keyspace, String path, TypeInformation<?> typeInformation) {
this.keyspace = keyspace;
this.path = path;
this.typeInformation = typeInformation;
}
public String getKeyspace() {
return keyspace;
}
public String getPath() {
return path;
}
public TypeInformation<?> getTypeInformation() {
return typeInformation;
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2016 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.index;
import java.io.Serializable;
import java.util.Set;
/**
* {@link IndexDefinitionProvider} give access to {@link IndexDefinition}s for creating secondary index structures.
*
* @author Christoph Strobl
* @since 1.7
*/
public interface IndexDefinitionProvider {
/**
* Gets all of the {@link RedisIndexSetting} for a given keyspace.
*
* @param keyspace the keyspace to get
* @return never {@literal null}
*/
boolean hasIndexFor(Serializable keyspace);
/**
* Checks if an index is defined for a given keyspace and property path.
*
* @param keyspace
* @param path
* @return true if index is defined.
*/
boolean hasIndexFor(Serializable keyspace, String path);
/**
* Get the list of {@link IndexDefinition} for a given keyspace.
*
* @param keyspace
* @return never {@literal null}.
*/
Set<IndexDefinition> getIndexDefinitionsFor(Serializable keyspace);
/**
* Get the list of {@link IndexDefinition} for a given keyspace and property path.
*
* @param keyspace
* @param path
* @return never {@literal null}.
*/
Set<IndexDefinition> getIndexDefinitionsFor(Serializable keyspace, String path);
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2016 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.index;
/**
* Registry that allows adding {@link IndexDefinition}.
*
* @author Christoph Strobl
* @since 1.7
*/
public interface IndexDefinitionRegistry {
/**
* Add given {@link RedisIndexSetting}.
*
* @param indexDefinition must not be {@literal null}.
*/
void addIndexDefinition(IndexDefinition indexDefinition);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2016 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.index;
import org.springframework.core.convert.converter.Converter;
/**
* {@link Converter} implementation that is used to transform values for usage in a particular secondary index.
*
* @author Christoph Strobl
* @since 1.7
*/
public interface IndexValueTransformer extends Converter<Object, Object> {
}

View File

@@ -22,7 +22,9 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Mark property to be included in a secondary index.
* Mark properties value to be included in a secondary index. <br />
* Uses Redos {@literal SET} for storage. <br />
* The value will be part of the key built for the index.
*
* @author Christoph Strobl
* @since 1.7
@@ -32,11 +34,4 @@ import java.lang.annotation.Target;
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
public @interface Indexed {
/**
* Type of index to use.
*
* @return
*/
IndexType type() default IndexType.SIMPLE;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2016 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.
@@ -16,15 +16,16 @@
package org.springframework.data.redis.core.index;
/**
* {@link IndexType} indicates structure of index.
* {@link IndexDefinition} that is based on a property paths.
*
* @author Christoph Strobl
* @since 1.7
*/
public enum IndexType {
public interface PathBasedRedisIndexDefinition extends IndexDefinition {
/**
* Simple indicates usage of Redis Set for persisting data.
* @return can be {@literal null}.
*/
SIMPLE
String getPath();
}

View File

@@ -0,0 +1,263 @@
/*
* Copyright 2016 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.index;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Base {@link IndexDefinition} implementation.
*
* @author Christoph Strobl
* @since 1.7
*/
public abstract class RedisIndexDefinition implements IndexDefinition {
private final String keyspace;
private final String indexName;
private final String path;
private List<Condition<?>> conditions;
private IndexValueTransformer valueTransformer;
/**
* Creates new {@link RedisIndexDefinition}.
*
* @param keyspace
* @param path
* @param indexName
*/
protected RedisIndexDefinition(String keyspace, String path, String indexName) {
this.keyspace = keyspace;
this.indexName = indexName;
this.path = path;
this.conditions = new ArrayList<IndexDefinition.Condition<?>>();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.IndexDefinition#getKeyspace()
*/
@Override
public String getKeyspace() {
return keyspace;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.IndexDefinition#getConditions()
*/
@Override
public Collection<Condition<?>> getConditions() {
return Collections.unmodifiableCollection(conditions);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.IndexDefinition#valueTransformer()
*/
@Override
public IndexValueTransformer valueTransformer() {
return valueTransformer != null ? valueTransformer : NoOpValueTransformer.INSTANCE;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.IndexDefinition#getIndexName()
*/
@Override
public String getIndexName() {
return indexName;
}
public String getPath() {
return this.path;
}
protected void addCondition(Condition<?> condition) {
Assert.notNull(condition, "Condition must not be null!");
this.conditions.add(condition);
}
public void setValueTransformer(IndexValueTransformer valueTransformer) {
this.valueTransformer = valueTransformer;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(indexName);
return result + ObjectUtils.nullSafeHashCode(keyspace);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof RedisIndexDefinition)) {
return false;
}
RedisIndexDefinition that = (RedisIndexDefinition) obj;
if (!ObjectUtils.nullSafeEquals(this.keyspace, that.keyspace)) {
return false;
}
return ObjectUtils.nullSafeEquals(this.indexName, that.indexName);
}
/**
* @author Christoph Strobl
* @since 1.7
*/
public static enum NoOpValueTransformer implements IndexValueTransformer {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Object convert(Object source) {
return source;
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
public static enum LowercaseIndexValueTransformer implements IndexValueTransformer {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Object convert(Object source) {
if (!(source instanceof String)) {
return source;
}
return ((String) source).toLowerCase();
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
public static class CompositeValueTransformer implements IndexValueTransformer {
private final List<IndexValueTransformer> transformers = new ArrayList<IndexValueTransformer>();
public CompositeValueTransformer(Collection<IndexValueTransformer> transformers) {
this.transformers.addAll(transformers);
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Object convert(Object source) {
Object tmp = source;
for (IndexValueTransformer transformer : transformers) {
tmp = transformer.convert(tmp);
}
return tmp;
}
}
/**
* @author Christoph Strobl
* @since 1.7
* @param <T>
*/
public static class OrCondition<T> implements Condition<T> {
private final List<Condition<T>> conditions = new ArrayList<Condition<T>>();
public OrCondition(Collection<Condition<T>> conditions) {
this.conditions.addAll(conditions);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.IndexDefinition.Condition#matches(java.lang.Object, org.springframework.data.redis.core.index.IndexDefinition.IndexingContext)
*/
@Override
public boolean matches(T value, IndexingContext context) {
for (Condition<T> condition : conditions) {
if (condition.matches(value, context)) {
return true;
}
}
return false;
}
}
/**
* @author Christoph Strobl
* @since 1.7
*/
public static class PathCondition implements Condition<Object> {
private final String path;
public PathCondition(String path) {
this.path = path;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.IndexDefinition.Condition#matches(java.lang.Object, org.springframework.data.redis.core.index.IndexDefinition.IndexingContext)
*/
@Override
public boolean matches(Object value, IndexingContext context) {
if (!StringUtils.hasText(path)) {
return true;
}
return ObjectUtils.nullSafeEquals(context.getPath(), path);
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2016 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.index;
/**
* {@link PathBasedRedisIndexDefinition} for including property values in a secondary index. <br />
* Uses Redis {@literal SET} for storage. <br />
*
* @author Christoph Strobl
* @since 1.7
*/
public class SimpleIndexDefinition extends RedisIndexDefinition implements PathBasedRedisIndexDefinition {
/**
* Creates new {@link SimpleIndexDefinition}.
*
* @param keyspace must not be {@literal null}.
* @param path
*/
public SimpleIndexDefinition(String keyspace, String path) {
this(keyspace, path, path);
}
/**
* Creates new {@link SimpleIndexDefinition}.
*
* @param keyspace must not be {@literal null}.
* @param path
* @param name must not be {@literal null}.
*/
public SimpleIndexDefinition(String keyspace, String path, String name) {
super(keyspace, path, name);
addCondition(new PathCondition(path));
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2016 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.index;
import org.springframework.data.redis.core.convert.SpelIndexResolver;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.util.ObjectUtils;
/**
* {@link SpelIndexDefinition} defines index that is evaluated based on a {@link SpelExpression} requires the
* {@link SpelIndexResolver} to be evaluated.
*
* @author Christoph Strobl
* @since 1.7
*/
public class SpelIndexDefinition extends RedisIndexDefinition {
private final String expression;
/**
* Creates new {@link SpelIndexDefinition}.
*
* @param keyspace must not be {@literal null}.
* @param expression must not be {@literal null}.
* @param indexName must not be {@literal null}.
*/
public SpelIndexDefinition(String keyspace, String expression, String indexName) {
super(keyspace, null, indexName);
this.expression = expression;
}
/**
* Get the raw expression.
*
* @return
*/
public String getExpression() {
return expression;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.RedisIndexDefinition#hashCode()
*/
@Override
public int hashCode() {
int result = super.hashCode();
result += ObjectUtils.nullSafeHashCode(expression);
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.index.RedisIndexDefinition#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof SpelIndexDefinition)) {
return false;
}
SpelIndexDefinition that = (SpelIndexDefinition) obj;
return ObjectUtils.nullSafeEquals(this.expression, that.expression);
}
}

View File

@@ -15,8 +15,11 @@
*/
package org.springframework.data.redis.core.mapping;
import org.springframework.data.annotation.Id;
import org.springframework.data.keyvalue.core.mapping.BasicKeyValuePersistentEntity;
import org.springframework.data.keyvalue.core.mapping.KeySpaceResolver;
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.redis.core.TimeToLiveAccessor;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
@@ -27,7 +30,8 @@ import org.springframework.util.Assert;
* @author Christoph Strobl
* @param <T>
*/
public class BasicRedisPersistentEntity<T> extends BasicKeyValuePersistentEntity<T> implements RedisPersistentEntity<T> {
public class BasicRedisPersistentEntity<T> extends BasicKeyValuePersistentEntity<T>
implements RedisPersistentEntity<T> {
private TimeToLiveAccessor timeToLiveAccessor;
@@ -55,4 +59,47 @@ public class BasicRedisPersistentEntity<T> extends BasicKeyValuePersistentEntity
return this.timeToLiveAccessor;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.BasicPersistentEntity#returnPropertyIfBetterIdPropertyCandidateOrNull(org.springframework.data.mapping.PersistentProperty)
*/
@Override
protected KeyValuePersistentProperty returnPropertyIfBetterIdPropertyCandidateOrNull(
KeyValuePersistentProperty property) {
Assert.notNull(property);
if (!property.isIdProperty()) {
return null;
}
KeyValuePersistentProperty currentIdProperty = getIdProperty();
boolean currentIdPropertyIsSet = currentIdProperty != null;
if (!currentIdPropertyIsSet) {
return property;
}
boolean currentIdPropertyIsExplicit = currentIdProperty.isAnnotationPresent(Id.class);
boolean newIdPropertyIsExplicit = property.isAnnotationPresent(Id.class);
if (currentIdPropertyIsExplicit && newIdPropertyIsExplicit) {
throw new MappingException(String.format(
"Attempt to add explicit id property %s but already have an property %s registered "
+ "as explicit id. Check your mapping configuration!",
property.getField(), currentIdProperty.getField()));
}
if (!currentIdPropertyIsExplicit && !newIdPropertyIsExplicit) {
throw new MappingException(
String.format("Attempt to add id property %s but already have an property %s registered "
+ "as id. Check your mapping configuration!", property.getField(), currentIdProperty.getField()));
}
if (newIdPropertyIsExplicit) {
return property;
}
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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,8 @@ package org.springframework.data.redis.core.mapping;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
import org.springframework.data.mapping.PersistentEntity;
@@ -31,6 +33,12 @@ import org.springframework.data.mapping.model.SimpleTypeHolder;
*/
public class RedisPersistentProperty extends KeyValuePersistentProperty {
private static final Set<String> SUPPORTED_ID_PROPERTY_NAMES = new HashSet<String>();
static {
SUPPORTED_ID_PROPERTY_NAMES.add("id");
}
/**
* Creates new {@link RedisPersistentProperty}.
*
@@ -44,4 +52,17 @@ public class RedisPersistentProperty extends KeyValuePersistentProperty {
super(field, propertyDescriptor, owner, simpleTypeHolder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#isIdProperty()
*/
@Override
public boolean isIdProperty() {
if (super.isIdProperty()) {
return true;
}
return SUPPORTED_ID_PROPERTY_NAMES.contains(getName());
}
}

View File

@@ -15,6 +15,10 @@
*/
package org.springframework.data.redis.repository.configuration;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Collections;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -26,6 +30,7 @@ import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.data.keyvalue.repository.config.KeyValueRepositoryConfigurationExtension;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.RedisKeyValueAdapter;
import org.springframework.data.redis.core.RedisKeyValueTemplate;
import org.springframework.data.redis.core.convert.CustomConversions;
@@ -79,6 +84,8 @@ public class RedisRepositoryConfigurationExtension extends KeyValueRepositoryCon
@Override
public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) {
String redisTemplateRef = configurationSource.getAttribute("redisTemplateRef");
RootBeanDefinition mappingContextDefinition = createRedisMappingContext(configurationSource);
mappingContextDefinition.setSource(configurationSource.getSource());
@@ -86,10 +93,11 @@ public class RedisRepositoryConfigurationExtension extends KeyValueRepositoryCon
// register coustom conversions
RootBeanDefinition customConversions = new RootBeanDefinition(CustomConversions.class);
registerIfNotAlreadyRegistered(customConversions, registry, REDIS_CUSTOM_CONVERSIONS_BEAN_NAME, configurationSource);
registerIfNotAlreadyRegistered(customConversions, registry, REDIS_CUSTOM_CONVERSIONS_BEAN_NAME,
configurationSource);
// Register referenceResolver
RootBeanDefinition redisReferenceResolver = createRedisReferenceResolverDefinition();
RootBeanDefinition redisReferenceResolver = createRedisReferenceResolverDefinition(redisTemplateRef);
redisReferenceResolver.setSource(configurationSource.getSource());
registerIfNotAlreadyRegistered(redisReferenceResolver, registry, REDIS_REFERENCE_RESOLVER_BEAN_NAME,
configurationSource);
@@ -104,16 +112,14 @@ public class RedisRepositoryConfigurationExtension extends KeyValueRepositoryCon
RootBeanDefinition redisKeyValueAdapterDefinition = new RootBeanDefinition(RedisKeyValueAdapter.class);
ConstructorArgumentValues constructorArgumentValuesForRedisKeyValueAdapter = new ConstructorArgumentValues();
String redisTemplateRef = configurationSource.getAttribute("redisTemplateRef");
if (StringUtils.hasText(redisTemplateRef)) {
constructorArgumentValuesForRedisKeyValueAdapter.addIndexedArgumentValue(0, new RuntimeBeanReference(
redisTemplateRef));
constructorArgumentValuesForRedisKeyValueAdapter.addIndexedArgumentValue(0,
new RuntimeBeanReference(redisTemplateRef));
}
constructorArgumentValuesForRedisKeyValueAdapter.addIndexedArgumentValue(1, new RuntimeBeanReference(
REDIS_CONVERTER_BEAN_NAME));
constructorArgumentValuesForRedisKeyValueAdapter.addIndexedArgumentValue(1,
new RuntimeBeanReference(REDIS_CONVERTER_BEAN_NAME));
redisKeyValueAdapterDefinition.setConstructorArgumentValues(constructorArgumentValuesForRedisKeyValueAdapter);
registerIfNotAlreadyRegistered(redisKeyValueAdapterDefinition, registry, REDIS_ADAPTER_BEAN_NAME,
@@ -122,14 +128,15 @@ public class RedisRepositoryConfigurationExtension extends KeyValueRepositoryCon
super.registerBeansForRoot(registry, configurationSource);
}
private RootBeanDefinition createRedisReferenceResolverDefinition() {
private RootBeanDefinition createRedisReferenceResolverDefinition(String redisTemplateRef) {
RootBeanDefinition beanDef = new RootBeanDefinition();
beanDef.setBeanClassName("org.springframework.data.redis.core.RedisKeyValueAdapter.ReferenceResolverImpl");
beanDef.setBeanClassName("org.springframework.data.redis.core.convert.ReferenceResolverImpl");
MutablePropertyValues props = new MutablePropertyValues();
props.add("adapter", new RuntimeBeanReference(REDIS_ADAPTER_BEAN_NAME));
beanDef.setPropertyValues(props);
ConstructorArgumentValues constructorArgs = new ConstructorArgumentValues();
constructorArgs.addIndexedArgumentValue(0, new RuntimeBeanReference(redisTemplateRef));
beanDef.setConstructorArgumentValues(constructorArgs);
return beanDef;
}
@@ -178,10 +185,10 @@ public class RedisRepositoryConfigurationExtension extends KeyValueRepositoryCon
RootBeanDefinition keyValueTemplateDefinition = new RootBeanDefinition(RedisKeyValueTemplate.class);
ConstructorArgumentValues constructorArgumentValuesForKeyValueTemplate = new ConstructorArgumentValues();
constructorArgumentValuesForKeyValueTemplate.addIndexedArgumentValue(0, new RuntimeBeanReference(
REDIS_ADAPTER_BEAN_NAME));
constructorArgumentValuesForKeyValueTemplate.addIndexedArgumentValue(1, new RuntimeBeanReference(
MAPPING_CONTEXT_BEAN_NAME));
constructorArgumentValuesForKeyValueTemplate.addIndexedArgumentValue(0,
new RuntimeBeanReference(REDIS_ADAPTER_BEAN_NAME));
constructorArgumentValuesForKeyValueTemplate.addIndexedArgumentValue(1,
new RuntimeBeanReference(MAPPING_CONTEXT_BEAN_NAME));
keyValueTemplateDefinition.setConstructorArgumentValues(constructorArgumentValuesForKeyValueTemplate);
@@ -205,4 +212,13 @@ public class RedisRepositoryConfigurationExtension extends KeyValueRepositoryCon
return beanDef;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingAnnotations()
*/
@Override
protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
return Collections.<Class<? extends Annotation>> singleton(RedisHash.class);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2016 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.repository.core;
import java.io.Serializable;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
import org.springframework.data.repository.core.support.PersistentEntityInformation;
/**
* {@link RedisEntityInformation} implementation using a {@link MongoPersistentEntity} instance to lookup the necessary
* information. Can be configured with a custom collection to be returned which will trump the one returned by the
* {@link MongoPersistentEntity} if given.
*
* @author Christoph Strobl
* @param <T>
* @param <ID>
*/
public class MappingRedisEntityInformation<T, ID extends Serializable>
extends PersistentEntityInformation<T, Serializable> implements RedisEntityInformation<T, Serializable> {
private final RedisPersistentEntity<T> entityMetadata;
/**
* @param entity
*/
public MappingRedisEntityInformation(RedisPersistentEntity<T> entity) {
super(entity);
this.entityMetadata = entity;
if (!entityMetadata.hasIdProperty()) {
throw new MappingException(
String.format("Entity %s requires to have an explicit id field. Did you forget to provide one using @Id?",
entity.getName()));
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2016 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.repository.core;
import java.io.Serializable;
import org.springframework.data.repository.core.EntityInformation;
/**
* @author Christoph Strobl
* @param <T>
* @param <ID>
*/
public interface RedisEntityInformation<T, ID extends Serializable> extends EntityInformation<T, ID> {
}

View File

@@ -16,9 +16,12 @@
package org.springframework.data.redis.repository.query;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.util.ObjectUtils;
/**
* Simple set of operations requried to run queries against Redis.
*
@@ -27,27 +30,102 @@ import java.util.Set;
*/
public class RedisOperationChain {
Set<Object> sismember = new LinkedHashSet<Object>();
Set<Object> orSismember = new LinkedHashSet<Object>();
private Set<PathAndValue> sismember = new LinkedHashSet<PathAndValue>();
private Set<PathAndValue> orSismember = new LinkedHashSet<PathAndValue>();
public void sismember(Object next) {
sismember.add(next);
public void sismember(String path, Object value) {
sismember(new PathAndValue(path, value));
}
public Set<Object> getSismember() {
public void sismember(PathAndValue pathAndValue) {
sismember.add(pathAndValue);
}
public Set<PathAndValue> getSismember() {
return sismember;
}
public void orSismember(Object next) {
orSismember.add(next);
public void orSismember(String path, Object value) {
orSismember(new PathAndValue(path, value));
}
public void orSismember(Collection<Object> next) {
public void orSismember(PathAndValue pathAndValue) {
orSismember.add(pathAndValue);
}
public void orSismember(Collection<PathAndValue> next) {
orSismember.addAll(next);
}
public Set<Object> getOrSismember() {
public Set<PathAndValue> getOrSismember() {
return orSismember;
}
public static class PathAndValue {
private final String path;
private final Collection<Object> values;
public PathAndValue(String path, Object singleValue) {
this.path = path;
this.values = Collections.singleton(singleValue);
}
public PathAndValue(String path, Collection<Object> values) {
this.path = path;
this.values = values != null ? values : Collections.emptySet();
}
public boolean isSingleValue() {
return values.size() == 1;
}
public String getPath() {
return path;
}
public Collection<Object> values() {
return values;
}
public Object getFirstValue() {
return values.isEmpty() ? null : values.iterator().next();
}
@Override
public String toString() {
return path + ":" + (isSingleValue() ? getFirstValue() : values);
}
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(path);
result += ObjectUtils.nullSafeHashCode(values);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof PathAndValue)) {
return false;
}
PathAndValue that = (PathAndValue) obj;
if (!ObjectUtils.nullSafeEquals(this.path, that.path)) {
return false;
}
return ObjectUtils.nullSafeEquals(this.values, that.values);
}
}
}

View File

@@ -49,7 +49,7 @@ public class RedisQueryCreator extends AbstractQueryCreator<KeyValueQuery<RedisO
switch (part.getType()) {
case SIMPLE_PROPERTY:
sink.sismember(part.getProperty().toDotPath() + ":" + iterator.next());
sink.sismember(part.getProperty().toDotPath(), iterator.next());
break;
default:
throw new IllegalArgumentException(part.getType() + "is not supported for redis query derivation");
@@ -74,7 +74,7 @@ public class RedisQueryCreator extends AbstractQueryCreator<KeyValueQuery<RedisO
*/
@Override
protected RedisOperationChain or(RedisOperationChain base, RedisOperationChain criteria) {
base.orSismember(criteria.sismember);
base.orSismember(criteria.getSismember());
return base;
}
@@ -87,6 +87,12 @@ public class RedisQueryCreator extends AbstractQueryCreator<KeyValueQuery<RedisO
KeyValueQuery<RedisOperationChain> query = new KeyValueQuery<RedisOperationChain>(criteria);
if (query.getCritieria().getSismember().size() == 1 && query.getCritieria().getOrSismember().size() == 1) {
query.getCritieria().getOrSismember().add(query.getCritieria().getSismember().iterator().next());
query.getCritieria().getSismember().clear();
}
if (sort != null) {
query.setSort(sort);
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.repository.support;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.springframework.data.keyvalue.core.KeyValueOperations;
@@ -22,6 +23,9 @@ import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery;
import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery.QueryInitialization;
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
import org.springframework.data.redis.repository.core.MappingRedisEntityInformation;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
@@ -69,6 +73,21 @@ public class RedisRepositoryFactory extends KeyValueRepositoryFactory {
return new RedisQueryLookupStrategy(key, evaluationContextProvider, getKeyValueOperations(), getQueryCreator());
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory#getEntityInformation(java.lang.Class)
*/
@Override
@SuppressWarnings("unchecked")
public <T, ID extends Serializable> EntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
RedisPersistentEntity<T> entity = (RedisPersistentEntity<T>) getMappingContext().getPersistentEntity(domainClass);
EntityInformation<T, ID> entityInformation = (EntityInformation<T, ID>) new MappingRedisEntityInformation<T, ID>(
entity);
return entityInformation;
}
/**
* @author Christoph Strobl
* @since 1.7

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -18,9 +18,10 @@ package org.springframework.data.redis.repository.support;
import java.io.Serializable;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
/**
* Adapter for Springs {@link FactoryBean} interface to allow easy setup of {@link RedisRepositoryFactory} via Spring
@@ -37,10 +38,11 @@ public class RedisRepositoryFactoryBean<T extends Repository<S, ID>, S, ID exten
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#createRepositoryFactory()
* @see org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean#createRepositoryFactory(org.springframework.data.keyvalue.core.KeyValueOperations, java.lang.Class)
*/
@Override
protected RepositoryFactorySupport createRepositoryFactory() {
return new RedisRepositoryFactory(getOperations(), getQueryCreator());
protected RedisRepositoryFactory createRepositoryFactory(KeyValueOperations operations,
Class<? extends AbstractQueryCreator<?, ?>> queryCreator) {
return new RedisRepositoryFactory(operations, queryCreator);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -19,10 +19,13 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.Assert;
/**
* Some handy methods for dealing with byte arrays.
*
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 1.7
*/
public final class ByteUtils {
@@ -76,4 +79,23 @@ public final class ByteUtils {
}
return bytes.toArray(new byte[bytes.size()][]);
}
/**
* Merge multiple {@code byte} arrays into one array
*
* @param firstArray must not be {@literal null}
* @param additionalArrays must not be {@literal null}
* @return
*/
public static byte[][] mergeArrays(byte[] firstArray, byte[]... additionalArrays) {
Assert.notNull(firstArray, "first array must not be null");
Assert.notNull(additionalArrays, "additional arrays must not be null");
byte[][] result = new byte[additionalArrays.length + 1][];
result[0] = firstArray;
System.arraycopy(additionalArrays, 0, result, 1, additionalArrays.length);
return result;
}
}