DATAGEODE-367 - Expose EntityInformation and the GemfireTemplate on SimpleGemfireRepository.

This commit is contained in:
John Blum
2020-09-14 16:41:50 -07:00
parent 24f2231674
commit 92a71670d6
6 changed files with 574 additions and 173 deletions

View File

@@ -19,7 +19,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;
/**
* Pivotal GemFire specific extension of the Spring Data {@link CrudRepository} interface.
* Apache Geode extension of the Spring Data {@link CrudRepository} interface.
*
* @author Oliver Gierke
* @author John Blum
@@ -28,10 +28,10 @@ import org.springframework.data.repository.CrudRepository;
public interface GemfireRepository<T, ID> extends CrudRepository<T, ID> {
/**
* Returns all entities sorted by the given options.
* Returns all entities ordered by the given {@link Sort}.
*
* @param sort the Spring Data Commons Sort type defining the ordering criteria.
* @return all entities sorted by the given options.
* @param sort {@link Sort} defining the ordering criteria.
* @return all entities ordered by the given {@link Sort}.
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
* @see org.springframework.data.domain.Sort
* @see java.lang.Iterable

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.gemfire.repository.support;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -37,22 +37,31 @@ import org.springframework.data.gemfire.repository.GemfireRepository;
import org.springframework.data.gemfire.repository.Wrapper;
import org.springframework.data.gemfire.repository.query.QueryString;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.data.gemfire.util.SpringUtils;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.util.StreamUtils;
import org.springframework.data.util.Streamable;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Basic Repository implementation for GemFire.
* Simple, basic {@link CrudRepository} implementation for Apache Geode.
*
* @author Oliver Gierke
* @author David Turanski
* @author John Blum
* @see java.io.Serializable
* @see org.apache.geode.cache.Cache
* @see org.apache.geode.cache.CacheTransactionManager
* @see org.apache.geode.cache.Region
* @see org.springframework.data.gemfire.GemfireTemplate
* @see org.springframework.data.gemfire.repository.GemfireRepository
* @see org.apache.geode.cache.Cache
* @see org.apache.geode.cache.Region
* @see org.springframework.data.repository.CrudRepository
* @see org.springframework.data.repository.core.EntityInformation
*/
public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID> {
@@ -60,18 +69,21 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
private final GemfireTemplate template;
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Constructs a new instance of {@link SimpleGemfireRepository} initialized with the {@link GemfireTemplate}
* and {@link EntityInformation}.
*
* @param template {@link GemfireTemplate} used to perform basic data access operations and simple OQL queries;
* must not be {@literal null}.
* @param entityInformation {@link EntityInformation} used to describe the entity; must not be {@literal null}.
* @param entityInformation {@link EntityInformation} that describes the entity; must not be {@literal null}.
* @throws IllegalArgumentException if {@link GemfireTemplate} or {@link EntityInformation} is {@literal null}.
* @see org.springframework.data.gemfire.GemfireTemplate
* @see org.springframework.data.repository.core.EntityInformation
*/
public SimpleGemfireRepository(GemfireTemplate template, EntityInformation<T, ID> entityInformation) {
public SimpleGemfireRepository(@NonNull GemfireTemplate template,
@NonNull EntityInformation<T, ID> entityInformation) {
Assert.notNull(template, "GemfireTemplate must not be null");
Assert.notNull(entityInformation, "EntityInformation must not be null");
@@ -80,44 +92,110 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
this.entityInformation = entityInformation;
}
/**
* Returns a reference to the {@link EntityInformation} type describing the entity.
*
* @return a reference to the {@link EntityInformation} type describing the entity.
* @see org.springframework.data.repository.core.EntityInformation
*/
public @NonNull EntityInformation<T, ID> getEntityInformation() {
return this.entityInformation;
}
/**
* Returns a reference to the SLF4J {@link Logger} used to log the operations of this {@link GemfireRepository}.
*
* @return a reference to the SLF4J {@link Logger} used to log the operations of this {@link GemfireRepository}.
* @see org.slf4j.Logger
*/
public @NonNull Logger getLogger() {
return this.logger;
}
/**
* Gets the {@link Region} to which this {@link GemfireRepository} performs all data access operations.
*
* @return a reference to the {@link Region} on which this {@link GemfireRepository} operates.
* @see org.apache.geode.cache.Region
* @see #getTemplate()
*/
public @NonNull Region<ID, T> getRegion() {
return getTemplate().getRegion();
}
/**
* Returns a reference to the {@link GemfireTemplate} used by this {@link GemfireRepository} to perform basic
* CRUD data access operations and simple OQL queries.
*
* @return a reference to the {@link GemfireTemplate} used by this {@link GemfireRepository}.
* @see org.springframework.data.gemfire.GemfireTemplate
*/
public @NonNull GemfireTemplate getTemplate() {
return this.template;
}
@Override
public <U extends T> U save(U entity) {
public <U extends T> U save(@NonNull U entity) {
ID id = this.entityInformation.getRequiredId(entity);
ID id = getEntityInformation().getRequiredId(entity);
this.template.put(id, entity);
// CREATE/UPDATE entity in Region
T existingValue = getTemplate().put(id, entity);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Overwrote existing value [{}] for ID [{}]", existingValue, id);
}
return entity;
}
@Override
public T save(Wrapper<T, ID> wrapper) {
public T save(@NonNull Wrapper<T, ID> wrapper) {
T entity = wrapper.getEntity();
this.template.put(wrapper.getKey(), entity);
// CREATE/UPDATE entity in Region
T existingValue = getTemplate().put(wrapper.getKey(), entity);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Overwrote existing value [{}] for ID [{}]", existingValue, wrapper.getKey());
}
return entity;
}
@Override
public <U extends T> Iterable<U> saveAll(Iterable<U> entities) {
public <U extends T> Iterable<U> saveAll(@NonNull Iterable<U> entities) {
EntityInformation<T, ID> entityInformation = getEntityInformation();
Map<ID, U> entitiesToSave = new HashMap<>();
entities.forEach(entity -> entitiesToSave.put(this.entityInformation.getRequiredId(entity), entity));
Streamable.of(CollectionUtils.nullSafeIterable(entities)).stream()
.filter(Objects::nonNull)
.forEach(entity -> entitiesToSave.put(entityInformation.getRequiredId(entity), entity));
this.template.putAll(entitiesToSave);
if (!entitiesToSave.isEmpty()) {
getTemplate().putAll(entitiesToSave);
}
return entitiesToSave.values();
}
/**
* Counts the number of entities stored in the {@link Region}.
*
* This method executes a {@literal SELECT count(*) FROM /Region} OQL query.
*
* @return a count of the number of entities stored in the {@link Region}.
*/
@Override
public long count() {
String countQuery = String.format("SELECT count(*) FROM %s", this.template.getRegion().getFullPath());
String regionPath = getRegion().getFullPath();
String countQuery = String.format("SELECT count(*) FROM %s", regionPath);
SelectResults<Integer> results = this.template.find(countQuery);
SelectResults<Integer> results = getTemplate().find(countQuery);
return Optional.ofNullable(results)
.map(SelectResults::iterator)
@@ -127,64 +205,104 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
.orElse(0L);
}
/**
* Determines whether an entity with the given ID is stored in the {@link Region}.
*
* @param id {@link Long} value identifying the entity.
* @return a boolean value indicating whether an entity with the given ID is stored in the {@link Region}.
* @see #findById(Object)
*/
@Override
public boolean existsById(ID id) {
return findById(id).isPresent();
}
@Override
public Optional<T> findById(ID id) {
return Optional.ofNullable(this.template.get(id));
public @NonNull Iterable<T> findAll() {
String regionPath = getRegion().getFullPath();
String query = String.format("SELECT * FROM %s", regionPath);
SelectResults<T> selectResults = getTemplate().find(query);
return toList(selectResults);
}
@Override
public Collection<T> findAll() {
SelectResults<T> results =
this.template.find(String.format("SELECT * FROM %s", this.template.getRegion().getFullPath()));
return results.asList();
}
@Override
public Iterable<T> findAll(Sort sort) {
public @NonNull Iterable<T> findAll(@NonNull Sort sort) {
QueryString query = QueryString.of("SELECT * FROM /RegionPlaceholder")
.fromRegion(this.entityInformation.getJavaType(), this.template.getRegion())
.fromRegion(getEntityInformation().getJavaType(), getRegion())
.orderBy(sort);
SelectResults<T> selectResults = this.template.find(query.toString());
SelectResults<T> selectResults = getTemplate().find(query.toString());
return selectResults.asList();
return toList(selectResults);
}
@Override
public Collection<T> findAllById(Iterable<ID> ids) {
public @NonNull Iterable<T> findAllById(@NonNull Iterable<ID> ids) {
List<ID> keys = Streamable.of(ids).stream().collect(StreamUtils.toUnmodifiableList());
List<ID> keys = Streamable.of(CollectionUtils.nullSafeIterable(ids)).stream()
.filter(Objects::nonNull)
.collect(StreamUtils.toUnmodifiableList());
return CollectionUtils.<ID, T>nullSafeMap(this.template.getAll(keys)).values().stream()
.filter(Objects::nonNull).collect(Collectors.toList());
Map<ID, T> keysValues = !keys.isEmpty()
? getTemplate().getAll(keys)
: Collections.emptyMap();
List<T> values = CollectionUtils.nullSafeMap(keysValues).values().stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
return values;
}
@Override
public void deleteById(ID id) {
this.template.remove(id);
public Optional<T> findById(@NonNull ID id) {
T value = id != null
? getTemplate().get(id)
: null;
return Optional.ofNullable(value);
}
@Override
public void delete(T entity) {
deleteById(this.entityInformation.getRequiredId(entity));
public void delete(@NonNull T entity) {
deleteById(getEntityInformation().getRequiredId(entity));
}
@Override
public void deleteAll(Iterable<? extends T> entities) {
entities.forEach(this::delete);
public void deleteAll() {
getTemplate().execute((GemfireCallback<Void>) region -> {
if (isPartitioned(region) || isTransactionPresent(region)) {
doRegionClear(region);
}
else {
SpringUtils.safeDoOperation(() -> region.clear(), () -> doRegionClear(region));
}
return null;
});
}
@Override
public void deleteAll(@NonNull Iterable<? extends T> entities) {
CollectionUtils.nullSafeIterable(entities).forEach(this::delete);
}
@Override
public void deleteById(@NonNull ID id) {
getTemplate().remove(id);
}
boolean isPartitioned(Region<?, ?> region) {
return region != null && region.getAttributes() != null
return region != null
&& region.getAttributes() != null
&& isPartitioned(region.getAttributes().getDataPolicy());
}
@@ -206,23 +324,10 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
region.removeAll(region.keySet());
}
@Override
public void deleteAll() {
this.template.execute((GemfireCallback<Void>) region -> {
@NonNull List<T> toList(@Nullable SelectResults<T> selectResults) {
if (isPartitioned(region) || isTransactionPresent(region)) {
doRegionClear(region);
}
else {
try {
region.clear();
}
catch (UnsupportedOperationException ignore) {
doRegionClear(region);
}
}
return null;
});
return selectResults != null
? CollectionUtils.nullSafeList(selectResults.asList())
: Collections.emptyList();
}
}

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.repository.sample;
import org.springframework.data.annotation.Id;
@@ -23,7 +22,7 @@ import org.springframework.util.ObjectUtils;
* @author Stuart Williams
* @author John Blum
*/
public class Animal {
public class Animal implements Identifiable<Long> {
@Id
private Long id;
@@ -31,7 +30,7 @@ public class Animal {
private String name;
public Long getId() {
return id;
return this.id;
}
public void setId(Long id) {
@@ -39,7 +38,7 @@ public class Animal {
}
public String getName() {
return name;
return this.name;
}
public void setName(String name) {
@@ -48,7 +47,8 @@ public class Animal {
@Override
public boolean equals(final Object obj) {
if (obj == this) {
if (this == obj) {
return true;
}
@@ -64,15 +64,18 @@ public class Animal {
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getId());
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getName());
return hashValue;
}
@Override
public String toString() {
return String.format("{ @type = %1$s, id = %2$d, name = %3$s }", getClass().getSimpleName(), getId(), getName());
return String.format("{ @type = %1$s, id = %2$d, name = %3$s }",
getClass().getSimpleName(), getId(), getName());
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2020 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
*
* https://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.gemfire.repository.sample;
/**
* Defines a contract for Abstract Data Types (ADT) that can be identified.
*
* @author John Blum
* @since 2.4.0
*/
public interface Identifiable<T> {
T getId();
}

View File

@@ -13,27 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.repository.support;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import javax.annotation.Resource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionEvent;
import org.apache.geode.cache.query.SelectResults;
import org.apache.geode.cache.util.CacheListenerAdapter;
import org.junit.Before;
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.data.gemfire.GemfireTemplate;
@@ -48,11 +46,16 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Integration tests for {@link SimpleGemfireRepository}.
* Integration Tests for {@link SimpleGemfireRepository}.
*
* @author Oliver Gierke
* @author John Blum
* @see org.junit.Test
* @see org.apache.geode.cache.GemFireCache
* @see org.apache.geode.cache.Region
* @see org.springframework.data.gemfire.GemfireTemplate
* @see org.springframework.data.gemfire.LocalRegionFactoryBean
* @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication
* @see org.springframework.data.gemfire.repository.support.SimpleGemfireRepository
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringRunner
@@ -113,17 +116,17 @@ public class SimpleGemfireRepositoryIntegrationTests {
this.template.put(carter.getId(), carter);
this.template.put(leroi.getId(), leroi);
Collection<Person> result = this.repository.findAllById(Arrays.asList(carter.getId(), leroi.getId()));
Iterable<Person> result = this.repository.findAllById(Arrays.asList(carter.getId(), leroi.getId()));
assertThat(result).isNotNull();
assertThat(result.size()).isEqualTo(2);
assertThat(result).hasSize(2);
assertThat(result).containsAll(Arrays.asList(carter, leroi));
}
@Test
public void findAllWithIdsReturnsNoMatches() {
Collection<Person> results = this.repository.findAllById(Arrays.asList(1L, 2L));
Iterable<Person> results = this.repository.findAllById(Arrays.asList(1L, 2L));
assertThat(results).isNotNull();
assertThat(results).isEmpty();
@@ -139,7 +142,7 @@ public class SimpleGemfireRepositoryIntegrationTests {
this.template.put(kurt.getId(), kurt);
this.template.put(eddie.getId(), eddie);
Collection<Person> results = this.repository.findAllById(Arrays.asList(0L, 1L, 2L, 4L));
Iterable<Person> results = this.repository.findAllById(Arrays.asList(0L, 1L, 2L, 4L));
assertThat(results).isNotNull();
assertThat(results).hasSize(2);

View File

@@ -17,6 +17,8 @@ package org.springframework.data.gemfire.repository.support;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
@@ -35,6 +37,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -54,11 +57,15 @@ import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.query.SelectResults;
import org.springframework.data.domain.Sort;
import org.springframework.data.gemfire.GemfireTemplate;
import org.springframework.data.gemfire.repository.Wrapper;
import org.springframework.data.gemfire.repository.sample.Animal;
import org.springframework.data.gemfire.repository.sample.Identifiable;
import org.springframework.data.repository.core.EntityInformation;
import org.slf4j.Logger;
/**
* Unit Tests for {@link SimpleGemfireRepository}.
*
@@ -75,10 +82,12 @@ import org.springframework.data.repository.core.EntityInformation;
* @see org.springframework.data.repository.core.EntityInformation
* @since 1.4.5
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@SuppressWarnings({ "rawtypes", "unchecked" })
public class SimpleGemfireRepositoryUnitTests {
protected Map<Long, Animal> asMap(Iterable<Animal> animals) {
private final AtomicLong idSequence = new AtomicLong(0L);
private Map<Long, Animal> asMap(Iterable<Animal> animals) {
Map<Long, Animal> animalMap = new HashMap<>();
@@ -89,16 +98,17 @@ public class SimpleGemfireRepositoryUnitTests {
return animalMap;
}
protected Animal newAnimal(String name) {
private Animal newAnimal(String name) {
Animal animal = new Animal();
animal.setId(this.idSequence.incrementAndGet());
animal.setName(name);
return animal;
}
protected Animal newAnimal(Long id, String name) {
private Animal newAnimal(Long id, String name) {
Animal animal = newAnimal(name);
@@ -107,11 +117,15 @@ public class SimpleGemfireRepositoryUnitTests {
return animal;
}
protected GemfireTemplate newGemfireTemplate(Region<?, ?> region) {
private GemfireTemplate newGemfireTemplate(Region<?, ?> region) {
return new GemfireTemplate(region);
}
protected Cache mockCache(String name, boolean transactionExists) {
private <ID, T extends Identifiable<ID>> Wrapper<T, ID> newWrapper(T entity) {
return new Wrapper<>(entity, entity.getId());
}
private Cache mockCache(String name, boolean transactionExists) {
Cache mockCache = mock(Cache.class, String.format("%s.MockCache", name));
@@ -124,7 +138,7 @@ public class SimpleGemfireRepositoryUnitTests {
return mockCache;
}
protected EntityInformation<Animal, Long> mockEntityInformation() {
private EntityInformation<Animal, Long> mockEntityInformation() {
EntityInformation<Animal, Long> mockEntityInformation = mock(EntityInformation.class);
@@ -149,11 +163,11 @@ public class SimpleGemfireRepositoryUnitTests {
return mockEntityInformation;
}
protected Region mockRegion() {
private Region mockRegion() {
return mockRegion("MockRegion");
}
protected Region mockRegion(String name) {
private Region mockRegion(String name) {
Region mockRegion = mock(Region.class, String.format("%s.MockRegion", name));
@@ -163,7 +177,7 @@ public class SimpleGemfireRepositoryUnitTests {
return mockRegion;
}
protected Region mockRegion(String name, Cache mockCache, DataPolicy dataPolicy) {
private Region mockRegion(String name, Cache mockCache, DataPolicy dataPolicy) {
Region mockRegion = mockRegion(name);
@@ -179,17 +193,20 @@ public class SimpleGemfireRepositoryUnitTests {
}
@Test
public void constructsSimpleGemfireRepositorySuccessfully() {
public void constructSimpleGemfireRepositorySuccessfully() {
Region mockRegion = mock(Region.class);
Region mockRegion = mockRegion();
GemfireTemplate template = spy(new GemfireTemplate(mockRegion));
GemfireTemplate template = spy(newGemfireTemplate(mockRegion));
EntityInformation mockEntityInformation = mock(EntityInformation.class);
EntityInformation mockEntityInformation = mockEntityInformation();
SimpleGemfireRepository repository = new SimpleGemfireRepository(template, mockEntityInformation);
assertThat(repository).isNotNull();
assertThat(repository.getEntityInformation()).isEqualTo(mockEntityInformation);
assertThat(repository.getLogger()).isNotNull();
assertThat(repository.getTemplate()).isEqualTo(template);
verifyNoInteractions(template, mockRegion, mockEntityInformation);
}
@@ -225,29 +242,63 @@ public class SimpleGemfireRepositoryUnitTests {
}
@Test
public void saveEntityIsCorrect() {
public void getRegionFromTemplate() {
Region<Long, Animal> mockRegion = mockRegion();
GemfireTemplate template = spy(newGemfireTemplate(mockRegion));
EntityInformation<Animal, Long> mockEntityInformation = mockEntityInformation();
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(template, mockEntityInformation);
assertThat(repository).isNotNull();
assertThat(repository.getTemplate()).isEqualTo(template);
assertThat(repository.getRegion()).isEqualTo(mockRegion);
verify(template, times(1)).getRegion();
verifyNoInteractions(mockRegion, mockEntityInformation);
verifyNoMoreInteractions(template);
}
@Test
public void saveEntitySuccessfully() {
Animal cat = newAnimal(1L, "cat");
Logger mockLogger = mock(Logger.class);
Region<Long, Animal> mockRegion = mockRegion();
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation());
spy(new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation()));
Animal dog = repository.save(newAnimal("dog"));
doReturn(mockLogger).when(repository).getLogger();
doReturn(true).when(mockLogger).isDebugEnabled();
doReturn(cat).when(mockRegion).put(anyLong(), any());
Animal dog = repository.save(newAnimal(1L, "dog"));
assertThat(dog).isNotNull();
assertThat(dog.getId().longValue()).isEqualTo(1L);
assertThat(dog.getName()).isEqualTo("dog");
verify(mockLogger, times(1)).isDebugEnabled();
verify(mockLogger, times(1))
.debug(eq("Overwrote existing value [{}] for ID [{}]"), eq(cat), eq(1L));
verify(mockRegion, times(1)).put(eq(1L), eq(dog));
verifyNoMoreInteractions(mockLogger, mockRegion);
}
@Test
public void saveEntitiesIsCorrect() {
public void saveEntitiesSuccessfully() {
List<Animal> animals = new ArrayList<>(3);
animals.add(newAnimal("bird"));
animals.add(newAnimal("cat"));
animals.add(newAnimal("dog"));
animals.add(newAnimal(1L, "bird"));
animals.add(newAnimal(2L, "cat"));
animals.add(newAnimal(3L, "dog"));
Region<Long, Animal> mockRegion = mockRegion();
@@ -257,24 +308,82 @@ public class SimpleGemfireRepositoryUnitTests {
Iterable<Animal> savedAnimals = repository.saveAll(animals);
assertThat(savedAnimals).isNotNull();
assertThat(savedAnimals).isNotSameAs(animals);
assertThat(savedAnimals).containsAll(animals);
verify(mockRegion, times(1)).putAll(eq(asMap(savedAnimals)));
verifyNoMoreInteractions(mockRegion);
}
@Test
public void saveWrapperIsCorrect() {
Animal dog = newAnimal(1L, "dog");
public void saveWrapperSuccessfully() {
Wrapper dogWrapper = new Wrapper(dog, dog.getId());
Animal dog = newAnimal(2L, "dog");
Logger mockLogger = mock(Logger.class);
Region<Long, Animal> mockRegion = mockRegion();
SimpleGemfireRepository<Animal, Long> repository =
spy(new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation()));
doReturn(mockLogger).when(repository).getLogger();
doReturn(true).when(mockLogger).isDebugEnabled();
doReturn(dog).when(mockRegion).put(anyLong(), any());
Animal cat = repository.save(newWrapper(newAnimal(2L, "cat")));
assertThat(cat).isNotNull();
assertThat(cat.getId()).isEqualTo(2L);
assertThat(cat.getName()).isEqualTo("cat");
verify(mockLogger, times(1)).isDebugEnabled();
verify(mockLogger, times(1))
.debug(eq("Overwrote existing value [{}] for ID [{}]"), eq(dog), eq(2L));
verify(mockRegion, times(1)).put(eq(2L), eq(cat));
verifyNoMoreInteractions(mockLogger, mockRegion);
}
@Test
public void saveAllEntitiesWithIterableContainingNullEntitiesIsNullSafe() {
List<Animal> animals = new ArrayList<>();
animals.add(newAnimal(1L, "cat"));
animals.add(null);
animals.add(newAnimal(2L, "dog"));
Region<Long, Animal> mockRegion = mockRegion();
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation());
assertThat(repository.save(dogWrapper)).isEqualTo(dog);
Iterable<Animal> savedAnimals = repository.saveAll(animals);
verify(mockRegion, times(1)).put(eq(dog.getId()), eq(dog));
assertThat(savedAnimals).isNotNull();
assertThat(savedAnimals).isNotSameAs(animals);
assertThat(savedAnimals).hasSize(2);
assertThat(savedAnimals).containsAll(Arrays.asList(animals.get(0), animals.get(2)));
verify(mockRegion, times(1))
.putAll(eq(asMap(Arrays.asList(animals.get(0), animals.get(2)))));
verifyNoMoreInteractions(mockRegion);
}
@Test
public void saveAllEntitiesWithNullIterableIsNullSafe() {
Region<Long, Animal> mockRegion = mock(Region.class);
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation());
Iterable<?> iterable = repository.saveAll(null);
assertThat(iterable).isNotNull();
assertThat(iterable).isEmpty();
verifyNoInteractions(mockRegion);
}
@Test
@@ -287,7 +396,7 @@ public class SimpleGemfireRepositoryUnitTests {
GemfireTemplate template = spy(newGemfireTemplate(mockRegion));
doReturn(mockSelectResults).when(template).find(eq("SELECT count(*) FROM /Example"));
when(mockSelectResults.iterator()).thenReturn(Collections.singletonList(21).iterator());
doReturn(Collections.singletonList(21).iterator()).when(mockSelectResults).iterator();
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(template, mockEntityInformation());
@@ -309,7 +418,7 @@ public class SimpleGemfireRepositoryUnitTests {
GemfireTemplate template = spy(newGemfireTemplate(mockRegion));
doReturn(null).when(template).find(eq("SELECT count(*) FROM /Example"));
doReturn(null).when(template).find(anyString());
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(template, mockEntityInformation());
@@ -332,7 +441,7 @@ public class SimpleGemfireRepositoryUnitTests {
GemfireTemplate template = spy(newGemfireTemplate(mockRegion));
doReturn(mockSelectResults).when(template).find(eq("SELECT count(*) FROM /Example"));
doReturn(mockSelectResults).when(template).find(anyString());
doReturn(null).when(mockSelectResults).iterator();
SimpleGemfireRepository<Animal, Long> repository =
@@ -357,7 +466,7 @@ public class SimpleGemfireRepositoryUnitTests {
GemfireTemplate template = spy(newGemfireTemplate(mockRegion));
doReturn(mockSelectResults).when(template).find(eq("SELECT count(*) FROM /Example"));
doReturn(mockSelectResults).when(template).find(anyString());
doReturn(Collections.emptyIterator()).when(mockSelectResults).iterator();
SimpleGemfireRepository<Animal, Long> repository =
@@ -374,123 +483,263 @@ public class SimpleGemfireRepositoryUnitTests {
}
@Test
public void existsIsCorrect() {
public void countWhenSelectResultsIteratorContainsNullIsNullSafeReturnsZero() {
SelectResults mockSelectResults = mock(SelectResults.class);
Region mockRegion = mockRegion("Example");
Iterator mockIterator = mock(Iterator.class);
GemfireTemplate template = spy(newGemfireTemplate(mockRegion));
doReturn(mockSelectResults).when(template).find(anyString());
doReturn(mockIterator).when(mockSelectResults).iterator();
doReturn(true).when(mockIterator).hasNext();
doReturn(null).when(mockIterator).next();
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(template, mockEntityInformation());
assertThat(repository).isNotNull();
assertThat(repository.count()).isEqualTo(0L);
verify(template, times(1)).getRegion();
verify(mockRegion, times(1)).getFullPath();
verify(template, times(1)).find(eq("SELECT count(*) FROM /Example"));
verify(mockSelectResults, times(1)).iterator();
verify(mockIterator, times(1)).hasNext();
verify(mockIterator, times(1)).next();
verifyNoMoreInteractions(mockIterator, mockRegion, mockSelectResults, template);
}
@Test
public void existsByIdCallsFindById() {
Animal dog = newAnimal(1L, "dog");
Region<Long, Animal> mockRegion = mockRegion();
when(mockRegion.get(any(Long.class))).then(
invocation -> (dog.getId().equals(invocation.getArguments()[0]) ? dog : null));
doAnswer(invocation -> dog.getId().equals(invocation.getArgument(0)) ? dog : null)
.when(mockRegion).get(anyLong());
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation());
spy(new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation()));
assertThat(repository.existsById(1L)).isTrue();
assertThat(repository.existsById(2L)).isFalse();
assertThat(repository.existsById(10L)).isFalse();
verify(repository, times(1)).findById(eq(1L));
verify(mockRegion, times(1)).get(eq(1L));
verify(repository, times(1)).findById(eq(2L));
verify(mockRegion, times(1)).get(eq(2L));
verify(repository, times(1)).findById(eq(10L));
verify(mockRegion, times(1)).get(eq(10L));
verifyNoMoreInteractions(mockRegion);
}
@Test
public void findOneIsCorrect() {
Animal dog = newAnimal(1L, "dog");
public void findAllSuccessfully() {
GemfireTemplate mockTemplate = mock(GemfireTemplate.class);
List<Object> results = Arrays.asList("test", "mock", "fake");
Region mockRegion = mockRegion("Example");
SelectResults mockSelectResults = mock(SelectResults.class);
doReturn(mockRegion).when(mockTemplate).getRegion();
doReturn(mockSelectResults).when(mockTemplate).find(anyString());
doReturn(results).when(mockSelectResults).asList();
SimpleGemfireRepository repository = new SimpleGemfireRepository(mockTemplate, mockEntityInformation());
Iterable<Object> returnValue = repository.findAll();
assertThat(returnValue).isNotNull();
assertThat(returnValue).containsAll(results);
verify(mockTemplate, times(1)).getRegion();
verify(mockRegion, times(1)).getFullPath();
verify(mockTemplate, times(1)).find(eq("SELECT * FROM /Example"));
verify(mockSelectResults, times(1)).asList();
verifyNoMoreInteractions(mockRegion, mockSelectResults, mockTemplate);
}
@Test
public void findAllOrderedBySortSuccessfully() {
GemfireTemplate mockTemplate = mock(GemfireTemplate.class);
List<Object> results = Arrays.asList("test", "mock", "fake");
Region mockRegion = mockRegion("Example");
SelectResults mockSelectResults = mock(SelectResults.class);
doReturn(mockRegion).when(mockTemplate).getRegion();
doReturn(mockSelectResults).when(mockTemplate).find(anyString());
doReturn(results).when(mockSelectResults).asList();
SimpleGemfireRepository repository = new SimpleGemfireRepository(mockTemplate, mockEntityInformation());
Iterable<Object> returnValue = repository.findAll(Sort.by(Sort.Order.asc("name"), Sort.Order.desc("birthDate")));
assertThat(returnValue).isNotNull();
assertThat(returnValue).containsAll(results);
verify(mockTemplate, times(1)).getRegion();
verify(mockRegion, times(1)).getFullPath();
verify(mockTemplate, times(1))
.find(eq("SELECT DISTINCT * FROM /Example ORDER BY name ASC, birthDate DESC"));
verify(mockSelectResults, times(1)).asList();
verifyNoMoreInteractions(mockRegion, mockSelectResults, mockTemplate);
}
@Test
public void findAllByIdSuccessfully() {
Map<Long, Animal> animals = Stream.of(
newAnimal(1L, "bird"),
newAnimal(2L, "cat"),
newAnimal(3L, "dog")
).collect(Collectors.toMap(Animal::getId, Function.identity()));
Region<Long, Animal> mockRegion = mockRegion();
when(mockRegion.get(any(Long.class))).then(
invocation -> (dog.getId().equals(invocation.getArguments()[0]) ? dog : null));
doAnswer(invocation -> {
Collection<Long> keys = invocation.getArgument(0);
return animals.values().stream()
.filter((animal -> keys.contains(animal.getId())))
.collect(Collectors.toMap(Animal::getId, Function.identity()));
}).when(mockRegion).getAll(any(Collection.class));
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation());
assertThat(repository.findById(1L).orElse(null)).isEqualTo(dog);
assertThat(repository.findById(10L).isPresent()).isFalse();
}
@Test
public void findAllIsCorrect() {
Map<Long, Animal> animals =
Stream.of(newAnimal(1L, "bird"), newAnimal(2L, "cat"), newAnimal(3L, "dog"))
.collect(Collectors.toMap(Animal::getId, Function.identity()));
Region<Long, Animal> mockRegion = mockRegion();
when(mockRegion.getAll(any(Collection.class))).then(invocation -> {
Collection<Long> keys = invocation.getArgument(0);
return animals.values().stream().filter((animal -> keys.contains(animal.getId())))
.collect(Collectors.toMap(Animal::getId, Function.identity()));
});
SimpleGemfireRepository<Animal, Long> repository = new SimpleGemfireRepository<>(
newGemfireTemplate(mockRegion), mockEntityInformation());
Collection<Animal> animalsFound = repository.findAllById(Arrays.asList(1L, 3L));
Iterable<Animal> animalsFound = repository.findAllById(Arrays.asList(1L, 3L));
assertThat(animalsFound).isNotNull();
assertThat(animalsFound).hasSize(2);
assertThat(animalsFound).contains(animals.get(1L), animals.get(3L));
verify(mockRegion, times(1)).getAll(eq(Arrays.asList(1L, 3L)));
verifyNoMoreInteractions(mockRegion);
}
@Test
public void findAllWithIdsReturnsNoMatches() {
public void findAllByIdReturnsNoMatches() {
Region<Long, Animal> mockRegion = mockRegion();
when(mockRegion.getAll(any(Collection.class))).then(invocation -> {
Collection<Long> keys = invocation.getArgument(0);
Map<Long, Animal> result = new HashMap<>(keys.size());
for (Long key : keys) {
result.put(key, null);
}
return result;
});
doReturn(Collections.emptyMap()).when(mockRegion).getAll(any(Collection.class));
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation());
Collection<Animal> animalsFound = repository.findAllById(Arrays.asList(1L, 2L, 3L));
Iterable<Animal> animalsFound = repository.findAllById(Arrays.asList(1L, null, 2L, null, 3L));
assertThat(animalsFound).isNotNull();
assertThat(animalsFound).isEmpty();
verify(mockRegion, times(1)).getAll(eq(Arrays.asList(1L, 2L, 3L)));
verifyNoMoreInteractions(mockRegion);
}
@Test
public void findAllWithIdsReturnsPartialMatches() {
Map<Long, Animal> animals =
Stream.of(newAnimal(1L, "bird"), newAnimal(2L, "cat"), newAnimal(3L, "dog"))
.collect(Collectors.toMap(Animal::getId, Function.identity()));
public void findAllByIdReturnsPartialMatches() {
Map<Long, Animal> animals = Stream.of(
newAnimal(1L, "bird"),
newAnimal(2L, "cat"),
newAnimal(3L, "dog")
).collect(Collectors.toMap(Animal::getId, Function.identity()));
Region<Long, Animal> mockRegion = mockRegion();
when(mockRegion.getAll(any(Collection.class))).then(invocation -> {
doAnswer(invocation -> {
Collection<Long> keys = invocation.getArgument(0);
Map<Long, Animal> result = new HashMap<>(keys.size());
for (Long key : keys) {
result.put(key, animals.get(key));
}
return keys.stream()
.filter(animals::containsKey)
.collect(Collectors.toMap(Function.identity(), animals::get));
return result;
});
}).when(mockRegion).getAll(any(Collection.class));
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation());
Collection<Animal> animalsFound = repository.findAllById(Arrays.asList(0L, 1L, 2L, 4L));
Iterable<Animal> animalsFound = repository.findAllById(Arrays.asList(null, 0L, null, 1L, 2L, 4L, null));
assertThat(animalsFound).isNotNull();
assertThat(animalsFound).hasSize(2);
assertThat(animalsFound).contains(animals.get(1L), animals.get(2L));
verify(mockRegion, times(1)).getAll(eq(Arrays.asList(0L, 1L, 2L, 4L)));
verifyNoMoreInteractions(mockRegion);
}
@Test
public void deleteByIdIsCorrect() {
public void findAllByIdWithNullIterableIsNullSafe() {
Region<Long, Animal> mockRegion = mockRegion();
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository(newGemfireTemplate(mockRegion), mockEntityInformation());
Iterable<Animal> animals = repository.findAllById(null);
assertThat(animals).isNotNull();
assertThat(animals).isEmpty();
verifyNoInteractions(mockRegion);
}
@Test
public void findByIdSuccessfully() {
Animal dog = newAnimal(1L, "dog");
Region<Long, Animal> mockRegion = mockRegion();
doAnswer(invocation -> dog.getId().equals(invocation.getArgument(0)) ? dog : null)
.when(mockRegion).get(any(Long.class));
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation());
assertThat(repository.findById(1L).orElse(null)).isEqualTo(dog);
assertThat(repository.findById(2L).isPresent()).isFalse();
assertThat(repository.findById(10L).isPresent()).isFalse();
verify(mockRegion, times(1)).get(eq(1L));
verify(mockRegion, times(1)).get(eq(2L));
verify(mockRegion, times(1)).get(eq(10L));
verifyNoMoreInteractions(mockRegion);
}
@Test
public void findByIdWithNullIdIsNullSafe() {
Region mockRegion = mockRegion();
SimpleGemfireRepository repository =
new SimpleGemfireRepository(newGemfireTemplate(mockRegion), mockEntityInformation());
assertThat(repository.findById(null).isPresent()).isFalse();
verifyNoInteractions(mockRegion);
}
@Test
public void deleteByIdSuccessfully() {
Region<Long, Animal> mockRegion = mockRegion();
SimpleGemfireRepository<Animal, Long> repository =
@@ -499,37 +748,47 @@ public class SimpleGemfireRepositoryUnitTests {
repository.deleteById(1L);
verify(mockRegion, times(1)).remove(eq(1L));
verifyNoMoreInteractions(mockRegion);
}
@Test
public void deleteEntityIsCorrect() {
public void deleteEntitySuccessfully() {
Region<Long, Animal> mockRegion = mockRegion();
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation());
spy(new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation()));
repository.delete(newAnimal(1L, "dog"));
verify(repository, times(1)).deleteById(eq(1L));
verify(mockRegion, times(1)).remove(eq(1L));
verifyNoMoreInteractions(mockRegion);
}
@Test
public void deleteEntitiesIsCorrect() {
public void deleteEntitiesSuccessfully() {
Region<Long, Animal> mockRegion = mockRegion();
SimpleGemfireRepository<Animal, Long> repository =
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion), mockEntityInformation());
repository.deleteAll(Arrays.asList(newAnimal(1L, "bird"), newAnimal(2L, "cat"),
newAnimal(3L, "dog")));
repository.deleteAll(Arrays.asList(
newAnimal(1L, "bird"),
newAnimal(2L, "cat"),
newAnimal(3L, "dog")
));
verify(mockRegion, times(1)).remove(eq(1L));
verify(mockRegion, times(1)).remove(eq(2L));
verify(mockRegion, times(1)).remove(eq(3L));
verifyNoMoreInteractions(mockRegion);
}
@Test
public void deleteAllWithClear() {
Cache mockCache = mockCache("MockCache", false);
Region<Long, Animal> mockRegion = mockRegion("MockRegion", mockCache, DataPolicy.REPLICATE);
@@ -547,6 +806,7 @@ public class SimpleGemfireRepositoryUnitTests {
@Test
public void deleteAllWithKeysWhenClearThrowsException() {
Cache mockCache = mockCache("MockCache", false);
Region<Long, Animal> mockRegion = mockRegion("MockRegion", mockCache, DataPolicy.PERSISTENT_REPLICATE);
@@ -570,6 +830,7 @@ public class SimpleGemfireRepositoryUnitTests {
@Test
public void deleteAllWithKeysWhenPartitionRegion() {
Cache mockCache = mockCache("MockCache", false);
Region<Long, Animal> mockRegion = mockRegion("MockRegion", mockCache, DataPolicy.PERSISTENT_PARTITION);
@@ -592,6 +853,7 @@ public class SimpleGemfireRepositoryUnitTests {
@Test
public void deleteAllWithKeysWhenTransactionPresent() {
Cache mockCache = mockCache("MockCache", true);
Region<Long, Animal> mockRegion = mockRegion("MockRegion", mockCache, DataPolicy.REPLICATE);