diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/GemfireOperations.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/GemfireOperations.java index e388544d..88294f95 100644 --- a/spring-data-geode/src/main/java/org/springframework/data/gemfire/GemfireOperations.java +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/GemfireOperations.java @@ -14,6 +14,8 @@ package org.springframework.data.gemfire; import java.util.Collection; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import org.apache.geode.cache.Region; import org.apache.geode.cache.query.Query; @@ -22,10 +24,16 @@ import org.apache.geode.cache.query.SelectResults; import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.gemfire.util.CollectionUtils; /** + * {@link GemfireOperations} defines the {{@link Region} data access operations that can be performed + * using the {@literal Template software design pattern}. + * * @author David Turanski * @author John Blum + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.query.QueryService */ public interface GemfireOperations { @@ -41,16 +49,31 @@ public interface GemfireOperations { V get(K key); - Map getAll(Collection keys); + @SuppressWarnings("unchecked") + default Map getAll(Collection keys) { + + return CollectionUtils.nullSafeCollection(keys).stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap(key -> (K) key, this::get)); + } V put(K key, V value); - void putAll(Map map); + default void putAll(Map map) { + CollectionUtils.nullSafeMap(map).forEach((key, value) -> put(key, value)); + } V putIfAbsent(K key, V value); V remove(K key); + default void removeAll(Collection keys) { + + CollectionUtils.nullSafeCollection(keys).stream() + .filter(Objects::nonNull) + .forEach(this::remove); + } + V replace(K key, V value); boolean replace(K key, V oldValue, V newValue); diff --git a/spring-data-geode/src/test/java/org/springframework/data/gemfire/GemfireOperationsUnitTests.java b/spring-data-geode/src/test/java/org/springframework/data/gemfire/GemfireOperationsUnitTests.java new file mode 100644 index 00000000..6a31e5a6 --- /dev/null +++ b/spring-data-geode/src/test/java/org/springframework/data/gemfire/GemfireOperationsUnitTests.java @@ -0,0 +1,146 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.springframework.data.gemfire.test.support.MapBuilder; + +/** + * Unit Tests for the {@link GemfireOperations} interface. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.junit.MockitoJUnitRunner + * @see org.springframework.data.gemfire.GemfireOperations + * @since 2.5.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class GemfireOperationsUnitTests { + + @Mock + private GemfireOperations mockGemfireOperations; + + @Test + public void defaultGetAllKeysCallsGetForEachKey() { + + Map expected = MapBuilder.newMapBuilder() + .put(1, "one") + .put(2, "two") + .put(3, "three") + .build(); + + doAnswer(invocation -> expected.get(invocation.getArgument(0))) + .when(this.mockGemfireOperations).get(isA(Integer.class)); + + doCallRealMethod().when(this.mockGemfireOperations).getAll(isA(Collection.class)); + + Map actual = this.mockGemfireOperations.getAll(expected.keySet()); + + assertThat(actual).isNotNull(); + assertThat(actual).isEqualTo(expected); + + verify(this.mockGemfireOperations, times(1)).getAll(eq(expected.keySet())); + + expected.forEach((key, value) -> + verify(this.mockGemfireOperations, times(1)).get(eq(key))); + + verifyNoMoreInteractions(this.mockGemfireOperations); + } + + @Test + public void defaultGetAllIsNullSafe() { + + doCallRealMethod().when(this.mockGemfireOperations).getAll(any()); + + Map map = this.mockGemfireOperations.getAll(null); + + assertThat(map).isNotNull(); + assertThat(map).isEmpty(); + } + + @Test + public void defaultPutAllCallsPutForEachKeyValue() { + + Map expected = MapBuilder.newMapBuilder() + .put(1, "one") + .put(2, "two") + .put(3, "three") + .build(); + + doCallRealMethod().when(this.mockGemfireOperations).putAll(isA(Map.class)); + + this.mockGemfireOperations.putAll(expected); + + verify(this.mockGemfireOperations, times(1)).putAll(eq(expected)); + + expected.forEach((key, value) -> + verify(this.mockGemfireOperations, times(1)).put(eq(key), eq(value))); + + verifyNoMoreInteractions(this.mockGemfireOperations); + } + + @Test + public void defaultPutAllIsNullSafe() { + + doCallRealMethod().when(this.mockGemfireOperations).putAll(any()); + + this.mockGemfireOperations.putAll(null); + } + + @Test + public void defaultRemoveAllCallsRemoveForEachKey() { + + Collection keys = Arrays.asList(1, 2, 3); + + doCallRealMethod().when(this.mockGemfireOperations).removeAll(isA(Collection.class)); + + this.mockGemfireOperations.removeAll(keys); + + verify(this.mockGemfireOperations, times(1)).removeAll(eq(keys)); + + keys.forEach(key -> verify(this.mockGemfireOperations, times(1)).remove(eq(key))); + + verifyNoMoreInteractions(this.mockGemfireOperations); + } + + @Test + public void defaultRemoveAllIsNullSafe() { + + doCallRealMethod().when(this.mockGemfireOperations).removeAll(any()); + + this.mockGemfireOperations.removeAll(null); + } +}