DATAREDIS-425 - CRUD operation and index resolution refinements.

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

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

View File

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

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.core.convert;
import static org.hamcrest.core.IsEqual.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.util.TypeInformation;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class CompositeIndexResolverUnitTests {
@Mock IndexResolver resolver1;
@Mock IndexResolver resolver2;
@Mock TypeInformation<?> typeInfoMock;
/**
* @see DATAREDIS-425
*/
@Test(expected = IllegalArgumentException.class)
public void shouldRejectNull() {
new CompositeIndexResolver(null);
}
/**
* @see DATAREDIS-425
*/
@Test(expected = IllegalArgumentException.class)
public void shouldRejectCollectionWithNullValues() {
new CompositeIndexResolver(Arrays.asList(resolver1, null, resolver2));
}
/**
* @see DATAREDIS-425
*/
@Test
public void shouldCollectionIndexesFromResolvers() {
when(resolver1.resolveIndexesFor(any(TypeInformation.class), anyObject())).thenReturn(
Collections.<IndexedData> singleton(new SimpleIndexedPropertyValue("spring", "data", "redis")));
when(resolver2.resolveIndexesFor(any(TypeInformation.class), anyObject())).thenReturn(
Collections.<IndexedData> singleton(new SimpleIndexedPropertyValue("redis", "data", "spring")));
CompositeIndexResolver resolver = new CompositeIndexResolver(Arrays.asList(resolver1, resolver2));
assertThat(resolver.resolveIndexesFor(typeInfoMock, "o.O").size(), equalTo(2));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.core.mapping;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsEqual.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.keyvalue.core.mapping.KeySpaceResolver;
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.redis.core.TimeToLiveAccessor;
import org.springframework.data.redis.core.convert.ConversionTestEntities;
import org.springframework.data.util.TypeInformation;
/**
* @author Christoph Strobl
* @param <T>
* @param <ID>
*/
@RunWith(MockitoJUnitRunner.class)
public class BasicRedisPersistentEntityUnitTests<T, ID extends Serializable> {
public @Rule ExpectedException expectedException = ExpectedException.none();
@Mock TypeInformation<T> entityInformation;
@Mock KeySpaceResolver keySpaceResolver;
@Mock TimeToLiveAccessor ttlAccessor;
BasicRedisPersistentEntity<T> entity;
@Before
@SuppressWarnings("unchecked")
public void setUp() {
when(entityInformation.getType()).thenReturn((Class<T>) ConversionTestEntities.Person.class);
entity = new BasicRedisPersistentEntity<T>(entityInformation, keySpaceResolver, ttlAccessor);
}
/**
* @see DATAREDIS-425
*/
@Test
public void addingMultipleIdPropertiesWithoutAnExplicitOneThrowsException() {
expectedException.expect(MappingException.class);
expectedException.expectMessage("Attempt to add id property");
expectedException.expectMessage("but already have an property");
KeyValuePersistentProperty property1 = mock(RedisPersistentProperty.class);
when(property1.isIdProperty()).thenReturn(true);
KeyValuePersistentProperty property2 = mock(RedisPersistentProperty.class);
when(property2.isIdProperty()).thenReturn(true);
entity.addPersistentProperty(property1);
entity.addPersistentProperty(property2);
}
/**
* @see DATAREDIS-425
*/
@Test
@SuppressWarnings("unchecked")
public void addingMultipleExplicitIdPropertiesThrowsException() {
expectedException.expect(MappingException.class);
expectedException.expectMessage("Attempt to add explicit id property");
expectedException.expectMessage("but already have an property");
KeyValuePersistentProperty property1 = mock(RedisPersistentProperty.class);
when(property1.isIdProperty()).thenReturn(true);
when(property1.isAnnotationPresent(any(Class.class))).thenReturn(true);
KeyValuePersistentProperty property2 = mock(RedisPersistentProperty.class);
when(property2.isIdProperty()).thenReturn(true);
when(property2.isAnnotationPresent(any(Class.class))).thenReturn(true);
entity.addPersistentProperty(property1);
entity.addPersistentProperty(property2);
}
/**
* @see DATAREDIS-425
*/
@Test
@SuppressWarnings("unchecked")
public void explicitIdPropertiyShouldBeFavoredOverNonExplicit() {
KeyValuePersistentProperty property1 = mock(RedisPersistentProperty.class);
when(property1.isIdProperty()).thenReturn(true);
KeyValuePersistentProperty property2 = mock(RedisPersistentProperty.class);
when(property2.isIdProperty()).thenReturn(true);
when(property2.isAnnotationPresent(any(Class.class))).thenReturn(true);
entity.addPersistentProperty(property1);
entity.addPersistentProperty(property2);
assertThat(entity.getIdProperty(), is(equalTo(property2)));
}
}

View File

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

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.repository.configuration;
import static org.junit.Assert.*;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.data.annotation.Id;
import org.springframework.data.keyvalue.repository.KeyValueRepository;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.RepositoryConfiguration;
import org.springframework.data.repository.config.RepositoryConfigurationSource;
/**
* @author Christoph Strobl
*/
public class RedisRepositoryConfigurationExtensionUnitTests {
StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(Config.class, true);
ResourceLoader loader = new PathMatchingResourcePatternResolver();
Environment environment = new StandardEnvironment();
RepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(metadata,
EnableRedisRepositories.class, loader, environment);
RedisRepositoryConfigurationExtension extension;
@Before
public void setUp() {
extension = new RedisRepositoryConfigurationExtension();
}
/**
* @see DATAREDIS-425
*/
@Test
public void isStrictMatchIfDomainTypeIsAnnotatedWithDocument() {
assertHasRepo(SampleRepository.class, extension.getRepositoryConfigurations(configurationSource, loader, true));
}
/**
* @see DATAREDIS-425
*/
@Test
public void isStrictMatchIfRepositoryExtendsStoreSpecificBase() {
assertHasRepo(StoreRepository.class, extension.getRepositoryConfigurations(configurationSource, loader, true));
}
/**
* @see DATAREDIS-425
*/
@Test
public void isNotStrictMatchIfDomainTypeIsNotAnnotatedWithDocument() {
assertDoesNotHaveRepo(UnannotatedRepository.class,
extension.getRepositoryConfigurations(configurationSource, loader, true));
}
private static void assertDoesNotHaveRepo(Class<?> repositoryInterface,
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configs) {
try {
assertHasRepo(repositoryInterface, configs);
fail("Expected not to find config for repository interface ".concat(repositoryInterface.getName()));
} catch (AssertionError error) {
// repo not there. we're fine.
}
}
private static void assertHasRepo(Class<?> repositoryInterface,
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configs) {
for (RepositoryConfiguration<?> config : configs) {
if (config.getRepositoryInterface().equals(repositoryInterface.getName())) {
return;
}
}
fail("Expected to find config for repository interface ".concat(repositoryInterface.getName()).concat(" but got ")
.concat(configs.toString()));
}
@EnableRedisRepositories(considerNestedRepositories = true)
static class Config {
}
@RedisHash
static class Sample {
@Id String id;
}
interface SampleRepository extends Repository<Sample, Long> {}
interface UnannotatedRepository extends Repository<Object, Long> {}
interface StoreRepository extends KeyValueRepository<Object, Long> {}
}

View File

@@ -0,0 +1,148 @@
package org.springframework.data.redis.repository.configuration;
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsEqual.*;
import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.RedisKeyValueAdapter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.convert.ReferenceResolver;
import org.springframework.data.redis.repository.configuration.RedisRepositoryConfigurationUnitTests.ContextWithCustomReferenceResolver;
import org.springframework.data.redis.repository.configuration.RedisRepositoryConfigurationUnitTests.ContextWithoutCustomization;
import org.springframework.data.repository.Repository;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;
/**
* @author Christoph Strobl
*/
@RunWith(Suite.class)
@SuiteClasses({ ContextWithCustomReferenceResolver.class, ContextWithoutCustomization.class })
public class RedisRepositoryConfigurationUnitTests {
static RedisTemplate<?, ?> createTemplateMock() {
RedisTemplate<?, ?> template = mock(RedisTemplate.class);
RedisConnectionFactory connectionFactory = mock(RedisConnectionFactory.class);
RedisConnection connection = mock(RedisConnection.class);
when(template.getConnectionFactory()).thenReturn(connectionFactory);
when(connectionFactory.getConnection()).thenReturn(connection);
return template;
}
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
@ContextConfiguration(classes = { ContextWithCustomReferenceResolver.Config.class })
public static class ContextWithCustomReferenceResolver {
@EnableRedisRepositories(considerNestedRepositories = true,
includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = { ".*ContextSampleRepository" }) })
static class Config {
@Bean
RedisTemplate<?, ?> redisTemplate() {
return createTemplateMock();
}
@Bean
ReferenceResolver redisReferenceResolver() {
return mock(ReferenceResolver.class);
}
}
@Autowired ApplicationContext ctx;
/**
* @see DATAREDIS-425
*/
@Test
public void shouldPickUpReferenceResolver() {
RedisKeyValueAdapter adapter = (RedisKeyValueAdapter) ctx.getBean("redisKeyValueAdapter");
Object referenceResolver = ReflectionTestUtils.getField(adapter.getConverter(), "referenceResolver");
assertThat(referenceResolver, is(equalTo(ctx.getBean("redisReferenceResolver"))));
assertThat(mockingDetails(referenceResolver).isMock(), is(true));
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
@ContextConfiguration(classes = { ContextWithoutCustomization.Config.class })
public static class ContextWithoutCustomization {
@EnableRedisRepositories(considerNestedRepositories = true,
includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = { ".*ContextSampleRepository" }) })
static class Config {
@Bean
RedisTemplate<?, ?> redisTemplate() {
return createTemplateMock();
}
}
@Autowired ApplicationContext ctx;
/**
* @see DATAREDIS-425
*/
@Test
public void shouldInitWithDefaults() {
assertThat(ctx.getBean(ContextSampleRepository.class), is(notNullValue()));
}
/**
* @see DATAREDIS-425
*/
@Test
public void shouldRegisterDefaultBeans() {
assertThat(ctx.getBean(ContextSampleRepository.class), is(notNullValue()));
assertThat(ctx.getBean("redisKeyValueAdapter"), is(notNullValue()));
assertThat(ctx.getBean("redisCustomConversions"), is(notNullValue()));
assertThat(ctx.getBean("redisReferenceResolver"), is(notNullValue()));
}
}
@RedisHash
static class Sample {
String id;
}
interface ContextSampleRepository extends Repository<Sample, Long> {}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.repository.core;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.redis.core.convert.ConversionTestEntities;
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class MappingRedisEntityInformationUnitTests<T, ID extends Serializable> {
@Mock RedisPersistentEntity<T> entity;
/**
* @see DATAREDIS-425
*/
@Test(expected = MappingException.class)
@SuppressWarnings("unchecked")
public void throwsMappingExceptionWhenNoIdPropertyPresent() {
when(entity.hasIdProperty()).thenReturn(false);
when(entity.getType()).thenReturn((Class<T>) ConversionTestEntities.Person.class);
new MappingRedisEntityInformation<T, ID>(entity);
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.repository.query;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import static org.hamcrest.core.IsCollectionContaining.*;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import org.junit.Test;
import org.mockito.Mock;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.redis.core.convert.ConversionTestEntities;
import org.springframework.data.redis.repository.query.RedisOperationChain.PathAndValue;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.parser.PartTree;
/**
* @author Christoph Strobl
*/
public class RedisQueryCreatorUnitTests {
private @Mock RepositoryMetadata metadataMock;
/**
* @see DATAREDIS-425
*/
@Test
public void findBySingleSimpleProperty() throws SecurityException, NoSuchMethodException {
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
SampleRepository.class.getMethod("findByFirstname", String.class), new Object[] { "eddard" });
KeyValueQuery<RedisOperationChain> query = creator.createQuery();
assertThat(query.getCritieria().getSismember(), hasSize(1));
assertThat(query.getCritieria().getSismember(), hasItem(new PathAndValue("firstname", "eddard")));
}
/**
* @see DATAREDIS-425
*/
@Test
public void findByMultipleSimpleProperties() throws SecurityException, NoSuchMethodException {
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
SampleRepository.class.getMethod("findByFirstnameAndAge", String.class, Integer.class), new Object[] {
"eddard", 43 });
KeyValueQuery<RedisOperationChain> query = creator.createQuery();
assertThat(query.getCritieria().getSismember(), hasSize(2));
assertThat(query.getCritieria().getSismember(), hasItem(new PathAndValue("firstname", "eddard")));
assertThat(query.getCritieria().getSismember(), hasItem(new PathAndValue("age", 43)));
}
/**
* @see DATAREDIS-425
*/
@Test
public void findByMultipleSimplePropertiesUsingOr() throws SecurityException, NoSuchMethodException {
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
SampleRepository.class.getMethod("findByAgeOrFirstname", Integer.class, String.class), new Object[] { 43,
"eddard" });
KeyValueQuery<RedisOperationChain> query = creator.createQuery();
assertThat(query.getCritieria().getOrSismember(), hasSize(2));
assertThat(query.getCritieria().getOrSismember(), hasItem(new PathAndValue("age", 43)));
assertThat(query.getCritieria().getOrSismember(), hasItem(new PathAndValue("firstname", "eddard")));
}
private RedisQueryCreator createQueryCreatorForMethodWithArgs(Method method, Object[] args) {
PartTree partTree = new PartTree(method.getName(), method.getReturnType());
RedisQueryCreator creator = new RedisQueryCreator(partTree, new ParametersParameterAccessor(new DefaultParameters(
method), args));
return creator;
}
private interface SampleRepository extends Repository<ConversionTestEntities.Person, String> {
ConversionTestEntities.Person findByFirstname(String firstname);
ConversionTestEntities.Person findByFirstnameAndAge(String firstname, Integer age);
ConversionTestEntities.Person findByAgeOrFirstname(Integer age, String firstname);
}
}