diff --git a/apache-geode-extensions/src/main/java/org/springframework/geode/util/CacheUtils.java b/apache-geode-extensions/src/main/java/org/springframework/geode/util/CacheUtils.java new file mode 100644 index 00000000..289e42b8 --- /dev/null +++ b/apache-geode-extensions/src/main/java/org/springframework/geode/util/CacheUtils.java @@ -0,0 +1,171 @@ +/* + * 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.geode.util; + +import static org.springframework.geode.util.GeodeAssertions.assertThat; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionAttributes; +import org.apache.geode.cache.RegionService; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.internal.cache.GemFireCacheImpl; + +/** + * Abstract utility class for working with GemFire/Geode cache instances, such as {@link ClientCache} + * and {@literal peer} {@link Cache} instances. + * + * @author John Blum + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.RegionService + * @see org.apache.geode.cache.client.ClientCache + * @since 1.3.0 + */ +public abstract class CacheUtils { + + /** + * Collects all {@link Object values} from the given {@link Region}. + * + * This method is capable of pulling {@link Object values} from either {@literal client} + * or {@literal peer} {@link Region Regions}. + * + * @param {@link Class type} of the {@link Region} {@link Object values}. + * @param region {@link Region} from which to collect the {@link Object values}. + * @return a {@link Collection} of all {@link Object values} from the given {@link Region}. + * @throws IllegalArgumentException if {@link Region} is {@literal null}. + * @see org.apache.geode.cache.Region + * @see java.util.Collection + */ + // TODO Add Predicate-based Filtering + public static Collection collectValues(Region region) { + + assertThat(region).isNotNull(); + + return isClientRegion(region) + ? clientRegionValues(region) + : region.values(); + + } + + private static Collection clientRegionValues(Region region) { + + Set keys = nullSafeSet(region.keySetOnServer()); + + return !keys.isEmpty() ? getAll(keys, region) : Collections.emptySet(); + } + + private static Collection getAll(Set keys, Region region) { + return nullSafeMap(region.getAll(keys)).values(); + // Fallback procedure if region.getAll(keys) is buggered + //return keys.stream().map(region::get).collect(Collectors.toSet()); + } + + private static boolean hasText(String value) { + return !(value == null || value.trim().isEmpty()); + } + + private static Map nullSafeMap(Map map) { + return map != null ? map :Collections.emptyMap(); + } + + private static Set nullSafeSet(Set set) { + return set != null ? set : Collections.emptySet(); + } + + /** + * Null-safe method to determine whether the given {@link RegionService} is an instance of {@link ClientCache}. + * + * The problem is, {@link GemFireCacheImpl} implements both the (peer) {@link Cache} + * and {@link ClientCache} interfaces. #sigh + * + * @param regionService {@link RegionService} to evaluate. + * @return a boolean value indicating whether the {@link RegionService} an instance of {@link ClientCache}. + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.RegionService + */ + public static boolean isClientCache(RegionService regionService) { + + boolean result = regionService instanceof ClientCache; + + if (regionService instanceof GemFireCacheImpl) { + result &= ((GemFireCacheImpl) regionService).isClient(); + } + + return result; + } + + /** + * Null-safe method to determine whether the given {@link Region} is a {@literal client} {@link Region} + * in a {@link ClientCache}. + * + * @param region {@link Region} to evaluate. + * @return a boolean value indicating whether the given {@link Region} is a {@literal client} {@link Region}. + * @see org.apache.geode.cache.Region + * @see #isClientCache(RegionService) + */ + public static boolean isClientRegion(Region region) { + + return region != null && (isClientCache(region.getRegionService()) + || Optional.ofNullable(region.getAttributes()) + .map(RegionAttributes::getPoolName) + .filter(CacheUtils::hasText) + .isPresent()); + } + + /** + * Null-safe method to determine whether the given {@link RegionService} is an instance of + * a {@literal peer} {@link Cache}. + * + * The problem is, {@link GemFireCacheImpl} implements both the (peer) {@link Cache} + * and {@link ClientCache} interfaces. #sigh + * + * @param regionService {@link RegionService} to evaluate. + * @return a boolean value indicating whether the {@link RegionService} is an instance of + * a {@literal peer} {@link Cache}. + * @see org.apache.geode.cache.RegionService + * @see org.apache.geode.cache.Cache + */ + public static boolean isPeerCache(RegionService regionService) { + + boolean result = regionService instanceof Cache; + + if (regionService instanceof GemFireCacheImpl) { + result &= !((GemFireCacheImpl) regionService).isClient(); + } + + return result; + } + + /** + * Null-safe method to determine whether the given {@link Region} is a {@literal peer} {@link Region} + * in a {@literal peer} {@link Cache}. + * + * @param region {@link Region} to evaluate. + * @return a boolean value indicating whether the given {@link Region} is a {@literal peer} {@link Region}. + * @see org.apache.geode.cache.Region + * @see #isPeerCache(RegionService) + */ + public static boolean isPeerRegion(Region region) { + return region != null && !isClientRegion(region); + } +} diff --git a/apache-geode-extensions/src/test/java/org/springframework/geode/util/CacheUtilsUnitTests.java b/apache-geode-extensions/src/test/java/org/springframework/geode/util/CacheUtilsUnitTests.java new file mode 100644 index 00000000..afc488a0 --- /dev/null +++ b/apache-geode-extensions/src/test/java/org/springframework/geode/util/CacheUtilsUnitTests.java @@ -0,0 +1,373 @@ +/* + * 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.geode.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.junit.Test; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionAttributes; +import org.apache.geode.cache.RegionService; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.internal.cache.GemFireCacheImpl; + +/** + * Unit Tests for {@link CacheUtils}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.RegionAttributes + * @see org.apache.geode.cache.RegionService + * @see org.apache.geode.cache.client.ClientCache + * @since 1.3.0 + */ +@SuppressWarnings("unchecked") +public class CacheUtilsUnitTests { + + @Test + public void collectValuesFromClientRegion() { + + Region mockRegion = mock(Region.class); + + RegionService mockRegionService = mock(ClientCache.class); + + Set keySetOnServer = new TreeSet<>(Arrays.asList(1, 2, 3)); + + Map keysValues = new HashMap<>(); + + keysValues.put(1, "one"); + keysValues.put(2, "two"); + keysValues.put(3, "three"); + + doReturn(mockRegionService).when(mockRegion).getRegionService(); + doReturn(keySetOnServer).when(mockRegion).keySetOnServer(); + doReturn(keysValues).when(mockRegion).getAll(eq(keySetOnServer)); + + assertThat(CacheUtils.collectValues(mockRegion)).containsExactlyInAnyOrder(keysValues.values().toArray()); + + verify(mockRegion, times(1)).getRegionService(); + verify(mockRegion, times(1)).keySetOnServer(); + verify(mockRegion, times(1)).getAll(eq(keySetOnServer)); + verifyNoMoreInteractions(mockRegion); + verifyNoInteractions(mockRegionService); + } + + @Test + public void collectValuesFromClientRegionWhenClientRegionGetAllKeysReturnsNullMapIsNullSafe() { + + Region mockRegion = mock(Region.class); + + RegionService mockRegionService = mock(ClientCache.class); + + Set keySetOnServer = new TreeSet<>(Arrays.asList(1, 2)); + + doReturn(mockRegionService).when(mockRegion).getRegionService(); + doReturn(keySetOnServer).when(mockRegion).keySetOnServer(); + doReturn(null).when(mockRegion).getAll(eq(keySetOnServer)); + + Collection values = CacheUtils.collectValues(mockRegion); + + assertThat(values).isNotNull(); + assertThat(values).isEmpty(); + + verify(mockRegion, times(1)).getRegionService(); + verify(mockRegion, times(1)).keySetOnServer(); + verify(mockRegion, times(1)).getAll(eq(keySetOnServer)); + verifyNoMoreInteractions(mockRegion); + verifyNoInteractions(mockRegionService); + } + + @Test + public void collectValuesFromClientRegionWhenClientRegionKeySetOnServerReturnsNullSetIsNullSafe() { + + Region mockRegion = mock(Region.class); + + RegionService mockRegionService = mock(ClientCache.class); + + doReturn(mockRegionService).when(mockRegion).getRegionService(); + doReturn(null).when(mockRegion).keySetOnServer(); + + Collection values = CacheUtils.collectValues(mockRegion); + + assertThat(values).isNotNull(); + assertThat(values).isEmpty(); + + verify(mockRegion, times(1)).getRegionService(); + verify(mockRegion, times(1)).keySetOnServer(); + verifyNoMoreInteractions(mockRegion); + verifyNoInteractions(mockRegionService); + } + + @Test + public void collectValuesFromClientRegionWhenClientRegionGetRegionServiceReturnsNullIsNullSafe() { + + Collection values = Arrays.asList("one", "two", "three"); + + Region mockRegion = mock(Region.class); + + doReturn(null).when(mockRegion).getRegionService(); + doReturn(values).when(mockRegion).values(); + + assertThat(CacheUtils.collectValues(mockRegion)).containsExactlyInAnyOrder(values.toArray()); + + verify(mockRegion, times(1)).getRegionService(); + verify(mockRegion, times(1)).values(); + } + + @Test + public void collectValuesFromPeerRegion() { + + Region mockRegion = mock(Region.class); + + RegionService mockRegionService = mock(Cache.class); + + Collection values = Arrays.asList("one", "two", "three"); + + doReturn(mockRegionService).when(mockRegion).getRegionService(); + doReturn(values).when(mockRegion).values(); + + assertThat(CacheUtils.collectValues(mockRegion)).containsExactlyInAnyOrder(values.toArray()); + + verify(mockRegion, times(1)).getRegionService(); + verify(mockRegion, times(1)).values(); + verifyNoInteractions(mockRegionService); + } + + @Test(expected = IllegalArgumentException.class) + public void collectValuesWithNullRegionThrowsIllegalArgumentException() { + + try { + CacheUtils.collectValues(null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessageStartingWith("Argument must not be null"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void isClientCacheWithClientCache() { + assertThat(CacheUtils.isClientCache(mock(ClientCache.class))).isTrue(); + } + + @Test + public void isClientCacheWithGemFireCacheImplWhenIsClientReturnsTrue() { + + GemFireCacheImpl mockCache = mock(GemFireCacheImpl.class); + + doReturn(true).when(mockCache).isClient(); + + assertThat(CacheUtils.isClientCache(mockCache)).isTrue(); + + verify(mockCache, times(1)).isClient(); + verifyNoMoreInteractions(mockCache); + } + + @Test + public void isClientCacheWithGemFireCacheImplWhenIsClientReturnsFalse() { + + GemFireCacheImpl mockCache = mock(GemFireCacheImpl.class); + + doReturn(false).when(mockCache).isClient(); + + assertThat(CacheUtils.isClientCache(mockCache)).isFalse(); + + verify(mockCache, times(1)).isClient(); + verifyNoMoreInteractions(mockCache); + } + + @Test + public void isClientCacheWithNonClientCache() { + assertThat(CacheUtils.isClientCache(mock(Cache.class))).isFalse(); + assertThat(CacheUtils.isClientCache(mock(GemFireCache.class))).isFalse(); + assertThat(CacheUtils.isClientCache(mock(RegionService.class))).isFalse(); + } + + @Test + public void isClientCacheWithNull() { + assertThat(CacheUtils.isClientCache(null)).isFalse(); + } + + @Test + public void isClientRegionWithClientRegionInClientCache() { + + Region mockRegion = mock(Region.class); + + RegionService mockRegionService = mock(ClientCache.class); + + doReturn(mockRegionService).when(mockRegion).getRegionService(); + + assertThat(CacheUtils.isClientRegion(mockRegion)).isTrue(); + assertThat(CacheUtils.isPeerRegion(mockRegion)).isFalse(); + + verify(mockRegion, times(2)).getRegionService(); + verifyNoMoreInteractions(mockRegion); + verifyNoInteractions(mockRegionService); + } + + @Test + public void isClientRegionWithClientRegionDeterminedByPoolName() { + + Region mockRegion = mock(Region.class); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + RegionService mockRegionService = mock(RegionService.class); + + doReturn(mockRegionAttributes).when(mockRegion).getAttributes(); + doReturn(mockRegionService).when(mockRegion).getRegionService(); + doReturn("TestPool").when(mockRegionAttributes).getPoolName(); + + assertThat(CacheUtils.isClientRegion(mockRegion)).isTrue(); + assertThat(CacheUtils.isPeerRegion(mockRegion)).isFalse(); + + verify(mockRegion, times(2)).getRegionService(); + verify(mockRegion, times(2)).getAttributes(); + verify(mockRegionAttributes, times(2)).getPoolName(); + verifyNoMoreInteractions(mockRegion, mockRegionAttributes); + verifyNoInteractions(mockRegionService); + } + + @Test + public void isClientRegionWithRegionHavingNoPoolName() { + + Region mockRegion = mock(Region.class); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + doReturn(mockRegionAttributes).when(mockRegion).getAttributes(); + doReturn(null).when(mockRegion).getRegionService(); + doReturn(" ").when(mockRegionAttributes).getPoolName(); + + assertThat(CacheUtils.isClientRegion(mockRegion)).isFalse(); + assertThat(CacheUtils.isPeerRegion(mockRegion)).isTrue(); + + verify(mockRegion, times(2)).getRegionService(); + verify(mockRegion, times(2)).getAttributes(); + verify(mockRegionAttributes, times(2)).getPoolName(); + verifyNoMoreInteractions(mockRegion, mockRegionAttributes); + } + + @Test + public void isClientRegionWithRegionHavingNoRegionAttributes() { + + Region mockRegion = mock(Region.class); + + doReturn(null).when(mockRegion).getAttributes(); + doReturn(null).when(mockRegion).getRegionService(); + + assertThat(CacheUtils.isClientRegion(mockRegion)).isFalse(); + assertThat(CacheUtils.isPeerRegion(mockRegion)).isTrue(); + + verify(mockRegion, times(2)).getRegionService(); + verify(mockRegion, times(2)).getAttributes(); + verifyNoMoreInteractions(mockRegion); + } + + @Test + public void isClientRegionWithPeerRegionInPeerCache() { + + Region mockRegion = mock(Region.class); + + RegionService mockRegionService = mock(Cache.class); + + doReturn(mockRegionService).when(mockRegion).getRegionService(); + + assertThat(CacheUtils.isClientRegion(mockRegion)).isFalse(); + assertThat(CacheUtils.isPeerRegion(mockRegion)).isTrue(); + + verify(mockRegion, times(2)).getRegionService(); + verify(mockRegion, times(2)).getAttributes(); + verifyNoMoreInteractions(mockRegion); + } + + @Test + public void isClientRegionWithNull() { + assertThat(CacheUtils.isClientRegion(null)).isFalse(); + } + + @Test + public void isPeerCacheWithPeerCache() { + assertThat(CacheUtils.isPeerCache(mock(Cache.class))).isTrue(); + } + + @Test + public void isPeerCacheWithGemFireCacheImplWhenIsClientReturnsTrue() { + + GemFireCacheImpl mockCache = mock(GemFireCacheImpl.class); + + doReturn(true).when(mockCache).isClient(); + + assertThat(CacheUtils.isPeerCache(mockCache)).isFalse(); + + verify(mockCache, times(1)).isClient(); + verifyNoMoreInteractions(mockCache); + } + + @Test + public void isPeerCacheWithGemFireCacheImplWhenIsClientReturnsFalse() { + + GemFireCacheImpl mockCache = mock(GemFireCacheImpl.class); + + doReturn(false).when(mockCache).isClient(); + + assertThat(CacheUtils.isPeerCache(mockCache)).isTrue(); + + verify(mockCache, times(1)).isClient(); + verifyNoMoreInteractions(mockCache); + } + + @Test + public void isPeerCacheWithNonPeerCache() { + assertThat(CacheUtils.isPeerCache(mock(ClientCache.class))).isFalse(); + assertThat(CacheUtils.isPeerCache(mock(GemFireCache.class))).isFalse(); + assertThat(CacheUtils.isPeerCache(mock(RegionService.class))).isFalse(); + } + + @Test + public void isPeerCacheWithNull() { + assertThat(CacheUtils.isPeerCache(null)).isFalse(); + } + + @Test + public void isPeerRegionWithNull() { + assertThat(CacheUtils.isPeerRegion(null)).isFalse(); + } +}