/*
* Copyright 2016-2017 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;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.hamcrest.core.IsNull;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
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.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.keyvalue.core.KeyValueTemplate;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
import org.springframework.data.redis.core.index.GeoIndexed;
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.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
* Base for testing Redis repository support in different configurations.
*
* @author Christoph Strobl
* @author Mark Paluch
*/
public abstract class RedisRepositoryIntegrationTestBase {
@Autowired PersonRepository repo;
@Autowired CityRepository cityRepo;
@Autowired KeyValueTemplate kvTemplate;
@Before
public void setUp() {
// flush keyspaces
kvTemplate.delete(Person.class);
kvTemplate.delete(City.class);
}
@Test // DATAREDIS-425
public void simpleFindShouldReturnEntitiesCorrectly() {
Person rand = new Person();
rand.firstname = "rand";
rand.lastname = "al'thor";
Person egwene = new Person();
egwene.firstname = "egwene";
repo.saveAll(Arrays.asList(rand, egwene));
assertThat(repo.count(), is(2L));
assertThat(repo.findById(rand.id), is(Optional.of(rand)));
assertThat(repo.findById(egwene.id), is(Optional.of(egwene)));
assertThat(repo.findByFirstname("rand").size(), is(1));
assertThat(repo.findByFirstname("rand"), hasItem(rand));
assertThat(repo.findByFirstname("egwene").size(), is(1));
assertThat(repo.findByFirstname("egwene"), hasItem(egwene));
assertThat(repo.findByLastname("al'thor"), hasItem(rand));
}
@Test // DATAREDIS-425
public void simpleFindByMultipleProperties() {
Person egwene = new Person();
egwene.firstname = "egwene";
egwene.lastname = "al'vere";
Person marin = new Person();
marin.firstname = "marin";
marin.lastname = "al'vere";
repo.saveAll(Arrays.asList(egwene, marin));
assertThat(repo.findByLastname("al'vere").size(), is(2));
assertThat(repo.findByFirstnameAndLastname("egwene", "al'vere").size(), is(1));
assertThat(repo.findByFirstnameAndLastname("egwene", "al'vere").get(0), is(egwene));
}
@Test // DATAREDIS-425
public void findReturnsReferenceDataCorrectly() {
// Prepare referenced data entry
City tarValon = new City();
tarValon.id = "1";
tarValon.name = "tar valon";
kvTemplate.insert(tarValon);
// Prepare domain entity
Person moiraine = new Person();
moiraine.firstname = "moiraine";
moiraine.city = tarValon; // reference data
// save domain entity
repo.save(moiraine);
// find and assert current location set correctly
Optional<Person> loaded = repo.findById(moiraine.getId());
assertThat(loaded.get().city, is(tarValon));
// remove reference location data
kvTemplate.delete("1", City.class);
// find and assert the location is gone
Optional<Person> reLoaded = repo.findById(moiraine.getId());
assertThat(reLoaded.get().city, IsNull.nullValue());
}
@Test // DATAREDIS-425
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.saveAll(Arrays.asList(eddard, robb, sansa, arya, bran, rickon));
Page<Person> page1 = repo.findPersonByLastname("stark", PageRequest.of(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));
}
@Test // DATAREDIS-425
public void findUsingOrReturnsResultCorrectly() {
Person eddard = new Person("eddard", "stark");
Person robb = new Person("robb", "stark");
Person jon = new Person("jon", "snow");
repo.saveAll(Arrays.asList(eddard, robb, jon));
List<Person> eddardAndJon = repo.findByFirstnameOrLastname("eddard", "snow");
assertThat(eddardAndJon, hasSize(2));
assertThat(eddardAndJon, containsInAnyOrder(eddard, jon));
}
@Test // DATAREDIS-547
public void shouldApplyFirstKeywordCorrectly() {
Person eddard = new Person("eddard", "stark");
Person robb = new Person("robb", "stark");
Person jon = new Person("jon", "snow");
repo.saveAll(Arrays.asList(eddard, robb, jon));
assertThat(repo.findFirstBy(), hasSize(1));
}
@Test // DATAREDIS-547
public void shouldApplyPageableCorrectlyWhenUsingFindAll() {
Person eddard = new Person("eddard", "stark");
Person robb = new Person("robb", "stark");
Person jon = new Person("jon", "snow");
repo.saveAll(Arrays.asList(eddard, robb, jon));
Page<Person> firstPage = repo.findAll(PageRequest.of(0, 2));
assertThat(firstPage.getContent(), hasSize(2));
assertThat(repo.findAll(firstPage.nextPageable()).getContent(), hasSize(1));
}
@Test // DATAREDIS-551
public void shouldApplyPageableCorrectlyWhenUsingFindByWithoutCriteria() {
Person eddard = new Person("eddard", "stark");
Person robb = new Person("robb", "stark");
Person jon = new Person("jon", "snow");
repo.saveAll(Arrays.asList(eddard, robb, jon));
Page<Person> firstPage = repo.findBy(PageRequest.of(0, 2));
assertThat(firstPage.getContent(), hasSize(2));
assertThat(firstPage.getTotalElements(), is(equalTo(3L)));
assertThat(repo.findBy(firstPage.nextPageable()).getContent(), hasSize(1));
}
@Test // DATAREDIS-547
public void shouldReturnEmptyListWhenPageableOutOfBoundsUsingFindAll() {
Person eddard = new Person("eddard", "stark");
Person robb = new Person("robb", "stark");
Person jon = new Person("jon", "snow");
repo.saveAll(Arrays.asList(eddard, robb, jon));
Page<Person> firstPage = repo.findAll(PageRequest.of(100, 2));
assertThat(firstPage.getContent(), hasSize(0));
}
@Test // DATAREDIS-547
public void shouldReturnEmptyListWhenPageableOutOfBoundsUsingQueryMethod() {
Person eddard = new Person("eddard", "stark");
Person robb = new Person("robb", "stark");
Person sansa = new Person("sansa", "stark");
repo.saveAll(Arrays.asList(eddard, robb, sansa));
Page<Person> page1 = repo.findPersonByLastname("stark", PageRequest.of(1, 3));
assertThat(page1.getNumberOfElements(), is(0));
assertThat(page1.getContent(), hasSize(0));
assertThat(page1.getTotalElements(), is(3L));
Page<Person> page2 = repo.findPersonByLastname("stark", PageRequest.of(2, 3));
assertThat(page2.getNumberOfElements(), is(0));
assertThat(page2.getContent(), hasSize(0));
assertThat(page2.getTotalElements(), is(3L));
}
@Test // DATAREDIS-547
public void shouldApplyTopKeywordCorrectly() {
Person eddard = new Person("eddard", "stark");
Person robb = new Person("robb", "stark");
Person jon = new Person("jon", "snow");
repo.saveAll(Arrays.asList(eddard, robb, jon));
assertThat(repo.findTop2By(), hasSize(2));
}
@Test // DATAREDIS-547
public void shouldApplyTopKeywordCorrectlyWhenCriteriaPresent() {
Person eddard = new Person("eddard", "stark");
Person tyrion = new Person("tyrion", "lannister");
Person robb = new Person("robb", "stark");
Person jon = new Person("jon", "snow");
Person arya = new Person("arya", "stark");
repo.saveAll(Arrays.asList(eddard, tyrion, robb, jon, arya));
List<Person> result = repo.findTop2ByLastname("stark");
assertThat(result, hasSize(2));
for (Person p : result) {
assertThat(p.getLastname(), is("stark"));
}
}
@Test // DATAREDIS-533
public void nearQueryShouldReturnResultsCorrectly() {
City palermo = new City();
palermo.location = new Point(13.361389D, 38.115556D);
City catania = new City();
catania.location = new Point(15.087269D, 37.502669D);
cityRepo.saveAll(Arrays.asList(palermo, catania));
List<City> result = cityRepo.findByLocationNear(new Point(15D, 37D), new Distance(200, Metrics.KILOMETERS));
assertThat(result, hasItems(palermo, catania));
result = cityRepo.findByLocationNear(new Point(15D, 37D), new Distance(100, Metrics.KILOMETERS));
assertThat(result, hasItems(catania));
assertThat(result, not(hasItems(palermo)));
}
@Test // DATAREDIS-533
public void nearQueryShouldFindNothingIfOutOfRange() {
City palermo = new City();
palermo.location = new Point(13.361389D, 38.115556D);
City catania = new City();
catania.location = new Point(15.087269D, 37.502669D);
cityRepo.saveAll(Arrays.asList(palermo, catania));
List<City> result = cityRepo.findByLocationNear(new Point(15D, 37D), new Distance(10, Metrics.KILOMETERS));
assertThat(result, is(empty()));
}
@Test // DATAREDIS-533
public void nearQueryShouldReturnResultsCorrectlyOnNestedProperty() {
City palermo = new City();
palermo.location = new Point(13.361389D, 38.115556D);
City catania = new City();
catania.location = new Point(15.087269D, 37.502669D);
Person p1 = new Person("foo", "bar");
p1.hometown = palermo;
Person p2 = new Person("two", "two");
p2.hometown = catania;
repo.saveAll(Arrays.asList(p1, p2));
List<Person> result = repo.findByHometownLocationNear(new Point(15D, 37D), new Distance(200, Metrics.KILOMETERS));
assertThat(result, hasItems(p1, p2));
result = repo.findByHometownLocationNear(new Point(15D, 37D), new Distance(100, Metrics.KILOMETERS));
assertThat(result, hasItems(p2));
assertThat(result, not(hasItems(p1)));
}
public static interface PersonRepository extends PagingAndSortingRepository<Person, String> {
List<Person> findByFirstname(String firstname);
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);
List<Person> findFirstBy();
List<Person> findTop2By();
List<Person> findTop2ByLastname(String lastname);
Page<Person> findBy(Pageable page);
List<Person> findByHometownLocationNear(Point point, Distance distance);
}
public static interface CityRepository extends CrudRepository<City, String> {
List<City> findByLocationNear(Point point, Distance distance);
}
/**
* Custom Redis {@link IndexConfiguration} forcing index of {@link Person#lastname}.
*
* @author Christoph Strobl
*/
static class MyIndexConfiguration extends IndexConfiguration {
@Override
protected Iterable<IndexDefinition> initialConfiguration() {
return Collections.<IndexDefinition> singleton(new SimpleIndexDefinition("persons", "lastname"));
}
}
/**
* Custom Redis {@link IndexConfiguration} forcing index of {@link Person#lastname}.
*
* @author Christoph Strobl
*/
static class MyKeyspaceConfiguration extends KeyspaceConfiguration {
@Override
protected Iterable<KeyspaceSettings> initialConfiguration() {
return Collections.singleton(new KeyspaceSettings(City.class, "cities"));
}
}
@RedisHash("persons")
@SuppressWarnings("serial")
public static class Person implements Serializable {
@Id String id;
@Indexed String firstname;
String lastname;
@Reference City city;
City hometown;
public Person() {}
public Person(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
public City getCity() {
return city;
}
public void setCity(City city) {
this.city = city;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getLastname() {
return lastname;
}
@Override
public String toString() {
return "Person [id=" + id + ", firstname=" + firstname + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((firstname == null) ? 0 : firstname.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Person)) {
return false;
}
Person other = (Person) obj;
if (firstname == null) {
if (other.firstname != null) {
return false;
}
} else if (!firstname.equals(other.firstname)) {
return false;
}
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
return true;
}
}
public static class City {
@Id String id;
String name;
@GeoIndexed Point location;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof City)) {
return false;
}
City other = (City) obj;
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
}
}