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

@@ -28,6 +28,8 @@ public class Person {
We have a pretty simple domain object here. Note that it has a property named `id` annotated with `org.springframework.data.annotation.Id` and a `@RedisHash` annotation on its type.
Those two are responsible for creating the actual key used to persist the hash.
NOTE: Properties annotated with `@Id` as well as those named `id` are considered as the identifier properties. Those with the annotation are favored over others.
To now actually have a component responsible for storage and retrieval we need to define a repository interface.
.Basic Repository Interface To Persist Person Entities

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;
}
}

View File

@@ -30,13 +30,17 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.convert.IndexResolverImpl;
import org.springframework.data.redis.core.convert.IndexedData;
import org.springframework.data.redis.core.convert.MappingRedisConverter;
import org.springframework.data.redis.core.convert.PathIndexResolver;
import org.springframework.data.redis.core.convert.ReferenceResolver;
import org.springframework.data.redis.core.convert.SimpleIndexedPropertyValue;
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
@@ -50,6 +54,7 @@ public class IndexWriterUnitTests {
private static final String KEY = "key-1";
private static final byte[] KEY_BIN = KEY.getBytes(CHARSET);
IndexWriter writer;
MappingRedisConverter converter;
@Mock RedisConnection connectionMock;
@Mock ReferenceResolver referenceResolverMock;
@@ -57,8 +62,7 @@ public class IndexWriterUnitTests {
@Before
public void setUp() {
MappingRedisConverter converter = new MappingRedisConverter(new RedisMappingContext(), new IndexResolverImpl(),
referenceResolverMock);
converter = new MappingRedisConverter(new RedisMappingContext(), new PathIndexResolver(), referenceResolverMock);
converter.afterPropertiesSet();
writer = new IndexWriter(connectionMock, converter);
@@ -143,6 +147,37 @@ public class IndexWriterUnitTests {
assertThat(captor.getAllValues(), hasItems(indexKey1, indexKey2));
}
/**
* @see DATAREDIS-425
*/
@Test(expected = InvalidDataAccessApiUsageException.class)
public void addToIndexShouldThrowDataAccessExceptionWhenAddingDataThatConnotBeConverted() {
writer.addKeyToIndex(KEY_BIN, new SimpleIndexedPropertyValue(KEYSPACE, "firstname", new DummyObject()));
}
/**
* @see DATAREDIS-425
*/
@Test
public void addToIndexShouldUseRegisteredConverterWhenAddingData() {
DummyObject value = new DummyObject();
final String identityHexString = ObjectUtils.getIdentityHexString(value);
((GenericConversionService) converter.getConversionService()).addConverter(new Converter<DummyObject, byte[]>() {
@Override
public byte[] convert(DummyObject source) {
return identityHexString.getBytes(CHARSET);
}
});
writer.addKeyToIndex(KEY_BIN, new SimpleIndexedPropertyValue(KEYSPACE, "firstname", value));
verify(connectionMock).sAdd(eq(("persons:firstname:" + identityHexString).getBytes(CHARSET)), eq(KEY_BIN));
}
static class StubIndxedData implements IndexedData {
@Override
@@ -151,9 +186,12 @@ public class IndexWriterUnitTests {
}
@Override
public String getKeySpace() {
public String getKeyspace() {
return KEYSPACE;
}
}
static class DummyObject {
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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 static org.hamcrest.core.IsEqual.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.util.TypeInformation;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class CompositeIndexResolverUnitTests {
@Mock IndexResolver resolver1;
@Mock IndexResolver resolver2;
@Mock TypeInformation<?> typeInfoMock;
/**
* @see DATAREDIS-425
*/
@Test(expected = IllegalArgumentException.class)
public void shouldRejectNull() {
new CompositeIndexResolver(null);
}
/**
* @see DATAREDIS-425
*/
@Test(expected = IllegalArgumentException.class)
public void shouldRejectCollectionWithNullValues() {
new CompositeIndexResolver(Arrays.asList(resolver1, null, resolver2));
}
/**
* @see DATAREDIS-425
*/
@Test
public void shouldCollectionIndexesFromResolvers() {
when(resolver1.resolveIndexesFor(any(TypeInformation.class), anyObject())).thenReturn(
Collections.<IndexedData> singleton(new SimpleIndexedPropertyValue("spring", "data", "redis")));
when(resolver2.resolveIndexesFor(any(TypeInformation.class), anyObject())).thenReturn(
Collections.<IndexedData> singleton(new SimpleIndexedPropertyValue("redis", "data", "spring")));
CompositeIndexResolver resolver = new CompositeIndexResolver(Arrays.asList(resolver1, resolver2));
assertThat(resolver.resolveIndexesFor(typeInfoMock, "o.O").size(), equalTo(2));
}
}

View File

@@ -26,17 +26,19 @@ import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.TimeToLive;
import org.springframework.data.redis.core.index.Indexed;
import lombok.EqualsAndHashCode;
/**
* @author Christoph Strobl
*/
class ConversionTestEntities {
public class ConversionTestEntities {
static final String KEYSPACE_PERSON = "persons";
static final String KEYSPACE_TWOT = "twot";
static final String KEYSPACE_LOCATION = "locations";
@RedisHash(KEYSPACE_PERSON)
static class Person {
public static class Person {
@Id String id;
String firstname;
@@ -59,80 +61,82 @@ class ConversionTestEntities {
Species species;
}
static class PersonWithAddressReference extends Person {
public static class PersonWithAddressReference extends Person {
@Reference AddressWithId addressRef;
}
static class Address {
public static class Address {
String city;
@Indexed String country;
}
static class AddressWithId extends Address {
public static class AddressWithId extends Address {
@Id String id;
}
static enum Gender {
public static enum Gender {
MALE, FEMALE
}
static class AddressWithPostcode extends Address {
public static class AddressWithPostcode extends Address {
String postcode;
}
static class TaVeren extends Person {
public static class TaVeren extends Person {
Object feature;
Map<String, Object> characteristics;
List<Object> items;
}
@EqualsAndHashCode
@RedisHash(KEYSPACE_LOCATION)
static class Location {
public static class Location {
@Id String id;
String name;
Address address;
}
@RedisHash(timeToLive = 5)
static class ExpiringPerson {
public static class ExpiringPerson {
@Id String id;
String name;
}
static class ExipringPersonWithExplicitProperty extends ExpiringPerson {
public static class ExipringPersonWithExplicitProperty extends ExpiringPerson {
@TimeToLive(unit = TimeUnit.MINUTES) Long ttl;
}
static class Species {
public static class Species {
String name;
List<String> alsoKnownAs;
}
@RedisHash(KEYSPACE_TWOT)
static class TheWheelOfTime {
public static class TheWheelOfTime {
List<Person> mainCharacters;
List<Species> species;
Map<String, Location> places;
}
static class Item {
public static class Item {
@Indexed String type;
String description;
Size size;
}
static class Size {
public static class Size {
int width;
int height;

View File

@@ -629,7 +629,12 @@ public class MappingRedisConverterUnitTests {
location.id = "1";
location.name = "tar valon";
when(resolverMock.<Location> resolveReference(eq("1"), eq("locations"), eq(Location.class))).thenReturn(location);
Map<String, String> locationMap = new LinkedHashMap<String, String>();
locationMap.put("id", location.id);
locationMap.put("name", location.name);
when(resolverMock.resolveReference(eq("1"), eq("locations")))
.thenReturn(Bucket.newBucketFromStringMap(locationMap).rawMap());
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("location", "locations:1");
@@ -671,7 +676,12 @@ public class MappingRedisConverterUnitTests {
location.id = "1";
location.name = "tar valon";
when(resolverMock.<Location> resolveReference(eq("1"), eq("locations"), eq(Location.class))).thenReturn(location);
Map<String, String> locationMap = new LinkedHashMap<String, String>();
locationMap.put("id", location.id);
locationMap.put("name", location.name);
when(resolverMock.resolveReference(eq("1"), eq("locations")))
.thenReturn(Bucket.newBucketFromStringMap(locationMap).rawMap());
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("coworkers.[0].location", "locations:1");
@@ -726,9 +736,26 @@ public class MappingRedisConverterUnitTests {
tear.id = "3";
tear.name = "city of tear";
when(resolverMock.<Location> resolveReference(eq("1"), eq("locations"), eq(Location.class))).thenReturn(tarValon);
when(resolverMock.<Location> resolveReference(eq("2"), eq("locations"), eq(Location.class))).thenReturn(falme);
when(resolverMock.<Location> resolveReference(eq("3"), eq("locations"), eq(Location.class))).thenReturn(tear);
Map<String, String> tarValonMap = new LinkedHashMap<String, String>();
tarValonMap.put("id", tarValon.id);
tarValonMap.put("name", tarValon.name);
Map<String, String> falmeMap = new LinkedHashMap<String, String>();
falmeMap.put("id", falme.id);
falmeMap.put("name", falme.name);
Map<String, String> tearMap = new LinkedHashMap<String, String>();
tearMap.put("id", tear.id);
tearMap.put("name", tear.name);
Bucket.newBucketFromStringMap(tearMap).rawMap();
when(resolverMock.resolveReference(eq("1"), eq("locations")))
.thenReturn(Bucket.newBucketFromStringMap(tarValonMap).rawMap());
when(resolverMock.resolveReference(eq("2"), eq("locations")))
.thenReturn(Bucket.newBucketFromStringMap(falmeMap).rawMap());
when(resolverMock.resolveReference(eq("3"), eq("locations")))
.thenReturn(Bucket.newBucketFromStringMap(tearMap).rawMap());
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("visited.[0]", "locations:1");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-216 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.core.convert;
import static org.hamcrest.collection.IsEmptyCollection.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsCollectionContaining.*;
import static org.hamcrest.core.IsNull.*;
@@ -26,6 +27,8 @@ import static org.springframework.data.redis.core.convert.ConversionTestEntities
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hamcrest.core.IsCollectionContaining;
@@ -41,12 +44,12 @@ import org.springframework.data.redis.core.convert.ConversionTestEntities.Item;
import org.springframework.data.redis.core.convert.ConversionTestEntities.Location;
import org.springframework.data.redis.core.convert.ConversionTestEntities.Person;
import org.springframework.data.redis.core.convert.ConversionTestEntities.PersonWithAddressReference;
import org.springframework.data.redis.core.convert.ConversionTestEntities.Size;
import org.springframework.data.redis.core.convert.ConversionTestEntities.TaVeren;
import org.springframework.data.redis.core.convert.ConversionTestEntities.TheWheelOfTime;
import org.springframework.data.redis.core.index.IndexConfiguration;
import org.springframework.data.redis.core.index.IndexConfiguration.RedisIndexSetting;
import org.springframework.data.redis.core.index.IndexType;
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.util.ClassTypeInformation;
@@ -54,10 +57,10 @@ import org.springframework.data.util.ClassTypeInformation;
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class IndexResolverImplUnitTests {
public class PathIndexResolverUnitTests {
IndexConfiguration indexConfig;
IndexResolverImpl indexResolver;
PathIndexResolver indexResolver;
@Mock PersistentProperty<?> propertyMock;
@@ -65,8 +68,8 @@ public class IndexResolverImplUnitTests {
public void setUp() {
indexConfig = new IndexConfiguration();
this.indexResolver = new IndexResolverImpl(new RedisMappingContext(new MappingConfiguration(indexConfig,
new KeyspaceConfiguration())));
this.indexResolver = new PathIndexResolver(
new RedisMappingContext(new MappingConfiguration(indexConfig, new KeyspaceConfiguration())));
}
/**
@@ -74,7 +77,7 @@ public class IndexResolverImplUnitTests {
*/
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionOnNullMappingContext() {
new IndexResolverImpl(null);
new PathIndexResolver(null);
}
/**
@@ -145,9 +148,10 @@ public class IndexResolverImplUnitTests {
Set<IndexedData> indexes = indexResolver.resolveIndexesFor(ClassTypeInformation.from(TheWheelOfTime.class), twot);
assertThat(indexes.size(), is(2));
assertThat(indexes, IsCollectionContaining.<IndexedData> hasItems(new SimpleIndexedPropertyValue(KEYSPACE_TWOT,
"mainCharacters.address.country", "andor"), new SimpleIndexedPropertyValue(KEYSPACE_TWOT,
"mainCharacters.address.country", "saldaea")));
assertThat(indexes,
IsCollectionContaining.<IndexedData> hasItems(
new SimpleIndexedPropertyValue(KEYSPACE_TWOT, "mainCharacters.address.country", "andor"),
new SimpleIndexedPropertyValue(KEYSPACE_TWOT, "mainCharacters.address.country", "saldaea")));
}
/**
@@ -170,8 +174,8 @@ public class IndexResolverImplUnitTests {
Set<IndexedData> indexes = indexResolver.resolveIndexesFor(ClassTypeInformation.from(TheWheelOfTime.class), twot);
assertThat(indexes.size(), is(1));
assertThat(indexes, hasItem(new SimpleIndexedPropertyValue(KEYSPACE_TWOT, "places.stone-of-tear.address.country",
"illian")));
assertThat(indexes,
hasItem(new SimpleIndexedPropertyValue(KEYSPACE_TWOT, "places.stone-of-tear.address.country", "illian")));
}
/**
@@ -180,7 +184,7 @@ public class IndexResolverImplUnitTests {
@Test
public void shouldResolveConfiguredIndexesInMapOfSimpleTypes() {
indexConfig.addIndexDefinition(new RedisIndexSetting(KEYSPACE_PERSON, "physicalAttributes.eye-color"));
indexConfig.addIndexDefinition(new SimpleIndexDefinition(KEYSPACE_PERSON, "physicalAttributes.eye-color"));
Person rand = new Person();
rand.physicalAttributes = new LinkedHashMap<String, String>();
@@ -199,7 +203,7 @@ public class IndexResolverImplUnitTests {
@Test
public void shouldResolveConfiguredIndexesInMapOfComplexTypes() {
indexConfig.addIndexDefinition(new RedisIndexSetting(KEYSPACE_PERSON, "relatives.father.firstname"));
indexConfig.addIndexDefinition(new SimpleIndexDefinition(KEYSPACE_PERSON, "relatives.father.firstname"));
Person rand = new Person();
rand.relatives = new LinkedHashMap<String, Person>();
@@ -222,7 +226,7 @@ public class IndexResolverImplUnitTests {
@Test
public void shouldIgnoreConfiguredIndexesInMapWhenValueIsNull() {
indexConfig.addIndexDefinition(new RedisIndexSetting(KEYSPACE_PERSON, "physicalAttributes.eye-color"));
indexConfig.addIndexDefinition(new SimpleIndexDefinition(KEYSPACE_PERSON, "physicalAttributes.eye-color"));
Person rand = new Person();
rand.physicalAttributes = new LinkedHashMap<String, String>();
@@ -244,8 +248,8 @@ public class IndexResolverImplUnitTests {
rand.addressRef.id = "emond_s_field";
rand.addressRef.country = "andor";
Set<IndexedData> indexes = indexResolver.resolveIndexesFor(
ClassTypeInformation.from(PersonWithAddressReference.class), rand);
Set<IndexedData> indexes = indexResolver
.resolveIndexesFor(ClassTypeInformation.from(PersonWithAddressReference.class), rand);
assertThat(indexes.size(), is(0));
}
@@ -267,7 +271,7 @@ public class IndexResolverImplUnitTests {
public void resolveIndexShouldReturnDataWhenIndexConfigured() {
when(propertyMock.isAnnotationPresent(eq(Indexed.class))).thenReturn(false);
indexConfig.addIndexDefinition(new RedisIndexSetting(KEYSPACE_PERSON, "foo"));
indexConfig.addIndexDefinition(new SimpleIndexDefinition(KEYSPACE_PERSON, "foo"));
assertThat(resolve("foo", "rand"), notNullValue());
}
@@ -279,7 +283,7 @@ public class IndexResolverImplUnitTests {
public void resolveIndexShouldReturnDataWhenNoIndexConfiguredButPropertyAnnotated() {
when(propertyMock.isAnnotationPresent(eq(Indexed.class))).thenReturn(true);
when(propertyMock.findAnnotation(eq(Indexed.class))).thenReturn(createIndexedInstance(IndexType.SIMPLE));
when(propertyMock.findAnnotation(eq(Indexed.class))).thenReturn(createIndexedInstance());
assertThat(resolve("foo", "rand"), notNullValue());
}
@@ -292,7 +296,7 @@ public class IndexResolverImplUnitTests {
when(propertyMock.isCollectionLike()).thenReturn(true);
when(propertyMock.isAnnotationPresent(eq(Indexed.class))).thenReturn(true);
when(propertyMock.findAnnotation(eq(Indexed.class))).thenReturn(createIndexedInstance(IndexType.SIMPLE));
when(propertyMock.findAnnotation(eq(Indexed.class))).thenReturn(createIndexedInstance());
IndexedData index = resolve("list.[0].name", "rand");
@@ -307,7 +311,7 @@ public class IndexResolverImplUnitTests {
when(propertyMock.isMap()).thenReturn(true);
when(propertyMock.isAnnotationPresent(eq(Indexed.class))).thenReturn(true);
when(propertyMock.findAnnotation(eq(Indexed.class))).thenReturn(createIndexedInstance(IndexType.SIMPLE));
when(propertyMock.findAnnotation(eq(Indexed.class))).thenReturn(createIndexedInstance());
IndexedData index = resolve("map.[foo].name", "rand");
@@ -322,7 +326,7 @@ public class IndexResolverImplUnitTests {
when(propertyMock.isMap()).thenReturn(true);
when(propertyMock.isAnnotationPresent(eq(Indexed.class))).thenReturn(true);
when(propertyMock.findAnnotation(eq(Indexed.class))).thenReturn(createIndexedInstance(IndexType.SIMPLE));
when(propertyMock.findAnnotation(eq(Indexed.class))).thenReturn(createIndexedInstance());
IndexedData index = resolve("map.[0].name", "rand");
@@ -410,7 +414,8 @@ public class IndexResolverImplUnitTests {
*/
@Test
public void resolveIndexAllowCustomIndexName() {
indexConfig.addIndexDefinition(new RedisIndexSetting(KEYSPACE_PERSON, "items.type", "itemsType", IndexType.SIMPLE));
indexConfig.addIndexDefinition(new SimpleIndexDefinition(KEYSPACE_PERSON, "items.type", "itemsType"));
Item hat = new Item();
hat.type = "hat";
@@ -426,11 +431,78 @@ public class IndexResolverImplUnitTests {
assertThat(indexes, hasItem(new SimpleIndexedPropertyValue(KEYSPACE_PERSON, "itemsType", "hat")));
}
private IndexedData resolve(String path, Object value) {
return indexResolver.resolveIndex(KEYSPACE_PERSON, path, propertyMock, value);
/**
* @see DATAREDIS-425
*/
@Test
public void resolveIndexForTypeThatHasNoIndexDefined() {
Size size = new Size();
size.height = 10;
size.length = 20;
size.width = 30;
Set<IndexedData> indexes = indexResolver.resolveIndexesFor(ClassTypeInformation.from(Size.class), size);
assertThat(indexes, is(empty()));
}
private Indexed createIndexedInstance(final IndexType type) {
/**
* @see DATAREDIS-425
*/
@Test
public void resolveIndexOnMapField() {
IndexedOnMapField source = new IndexedOnMapField();
source.values = new LinkedHashMap<String, String>();
source.values.put("jon", "snow");
source.values.put("arya", "stark");
Set<IndexedData> indexes = indexResolver.resolveIndexesFor(ClassTypeInformation.from(IndexedOnMapField.class),
source);
assertThat(indexes.size(), is(2));
assertThat(indexes,
IsCollectionContaining.<IndexedData> hasItems(
new SimpleIndexedPropertyValue(IndexedOnMapField.class.getName(), "values.jon", "snow"),
new SimpleIndexedPropertyValue(IndexedOnMapField.class.getName(), "values.arya", "stark")));
}
/**
* @see DATAREDIS-425
*/
@Test
public void resolveIndexOnListField() {
IndexedOnListField source = new IndexedOnListField();
source.values = new ArrayList<String>();
source.values.add("jon");
source.values.add("arya");
Set<IndexedData> indexes = indexResolver.resolveIndexesFor(ClassTypeInformation.from(IndexedOnListField.class),
source);
assertThat(indexes.size(), is(2));
assertThat(indexes,
IsCollectionContaining.<IndexedData> hasItems(
new SimpleIndexedPropertyValue(IndexedOnListField.class.getName(), "values", "jon"),
new SimpleIndexedPropertyValue(IndexedOnListField.class.getName(), "values", "arya")));
}
private IndexedData resolve(String path, Object value) {
Set<IndexedData> data = indexResolver.resolveIndex(KEYSPACE_PERSON, path, propertyMock, value);
if (data.isEmpty()) {
return null;
}
assertThat(data.size(), is(1));
return data.iterator().next();
}
private Indexed createIndexedInstance() {
return new Indexed() {
@@ -439,10 +511,17 @@ public class IndexResolverImplUnitTests {
return Indexed.class;
}
@Override
public IndexType type() {
return type == null ? IndexType.SIMPLE : type;
}
};
}
static class IndexedOnListField {
@Indexed List<String> values;
}
static class IndexedOnMapField {
@Indexed Map<String, String> values;
}
}

View File

@@ -27,8 +27,7 @@ import org.junit.Test;
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
import org.springframework.data.redis.core.convert.KeyspaceConfiguration.KeyspaceSettings;
import org.springframework.data.redis.core.index.IndexConfiguration;
import org.springframework.data.redis.core.index.IndexConfiguration.RedisIndexSetting;
import org.springframework.data.redis.core.index.IndexType;
import org.springframework.data.redis.core.index.SpelIndexDefinition;
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.expression.AccessException;
@@ -175,7 +174,7 @@ public class SpelIndexResolverUnitTests {
private SpelIndexResolver createWithExpression(String expression) {
RedisIndexSetting principalIndex = new RedisIndexSetting(keyspace, expression, indexName, IndexType.SIMPLE);
SpelIndexDefinition principalIndex = new SpelIndexDefinition(keyspace, expression, indexName);
IndexConfiguration configuration = new IndexConfiguration();
configuration.addIndexDefinition(principalIndex);

View File

@@ -19,10 +19,10 @@ import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.redis.core.index.IndexConfiguration.RedisIndexSetting;
/**
* @author Rob Winch
* @author Christoph Strobl
*/
public class IndexConfigurationUnitTests {
@@ -33,7 +33,7 @@ public class IndexConfigurationUnitTests {
public void redisIndexSettingIndexNameDefaulted() {
String path = "path";
RedisIndexSetting setting = new RedisIndexSetting("keyspace", path);
SimpleIndexDefinition setting = new SimpleIndexDefinition("keyspace", path);
assertThat(setting.getIndexName(), equalTo(path));
}
@@ -44,7 +44,7 @@ public class IndexConfigurationUnitTests {
public void redisIndexSettingIndexNameExplicit() {
String indexName = "indexName";
RedisIndexSetting setting = new RedisIndexSetting("keyspace", "index", indexName, IndexType.SIMPLE);
SimpleIndexDefinition setting = new SimpleIndexDefinition("keyspace", "index", indexName);
assertThat(setting.getIndexName(), equalTo(indexName));
}
@@ -54,9 +54,9 @@ public class IndexConfigurationUnitTests {
@Test
public void redisIndexSettingIndexNameUsedInEquals() {
RedisIndexSetting setting1 = new RedisIndexSetting("keyspace", "path", "indexName1", IndexType.SIMPLE);
RedisIndexSetting setting2 = new RedisIndexSetting(setting1.getKeyspace(), setting1.getPath(),
setting1.getIndexName() + "other", setting1.getType());
SimpleIndexDefinition setting1 = new SimpleIndexDefinition("keyspace", "path", "indexName1");
SimpleIndexDefinition setting2 = new SimpleIndexDefinition(setting1.getKeyspace(), "path", setting1.getIndexName()
+ "other");
assertThat(setting1, not(equalTo(setting2)));
}
@@ -67,9 +67,9 @@ public class IndexConfigurationUnitTests {
@Test
public void redisIndexSettingIndexNameUsedInHashCode() {
RedisIndexSetting setting1 = new RedisIndexSetting("keyspace", "path", "indexName1", IndexType.SIMPLE);
RedisIndexSetting setting2 = new RedisIndexSetting(setting1.getKeyspace(), setting1.getPath(),
setting1.getIndexName() + "other", setting1.getType());
SimpleIndexDefinition setting1 = new SimpleIndexDefinition("keyspace", "path", "indexName1");
SimpleIndexDefinition setting2 = new SimpleIndexDefinition(setting1.getKeyspace(), "path", setting1.getIndexName()
+ "other");
assertThat(setting1.hashCode(), not(equalTo(setting2.hashCode())));
}

View File

@@ -0,0 +1,126 @@
/*
* 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.mapping;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsEqual.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
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.redis.core.convert.ConversionTestEntities;
import org.springframework.data.util.TypeInformation;
/**
* @author Christoph Strobl
* @param <T>
* @param <ID>
*/
@RunWith(MockitoJUnitRunner.class)
public class BasicRedisPersistentEntityUnitTests<T, ID extends Serializable> {
public @Rule ExpectedException expectedException = ExpectedException.none();
@Mock TypeInformation<T> entityInformation;
@Mock KeySpaceResolver keySpaceResolver;
@Mock TimeToLiveAccessor ttlAccessor;
BasicRedisPersistentEntity<T> entity;
@Before
@SuppressWarnings("unchecked")
public void setUp() {
when(entityInformation.getType()).thenReturn((Class<T>) ConversionTestEntities.Person.class);
entity = new BasicRedisPersistentEntity<T>(entityInformation, keySpaceResolver, ttlAccessor);
}
/**
* @see DATAREDIS-425
*/
@Test
public void addingMultipleIdPropertiesWithoutAnExplicitOneThrowsException() {
expectedException.expect(MappingException.class);
expectedException.expectMessage("Attempt to add id property");
expectedException.expectMessage("but already have an property");
KeyValuePersistentProperty property1 = mock(RedisPersistentProperty.class);
when(property1.isIdProperty()).thenReturn(true);
KeyValuePersistentProperty property2 = mock(RedisPersistentProperty.class);
when(property2.isIdProperty()).thenReturn(true);
entity.addPersistentProperty(property1);
entity.addPersistentProperty(property2);
}
/**
* @see DATAREDIS-425
*/
@Test
@SuppressWarnings("unchecked")
public void addingMultipleExplicitIdPropertiesThrowsException() {
expectedException.expect(MappingException.class);
expectedException.expectMessage("Attempt to add explicit id property");
expectedException.expectMessage("but already have an property");
KeyValuePersistentProperty property1 = mock(RedisPersistentProperty.class);
when(property1.isIdProperty()).thenReturn(true);
when(property1.isAnnotationPresent(any(Class.class))).thenReturn(true);
KeyValuePersistentProperty property2 = mock(RedisPersistentProperty.class);
when(property2.isIdProperty()).thenReturn(true);
when(property2.isAnnotationPresent(any(Class.class))).thenReturn(true);
entity.addPersistentProperty(property1);
entity.addPersistentProperty(property2);
}
/**
* @see DATAREDIS-425
*/
@Test
@SuppressWarnings("unchecked")
public void explicitIdPropertiyShouldBeFavoredOverNonExplicit() {
KeyValuePersistentProperty property1 = mock(RedisPersistentProperty.class);
when(property1.isIdProperty()).thenReturn(true);
KeyValuePersistentProperty property2 = mock(RedisPersistentProperty.class);
when(property2.isIdProperty()).thenReturn(true);
when(property2.isAnnotationPresent(any(Class.class))).thenReturn(true);
entity.addPersistentProperty(property1);
entity.addPersistentProperty(property2);
assertThat(entity.getIdProperty(), is(equalTo(property2)));
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.redis.repository;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsCollectionContaining.*;
import static org.junit.Assert.*;
@@ -30,16 +32,23 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Reference;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.keyvalue.core.KeyValueTemplate;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
import org.springframework.data.redis.core.index.IndexConfiguration;
import org.springframework.data.redis.core.index.IndexDefinition;
import org.springframework.data.redis.core.index.Indexed;
import org.springframework.data.redis.core.index.SimpleIndexDefinition;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.repository.CrudRepository;
import org.springframework.test.context.ContextConfiguration;
@@ -54,7 +63,8 @@ public class RedisRepositoryIntegrationTests {
@Configuration
@EnableRedisRepositories(considerNestedRepositories = true, indexConfiguration = MyIndexConfiguration.class,
keyspaceConfiguration = MyKeyspaceConfiguration.class)
keyspaceConfiguration = MyKeyspaceConfiguration.class,
includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*PersonRepository") })
static class Config {
@Bean
@@ -161,7 +171,50 @@ public class RedisRepositoryIntegrationTests {
// find and assert the location is gone
Person reLoaded = repo.findOne(moiraine.getId());
assertThat(reLoaded.city, IsNull.nullValue());
}
/**
* @see DATAREDIS-425
*/
@Test
public void findReturnsPageCorrectly() {
Person eddard = new Person("eddard", "stark");
Person robb = new Person("robb", "stark");
Person sansa = new Person("sansa", "stark");
Person arya = new Person("arya", "stark");
Person bran = new Person("bran", "stark");
Person rickon = new Person("rickon", "stark");
repo.save(Arrays.asList(eddard, robb, sansa, arya, bran, rickon));
Page<Person> page1 = repo.findPersonByLastname("stark", new PageRequest(0, 5));
assertThat(page1.getNumberOfElements(), is(5));
assertThat(page1.getTotalElements(), is(6L));
Page<Person> page2 = repo.findPersonByLastname("stark", page1.nextPageable());
assertThat(page2.getNumberOfElements(), is(1));
assertThat(page2.getTotalElements(), is(6L));
}
/**
* @see DATAREDIS-425
*/
@Test
public void findUsingOrReturnsResultCorrectly() {
Person eddard = new Person("eddard", "stark");
Person robb = new Person("robb", "stark");
Person jon = new Person("jon", "snow");
repo.save(Arrays.asList(eddard, robb, jon));
List<Person> eddardAndJon = repo.findByFirstnameOrLastname("eddard", "snow");
assertThat(eddardAndJon, hasSize(2));
assertThat(eddardAndJon, containsInAnyOrder(eddard, jon));
}
public static interface PersonRepository extends CrudRepository<Person, String> {
@@ -170,7 +223,11 @@ public class RedisRepositoryIntegrationTests {
List<Person> findByLastname(String lastname);
Page<Person> findPersonByLastname(String lastname, Pageable page);
List<Person> findByFirstnameAndLastname(String firstname, String lastname);
List<Person> findByFirstnameOrLastname(String firstname, String lastname);
}
/**
@@ -181,8 +238,8 @@ public class RedisRepositoryIntegrationTests {
static class MyIndexConfiguration extends IndexConfiguration {
@Override
protected Iterable<RedisIndexSetting> initialConfiguration() {
return Collections.singleton(new RedisIndexSetting("persons", "lastname"));
protected Iterable<IndexDefinition> initialConfiguration() {
return Collections.<IndexDefinition> singleton(new SimpleIndexDefinition("persons", "lastname"));
}
}
@@ -208,6 +265,14 @@ public class RedisRepositoryIntegrationTests {
String lastname;
@Reference City city;
public Person() {}
public Person(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
public City getCity() {
return city;
}

View File

@@ -0,0 +1,121 @@
/*
* 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.configuration;
import static org.junit.Assert.*;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.data.annotation.Id;
import org.springframework.data.keyvalue.repository.KeyValueRepository;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.RepositoryConfiguration;
import org.springframework.data.repository.config.RepositoryConfigurationSource;
/**
* @author Christoph Strobl
*/
public class RedisRepositoryConfigurationExtensionUnitTests {
StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(Config.class, true);
ResourceLoader loader = new PathMatchingResourcePatternResolver();
Environment environment = new StandardEnvironment();
RepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(metadata,
EnableRedisRepositories.class, loader, environment);
RedisRepositoryConfigurationExtension extension;
@Before
public void setUp() {
extension = new RedisRepositoryConfigurationExtension();
}
/**
* @see DATAREDIS-425
*/
@Test
public void isStrictMatchIfDomainTypeIsAnnotatedWithDocument() {
assertHasRepo(SampleRepository.class, extension.getRepositoryConfigurations(configurationSource, loader, true));
}
/**
* @see DATAREDIS-425
*/
@Test
public void isStrictMatchIfRepositoryExtendsStoreSpecificBase() {
assertHasRepo(StoreRepository.class, extension.getRepositoryConfigurations(configurationSource, loader, true));
}
/**
* @see DATAREDIS-425
*/
@Test
public void isNotStrictMatchIfDomainTypeIsNotAnnotatedWithDocument() {
assertDoesNotHaveRepo(UnannotatedRepository.class,
extension.getRepositoryConfigurations(configurationSource, loader, true));
}
private static void assertDoesNotHaveRepo(Class<?> repositoryInterface,
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configs) {
try {
assertHasRepo(repositoryInterface, configs);
fail("Expected not to find config for repository interface ".concat(repositoryInterface.getName()));
} catch (AssertionError error) {
// repo not there. we're fine.
}
}
private static void assertHasRepo(Class<?> repositoryInterface,
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configs) {
for (RepositoryConfiguration<?> config : configs) {
if (config.getRepositoryInterface().equals(repositoryInterface.getName())) {
return;
}
}
fail("Expected to find config for repository interface ".concat(repositoryInterface.getName()).concat(" but got ")
.concat(configs.toString()));
}
@EnableRedisRepositories(considerNestedRepositories = true)
static class Config {
}
@RedisHash
static class Sample {
@Id String id;
}
interface SampleRepository extends Repository<Sample, Long> {}
interface UnannotatedRepository extends Repository<Object, Long> {}
interface StoreRepository extends KeyValueRepository<Object, Long> {}
}

View File

@@ -0,0 +1,148 @@
package org.springframework.data.redis.repository.configuration;
/*
* 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.
*/
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsEqual.*;
import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.RedisKeyValueAdapter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.convert.ReferenceResolver;
import org.springframework.data.redis.repository.configuration.RedisRepositoryConfigurationUnitTests.ContextWithCustomReferenceResolver;
import org.springframework.data.redis.repository.configuration.RedisRepositoryConfigurationUnitTests.ContextWithoutCustomization;
import org.springframework.data.repository.Repository;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;
/**
* @author Christoph Strobl
*/
@RunWith(Suite.class)
@SuiteClasses({ ContextWithCustomReferenceResolver.class, ContextWithoutCustomization.class })
public class RedisRepositoryConfigurationUnitTests {
static RedisTemplate<?, ?> createTemplateMock() {
RedisTemplate<?, ?> template = mock(RedisTemplate.class);
RedisConnectionFactory connectionFactory = mock(RedisConnectionFactory.class);
RedisConnection connection = mock(RedisConnection.class);
when(template.getConnectionFactory()).thenReturn(connectionFactory);
when(connectionFactory.getConnection()).thenReturn(connection);
return template;
}
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
@ContextConfiguration(classes = { ContextWithCustomReferenceResolver.Config.class })
public static class ContextWithCustomReferenceResolver {
@EnableRedisRepositories(considerNestedRepositories = true,
includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = { ".*ContextSampleRepository" }) })
static class Config {
@Bean
RedisTemplate<?, ?> redisTemplate() {
return createTemplateMock();
}
@Bean
ReferenceResolver redisReferenceResolver() {
return mock(ReferenceResolver.class);
}
}
@Autowired ApplicationContext ctx;
/**
* @see DATAREDIS-425
*/
@Test
public void shouldPickUpReferenceResolver() {
RedisKeyValueAdapter adapter = (RedisKeyValueAdapter) ctx.getBean("redisKeyValueAdapter");
Object referenceResolver = ReflectionTestUtils.getField(adapter.getConverter(), "referenceResolver");
assertThat(referenceResolver, is(equalTo(ctx.getBean("redisReferenceResolver"))));
assertThat(mockingDetails(referenceResolver).isMock(), is(true));
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
@ContextConfiguration(classes = { ContextWithoutCustomization.Config.class })
public static class ContextWithoutCustomization {
@EnableRedisRepositories(considerNestedRepositories = true,
includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = { ".*ContextSampleRepository" }) })
static class Config {
@Bean
RedisTemplate<?, ?> redisTemplate() {
return createTemplateMock();
}
}
@Autowired ApplicationContext ctx;
/**
* @see DATAREDIS-425
*/
@Test
public void shouldInitWithDefaults() {
assertThat(ctx.getBean(ContextSampleRepository.class), is(notNullValue()));
}
/**
* @see DATAREDIS-425
*/
@Test
public void shouldRegisterDefaultBeans() {
assertThat(ctx.getBean(ContextSampleRepository.class), is(notNullValue()));
assertThat(ctx.getBean("redisKeyValueAdapter"), is(notNullValue()));
assertThat(ctx.getBean("redisCustomConversions"), is(notNullValue()));
assertThat(ctx.getBean("redisReferenceResolver"), is(notNullValue()));
}
}
@RedisHash
static class Sample {
String id;
}
interface ContextSampleRepository extends Repository<Sample, Long> {}
}

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.repository.core;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.redis.core.convert.ConversionTestEntities;
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class MappingRedisEntityInformationUnitTests<T, ID extends Serializable> {
@Mock RedisPersistentEntity<T> entity;
/**
* @see DATAREDIS-425
*/
@Test(expected = MappingException.class)
@SuppressWarnings("unchecked")
public void throwsMappingExceptionWhenNoIdPropertyPresent() {
when(entity.hasIdProperty()).thenReturn(false);
when(entity.getType()).thenReturn((Class<T>) ConversionTestEntities.Person.class);
new MappingRedisEntityInformation<T, ID>(entity);
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.query;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import static org.hamcrest.core.IsCollectionContaining.*;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import org.junit.Test;
import org.mockito.Mock;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.redis.core.convert.ConversionTestEntities;
import org.springframework.data.redis.repository.query.RedisOperationChain.PathAndValue;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.parser.PartTree;
/**
* @author Christoph Strobl
*/
public class RedisQueryCreatorUnitTests {
private @Mock RepositoryMetadata metadataMock;
/**
* @see DATAREDIS-425
*/
@Test
public void findBySingleSimpleProperty() throws SecurityException, NoSuchMethodException {
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
SampleRepository.class.getMethod("findByFirstname", String.class), new Object[] { "eddard" });
KeyValueQuery<RedisOperationChain> query = creator.createQuery();
assertThat(query.getCritieria().getSismember(), hasSize(1));
assertThat(query.getCritieria().getSismember(), hasItem(new PathAndValue("firstname", "eddard")));
}
/**
* @see DATAREDIS-425
*/
@Test
public void findByMultipleSimpleProperties() throws SecurityException, NoSuchMethodException {
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
SampleRepository.class.getMethod("findByFirstnameAndAge", String.class, Integer.class), new Object[] {
"eddard", 43 });
KeyValueQuery<RedisOperationChain> query = creator.createQuery();
assertThat(query.getCritieria().getSismember(), hasSize(2));
assertThat(query.getCritieria().getSismember(), hasItem(new PathAndValue("firstname", "eddard")));
assertThat(query.getCritieria().getSismember(), hasItem(new PathAndValue("age", 43)));
}
/**
* @see DATAREDIS-425
*/
@Test
public void findByMultipleSimplePropertiesUsingOr() throws SecurityException, NoSuchMethodException {
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
SampleRepository.class.getMethod("findByAgeOrFirstname", Integer.class, String.class), new Object[] { 43,
"eddard" });
KeyValueQuery<RedisOperationChain> query = creator.createQuery();
assertThat(query.getCritieria().getOrSismember(), hasSize(2));
assertThat(query.getCritieria().getOrSismember(), hasItem(new PathAndValue("age", 43)));
assertThat(query.getCritieria().getOrSismember(), hasItem(new PathAndValue("firstname", "eddard")));
}
private RedisQueryCreator createQueryCreatorForMethodWithArgs(Method method, Object[] args) {
PartTree partTree = new PartTree(method.getName(), method.getReturnType());
RedisQueryCreator creator = new RedisQueryCreator(partTree, new ParametersParameterAccessor(new DefaultParameters(
method), args));
return creator;
}
private interface SampleRepository extends Repository<ConversionTestEntities.Person, String> {
ConversionTestEntities.Person findByFirstname(String firstname);
ConversionTestEntities.Person findByFirstnameAndAge(String firstname, Integer age);
ConversionTestEntities.Person findByAgeOrFirstname(Integer age, String firstname);
}
}