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 index 289e42b8..6b2deee8 100644 --- 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 @@ -24,10 +24,12 @@ import java.util.Optional; import java.util.Set; import org.apache.geode.cache.Cache; +import org.apache.geode.cache.DataPolicy; 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.cache.client.Pool; import org.apache.geode.internal.cache.GemFireCacheImpl; /** @@ -36,9 +38,12 @@ import org.apache.geode.internal.cache.GemFireCacheImpl; * * @author John Blum * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.DataPolicy * @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 + * @see org.apache.geode.cache.client.Pool * @since 1.3.0 */ public abstract class CacheUtils { @@ -63,25 +68,72 @@ public abstract class CacheUtils { return isClientRegion(region) ? clientRegionValues(region) - : region.values(); - + : localRegionValues(region); } + /** + * Collects values from the given {@literal client} {@link Region}. + * + * @param {@link Class type} of the {@link Region Region's} values. + * @param region {@link Region} from which to collect values. + * @return a {@link Collection} of the {@literal client} {@link Region Region's} values. + * @see org.apache.geode.cache.Region + * @see #clientRegionValuesFromServer(Region) + * @see #localRegionValues(Region) + * @see #isProxyRegion(Region) + */ private static Collection clientRegionValues(Region region) { + return isProxyRegion(region) + ? clientRegionValuesFromServer(region) + : localRegionValues(region); + } + + /** + * Collections all values from the {@literal client} {@link Region} by pulling the values down from the server. + * + * @param {@link Class type} of the {@link Region Region's} values. + * @param region {@link Region} from which to collect values. + * @return a {@link Collection} containing the values from the {@literal client} {@link Region} on the server. + * @see org.apache.geode.cache.Region#keySetOnServer() + * @see #getAll(Region, Set) + */ + private static Collection clientRegionValuesFromServer(Region region) { + Set keys = nullSafeSet(region.keySetOnServer()); - return !keys.isEmpty() ? getAll(keys, region) : Collections.emptySet(); + return !keys.isEmpty() ? getAll(region, keys) : Collections.emptySet(); } - private static Collection getAll(Set keys, Region region) { + /** + * Gets all values from the given {@link Region} mapped to the specified {@link Set keys}. + * + * @param {@link Class type} of the {@link Region Region's} values. + * @param region {@link Region} from which to get all values. + * @param keys {@link Set} of keys targeting the values to retrieve. + * @return a {@link Collection} of the {@link Region Region's} values. + * @see org.apache.geode.cache.Region#getAll(Collection) + */ + private static Collection getAll(Region region, Set keys) { return nullSafeMap(region.getAll(keys)).values(); // Fallback procedure if region.getAll(keys) is buggered //return keys.stream().map(region::get).collect(Collectors.toSet()); } + /** + * Collects values from the given {@link Region}, locally. + * + * @param {@link Class type} of the {@link Region Region's} values. + * @param region {@link Region} from which to collect values. + * @return a {@link Collection} of the {@link Region Region's} values. + * @see org.apache.geode.cache.Region#values() + */ + private static Collection localRegionValues(Region region) { + return region.values(); + } + private static boolean hasText(String value) { - return !(value == null || value.trim().isEmpty()); + return value != null && !value.trim().isEmpty(); } private static Map nullSafeMap(Map map) { @@ -125,11 +177,8 @@ public abstract class CacheUtils { */ public static boolean isClientRegion(Region region) { - return region != null && (isClientCache(region.getRegionService()) - || Optional.ofNullable(region.getAttributes()) - .map(RegionAttributes::getPoolName) - .filter(CacheUtils::hasText) - .isPresent()); + return region != null + && (isClientCache(region.getRegionService()) || isRegionWithPool(region)); } /** @@ -168,4 +217,43 @@ public abstract class CacheUtils { public static boolean isPeerRegion(Region region) { return region != null && !isClientRegion(region); } + + /** + * Null-safe method to determine whether the given {@link Region} is a [client] {@literal PROXY} {@link Region}. + * + * The {@link Region} is a {@literal PROXY} if the {@link DataPolicy} is {@link DataPolicy#EMPTY} + * or the {@link Region} has a configured {@link Pool}. + * + * @param region {@link Region} to evaluate. + * @return a boolean value to determine whether the {@link Region} is a [client] {@literal PROXY} {@link Region}. + * @see org.apache.geode.cache.Region + * @see #isRegionWithPool(Region) + */ + public static boolean isProxyRegion(Region region) { + + return region != null + && region.getAttributes() != null + && (DataPolicy.EMPTY.equals(region.getAttributes().getDataPolicy()) + || isRegionWithPool(region)); + } + + /** + * Null-safe method to determine whether the given {@link Region} was configured with a {@link Pool}. + * + * A {@link Region} configured with a {@link Pool} (by {@link String name} specified in the {@link RegionAttributes}) + * is a strong indicator that the {@link Region} is a {@literal client} {@link Region}. + * + * @param region {@link Region} to evaluate. + * @return a boolean to determine whether the given {@link Region} was configured with a {@link Pool}. + * @see org.apache.geode.cache.client.Pool + * @see org.apache.geode.cache.Region + */ + public static boolean isRegionWithPool(Region region) { + + return Optional.ofNullable(region) + .map(Region::getAttributes) + .map(RegionAttributes::getPoolName) + .filter(CacheUtils::hasText) + .isPresent(); + } } 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 index afc488a0..9054f7fc 100644 --- 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 @@ -16,9 +16,11 @@ package org.springframework.geode.util; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -34,6 +36,7 @@ import java.util.TreeSet; import org.junit.Test; import org.apache.geode.cache.Cache; +import org.apache.geode.cache.DataPolicy; import org.apache.geode.cache.GemFireCache; import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionAttributes; @@ -63,6 +66,8 @@ public class CacheUtilsUnitTests { Region mockRegion = mock(Region.class); + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + RegionService mockRegionService = mock(ClientCache.class); Set keySetOnServer = new TreeSet<>(Arrays.asList(1, 2, 3)); @@ -73,17 +78,23 @@ public class CacheUtilsUnitTests { keysValues.put(2, "two"); keysValues.put(3, "three"); + doReturn(mockRegionAttributes).when(mockRegion).getAttributes(); doReturn(mockRegionService).when(mockRegion).getRegionService(); doReturn(keySetOnServer).when(mockRegion).keySetOnServer(); doReturn(keysValues).when(mockRegion).getAll(eq(keySetOnServer)); + doReturn(DataPolicy.EMPTY).when(mockRegionAttributes).getDataPolicy(); assertThat(CacheUtils.collectValues(mockRegion)).containsExactlyInAnyOrder(keysValues.values().toArray()); + verify(mockRegion, times(2)).getAttributes(); verify(mockRegion, times(1)).getRegionService(); verify(mockRegion, times(1)).keySetOnServer(); verify(mockRegion, times(1)).getAll(eq(keySetOnServer)); - verifyNoMoreInteractions(mockRegion); + verify(mockRegion, never()).values(); + verify(mockRegionAttributes, times(1)).getDataPolicy(); + verify(mockRegionAttributes, never()).getPoolName(); verifyNoInteractions(mockRegionService); + verifyNoMoreInteractions(mockRegion); } @Test @@ -91,24 +102,33 @@ public class CacheUtilsUnitTests { Region mockRegion = mock(Region.class); + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + RegionService mockRegionService = mock(ClientCache.class); Set keySetOnServer = new TreeSet<>(Arrays.asList(1, 2)); + doReturn(mockRegionAttributes).when(mockRegion).getAttributes(); doReturn(mockRegionService).when(mockRegion).getRegionService(); doReturn(keySetOnServer).when(mockRegion).keySetOnServer(); doReturn(null).when(mockRegion).getAll(eq(keySetOnServer)); + doReturn(DataPolicy.NORMAL).when(mockRegionAttributes).getDataPolicy(); + doReturn("Car").when(mockRegionAttributes).getPoolName(); Collection values = CacheUtils.collectValues(mockRegion); assertThat(values).isNotNull(); assertThat(values).isEmpty(); + verify(mockRegion, times(3)).getAttributes(); verify(mockRegion, times(1)).getRegionService(); verify(mockRegion, times(1)).keySetOnServer(); verify(mockRegion, times(1)).getAll(eq(keySetOnServer)); - verifyNoMoreInteractions(mockRegion); + verify(mockRegion, never()).values(); + verify(mockRegionAttributes, times(1)).getDataPolicy(); + verify(mockRegionAttributes, times(1)).getPoolName(); verifyNoInteractions(mockRegionService); + verifyNoMoreInteractions(mockRegion); } @Test @@ -116,20 +136,30 @@ public class CacheUtilsUnitTests { Region mockRegion = mock(Region.class); + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + RegionService mockRegionService = mock(ClientCache.class); + doReturn(mockRegionAttributes).when(mockRegion).getAttributes(); doReturn(mockRegionService).when(mockRegion).getRegionService(); doReturn(null).when(mockRegion).keySetOnServer(); + doReturn(DataPolicy.PARTITION).when(mockRegionAttributes).getDataPolicy(); + doReturn("Dead").when(mockRegionAttributes).getPoolName(); Collection values = CacheUtils.collectValues(mockRegion); assertThat(values).isNotNull(); assertThat(values).isEmpty(); + verify(mockRegion, times(3)).getAttributes(); verify(mockRegion, times(1)).getRegionService(); verify(mockRegion, times(1)).keySetOnServer(); - verifyNoMoreInteractions(mockRegion); + verify(mockRegion, never()).getAll(any()); + verify(mockRegion, never()).values(); + verify(mockRegionAttributes, times(1)).getDataPolicy(); + verify(mockRegionAttributes, times(1)).getPoolName(); verifyNoInteractions(mockRegionService); + verifyNoMoreInteractions(mockRegion); } @Test @@ -148,6 +178,36 @@ public class CacheUtilsUnitTests { verify(mockRegion, times(1)).values(); } + @Test + public void collectValuesFromLocalClientRegion() { + + Region mockRegion = mock(Region.class); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + RegionService mockRegionService = mock(ClientCache.class); + + doReturn(mockRegionAttributes).when(mockRegion).getAttributes(); + doReturn(mockRegionService).when(mockRegion).getRegionService(); + doReturn(DataPolicy.NORMAL).when(mockRegionAttributes).getDataPolicy(); + doReturn(" ").when(mockRegionAttributes).getPoolName(); + doReturn(Arrays.asList("one", "two")).when(mockRegion).values(); + + Collection values = CacheUtils.collectValues(mockRegion); + + assertThat(values).isNotNull(); + assertThat(values).containsExactly("one", "two"); + + verify(mockRegion, times(1)).getRegionService(); + verify(mockRegion, times(3)).getAttributes(); + verify(mockRegion, times(1)).values(); + verify(mockRegion, never()).keySetOnServer(); + verify(mockRegion, never()).getAll(any()); + verify(mockRegionAttributes, times(1)).getDataPolicy(); + verify(mockRegionAttributes, times(1)).getPoolName(); + verifyNoInteractions(mockRegionService); + } + @Test public void collectValuesFromPeerRegion() { @@ -370,4 +430,135 @@ public class CacheUtilsUnitTests { public void isPeerRegionWithNull() { assertThat(CacheUtils.isPeerRegion(null)).isFalse(); } + + @Test + public void isProxyRegionWithProxyRegionBasedOnDataPolicy() { + + Region mockRegion = mock(Region.class); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + doReturn(mockRegionAttributes).when(mockRegion).getAttributes(); + doReturn(DataPolicy.EMPTY).when(mockRegionAttributes).getDataPolicy(); + + assertThat(CacheUtils.isProxyRegion(mockRegion)).isTrue(); + + verify(mockRegion, times(2)).getAttributes(); + verify(mockRegionAttributes, times(1)).getDataPolicy(); + verify(mockRegionAttributes, never()).getPoolName(); + } + + @Test + public void isProxyRegionWithProxyRegionBasedOnPoolConfiguration() { + + Region mockRegion = mock(Region.class); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + doReturn(mockRegionAttributes).when(mockRegion).getAttributes(); + doReturn(DataPolicy.NORMAL).when(mockRegionAttributes).getDataPolicy(); + doReturn("TestPool").when(mockRegionAttributes).getPoolName(); + + assertThat(CacheUtils.isProxyRegion(mockRegion)).isTrue(); + + verify(mockRegion, times(3)).getAttributes(); + verify(mockRegionAttributes, times(1)).getDataPolicy(); + verify(mockRegionAttributes, times(1)).getPoolName(); + } + + @Test + public void isProxyRegionWithNonProxyRegion() { + + Region mockRegion = mock(Region.class); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + doReturn(mockRegionAttributes).when(mockRegion).getAttributes(); + doReturn(DataPolicy.NORMAL).when(mockRegionAttributes).getDataPolicy(); + doReturn(" ").when(mockRegionAttributes).getPoolName(); + + assertThat(CacheUtils.isProxyRegion(mockRegion)).isFalse(); + + verify(mockRegion, times(3)).getAttributes(); + verify(mockRegionAttributes, times(1)).getDataPolicy(); + verify(mockRegionAttributes, times(1)).getPoolName(); + } + + @Test + public void isProxyRegionWithRegionHavingNoAttributesIsNullSafe() { + + Region mockRegion = mock(Region.class); + + assertThat(CacheUtils.isProxyRegion(mockRegion)).isFalse(); + + verify(mockRegion, times(1)).getAttributes(); + verifyNoMoreInteractions(mockRegion); + } + + @Test + public void isProxyRegionWithNullRegionIsNullSafe() { + assertThat(CacheUtils.isProxyRegion(null)).isFalse(); + } + + @Test + public void isRegionWithPoolWithPooledRegion() { + + Region mockRegion = mock(Region.class); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + doReturn(mockRegionAttributes).when(mockRegion).getAttributes(); + doReturn("Swimming").when(mockRegionAttributes).getPoolName(); + + assertThat(CacheUtils.isRegionWithPool(mockRegion)).isTrue(); + + verify(mockRegion, times(1)).getAttributes(); + verify(mockRegionAttributes, times(1)).getPoolName(); + } + + @Test + public void isRegionWithPoolUsingRegionHavingNoAttributesIsNullSafe() { + + Region mockRegion = mock(Region.class); + + assertThat(CacheUtils.isRegionWithPool(mockRegion)).isFalse(); + + verify(mockRegion, times(1)).getAttributes(); + verifyNoMoreInteractions(mockRegion); + } + + @Test + public void isRegionWithPoolUsingNullRegionIsNullSafe() { + assertThat(CacheUtils.isRegionWithPool(null)).isFalse(); + } + + private void testIsRegionWithPoolUsingRegionWithInvalidPoolNameReturnsFalse(String poolName) { + + Region mockRegion = mock(Region.class); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + doReturn(mockRegionAttributes).when(mockRegion).getAttributes(); + doReturn(poolName).when(mockRegionAttributes).getPoolName(); + + assertThat(CacheUtils.isRegionWithPool(mockRegion)).isFalse(); + + verify(mockRegion, times(1)).getAttributes(); + verify(mockRegionAttributes, times(1)).getPoolName(); + } + + @Test + public void isRegionWithPoolUsingRegionConfiguredWithBlankPoolName() { + testIsRegionWithPoolUsingRegionWithInvalidPoolNameReturnsFalse(" "); + } + + @Test + public void isRegionWithPoolUsingRegionConfiguredWithEmptyPoolName() { + testIsRegionWithPoolUsingRegionWithInvalidPoolNameReturnsFalse(""); + } + + @Test + public void isRegionWithPoolUsingRegionConfiguredWithNullPoolName() { + testIsRegionWithPoolUsingRegionWithInvalidPoolNameReturnsFalse(null); + } }