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:
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,6 +36,6 @@ public interface IndexedData {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getKeySpace();
|
||||
String getKeyspace();
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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())));
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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> {}
|
||||
}
|
||||
@@ -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> {}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user