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:
@@ -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