From 19238e0d2b1db1282e5fa52fc3c03e7b70a46394 Mon Sep 17 00:00:00 2001 From: John Blum Date: Thu, 2 Apr 2020 00:32:30 -0700 Subject: [PATCH] DATAGEODE-244 - Add RegionResolver implementation to resolve a Region by name from a RegionService or CacheResolver. --- .../support/RegionServiceRegionResolver.java | 141 ++++++++++ .../RegionServiceRegionResolverUnitTests.java | 261 ++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 spring-data-geode/src/main/java/org/springframework/data/gemfire/support/RegionServiceRegionResolver.java create mode 100644 spring-data-geode/src/test/java/org/springframework/data/gemfire/support/RegionServiceRegionResolverUnitTests.java diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/support/RegionServiceRegionResolver.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/support/RegionServiceRegionResolver.java new file mode 100644 index 00000000..0b8b0c2c --- /dev/null +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/support/RegionServiceRegionResolver.java @@ -0,0 +1,141 @@ +/* + * 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.support; + +import java.util.Optional; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionService; +import org.apache.geode.cache.client.ClientCache; + +import org.springframework.data.gemfire.CacheResolver; +import org.springframework.data.gemfire.RegionResolver; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * {@link RegionResolver} implementation used to resolve a cache {@link Region} identified by {@link String name} + * using the configured {@link RegionService}. + * + * @author John Blum + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.RegionService + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.data.gemfire.CacheResolver + * @see org.springframework.data.gemfire.RegionResolver + * @see org.springframework.data.gemfire.support.AbstractCachingRegionResolver + * @since 2.3.0 + */ +public class RegionServiceRegionResolver extends AbstractCachingRegionResolver { + + /** + * Factory method used to construct a {@link RegionServiceRegionResolver} from a {@link CacheResolver}. + * + * The {@link CacheResolver} will resolve an instance of {@link GemFireCache}, such as a {@literal peer} + * {@link Cache} or a {@link ClientCache}, which is a {@link RegionService} capable of resolving a {@link Region} + * identified by {@link String name}. + * + * @param {@link Class subclass} of {@link GemFireCache}. + * @param cacheResolver {@link CacheResolver} used to resolve the {@link RegionService}. + * @return a new instance of {@link RegionServiceRegionResolver}. + * @throws IllegalArgumentException if {@link CacheResolver} is {@literal null}. + * @see #RegionServiceRegionResolver(RegionServiceResolver) + * @see org.springframework.data.gemfire.CacheResolver + */ + @NonNull + public static RegionServiceRegionResolver from(@NonNull CacheResolver cacheResolver) { + + Assert.notNull(cacheResolver, "CacheResolver must not be null"); + + return new RegionServiceRegionResolver<>(() -> Optional.ofNullable(cacheResolver.resolve())); + } + + /** + * Factory method used to construct a {@link RegionServiceRegionResolver} initialized with + * the given {@link RegionService}. + * + * The {@link RegionService} may be an instance of {@link GemFireCache}, such as a {@literal peer} {@link Cache} + * or {@link ClientCache}. + * + * @param regionService {@link RegionService} used to resolve cache {@link Region Regions} + * identified by {@link String name}; may be {@literal null}. + * @return a new instance of {@link RegionServiceRegionResolver}. + * @see #RegionServiceRegionResolver(RegionServiceResolver) + * @see org.apache.geode.cache.RegionService + */ + @NonNull + public static RegionServiceRegionResolver from(@Nullable RegionService regionService) { + return new RegionServiceRegionResolver<>(() -> Optional.ofNullable(regionService)); + } + + private final RegionServiceResolver resolver; + + /** + * Constructs a new instance of {@link RegionServiceRegionResolver} initialized with + * the given {@link RegionServiceResolver}. + * + * @param resolver {@link RegionServiceResolver} used to resolve the {@link RegionService} that is used to resolve + * cache {@link Region Regions} by {@link String name}. + * @throws IllegalArgumentException if {@link RegionServiceResolver} is {@literal null}. + * @see RegionServiceResolver + */ + public RegionServiceRegionResolver(RegionServiceResolver resolver) { + + Assert.notNull(resolver, "RegionServiceResolver must not be null"); + + this.resolver = resolver; + } + + /** + * Returns the configured {@link RegionServiceResolver} used to resolve the {@link RegionService} that is then used + * to resolve cache {@link Region Regions} by {@link String name}. + * + * @return the configured {@link RegionServiceResolver}. + * @see RegionServiceResolver + */ + protected RegionServiceResolver getRegionServiceResolver() { + return this.resolver; + } + + /** + * Resolves a cache {@link Region} identified by the given {@link String name} using the configured + * {@link RegionService} resolved from the {@link RegionServiceResolver}. + * + * @param {@link Class type} of the {@link Region} key. + * @param {@link Class type} of the {@link Region} value. + * @param regionName {@link String name} of the {@link Region} to resolve. + * @return the resolved cache {@link Region} identified by the given {@link String name}; may be {@literal null}. + * @see org.apache.geode.cache.RegionService#getRegion(String) + * @see #getRegionServiceResolver() + */ + @Nullable @Override + protected Region doResolve(@Nullable String regionName) { + + return getRegionServiceResolver().resolve() + .>map(regionService -> regionService.getRegion(regionName)) + .orElse(null); + } + + @FunctionalInterface + protected interface RegionServiceResolver { + Optional resolve(); + } +} diff --git a/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/RegionServiceRegionResolverUnitTests.java b/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/RegionServiceRegionResolverUnitTests.java new file mode 100644 index 00000000..52ddfdc8 --- /dev/null +++ b/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/RegionServiceRegionResolverUnitTests.java @@ -0,0 +1,261 @@ +/* + * 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.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.support.RegionServiceRegionResolver.RegionServiceResolver; + +import java.util.Optional; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.apache.geode.cache.AttributesMutator; +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionService; +import org.apache.geode.cache.client.ClientCache; + +import org.springframework.data.gemfire.CacheResolver; + +/** + * Unit Tests for {@link RegionServiceRegionResolver}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.mockito.junit.MockitoJUnitRunner + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.RegionService + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.data.gemfire.CacheResolver + * @see org.springframework.data.gemfire.support.RegionServiceRegionResolver + * @see org.springframework.data.gemfire.support.RegionServiceRegionResolver.RegionServiceResolver + * @since 2.3.0 + */ +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class RegionServiceRegionResolverUnitTests { + + @Mock + private Region mockRegion; + + @Before + public void setupMockRegion() { + + AttributesMutator mockAttributesMutator = mock(AttributesMutator.class); + + when(this.mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator); + when(mockAttributesMutator.getRegion()).thenReturn(this.mockRegion); + } + + @Test + public void constructRegionServiceRegionResolverWithNonNullRegionServiceResolver() { + + RegionServiceResolver mockRegionServiceResolver = mock(RegionServiceResolver.class); + + RegionServiceRegionResolver regionResolver = new RegionServiceRegionResolver(mockRegionServiceResolver); + + assertThat(regionResolver).isNotNull(); + assertThat(regionResolver.getRegionServiceResolver()).isEqualTo(mockRegionServiceResolver); + + verifyNoInteractions(mockRegionServiceResolver); + } + + @Test(expected = IllegalArgumentException.class) + public void constructRegionServiceRegionResolverWithNull() { + + try { + new RegionServiceRegionResolver<>(null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("RegionServiceResolver must not be null"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void fromNonNullCacheResolverResolvingClientCache() { + + ClientCache mockClientCache = mock(ClientCache.class); + + when(mockClientCache.getRegion(anyString())).thenReturn(this.mockRegion); + + CacheResolver mockClientCacheResolver = mock(CacheResolver.class); + + when(mockClientCacheResolver.resolve()).thenReturn(mockClientCache); + + RegionServiceRegionResolver regionResolver = RegionServiceRegionResolver.from(mockClientCacheResolver); + + assertThat(regionResolver).isNotNull(); + assertThat(regionResolver.getRegionServiceResolver()).isNotNull(); + assertThat(regionResolver.doResolve("TestRegion")).isEqualTo(this.mockRegion); + + verify(mockClientCache, times(1)).getRegion(eq("TestRegion")); + verify(mockClientCacheResolver, times(1)).resolve(); + verifyNoInteractions(this.mockRegion); + } + + @Test + public void fromNonNullCacheResolveResolvingNullCacheResolvesNullRegion() { + + CacheResolver mockCacheResolver = mock(CacheResolver.class); + + when(mockCacheResolver.resolve()).thenReturn(null); + + RegionServiceRegionResolver regionResolver = RegionServiceRegionResolver.from(mockCacheResolver); + + assertThat(regionResolver).isNotNull(); + assertThat(regionResolver.getRegionServiceResolver()).isNotNull(); + assertThat(regionResolver.doResolve("TestRegion")).isNull(); + + verify(mockCacheResolver, times(1)).resolve(); + } + + @Test + public void fromNonNullCacheResolverResolvingPeerCache() { + + CacheResolver mockPeerCacheResolver = mock(CacheResolver.class); + + RegionServiceRegionResolver regionResolver = RegionServiceRegionResolver.from(mockPeerCacheResolver); + + assertThat(regionResolver).isNotNull(); + assertThat(regionResolver.getRegionServiceResolver()).isNotNull(); + + verify(mockPeerCacheResolver, never()).resolve(); + } + + @Test(expected = IllegalArgumentException.class) + public void fromNullCacheResolverThrowsIllegalArgumentException() { + + try { + RegionServiceRegionResolver.from((CacheResolver) null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("CacheResolver must not be null"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void fromNonNullRegionService() { + + RegionService mockRegionService = mock(RegionService.class); + + when(mockRegionService.getRegion(anyString())).thenReturn(this.mockRegion); + + RegionServiceRegionResolver regionResolver = RegionServiceRegionResolver.from(mockRegionService); + + assertThat(regionResolver).isNotNull(); + assertThat(regionResolver.getRegionServiceResolver()).isNotNull(); + assertThat(regionResolver.getRegionServiceResolver().resolve().orElse(null)).isEqualTo(mockRegionService); + assertThat(regionResolver.doResolve("TestRegion")).isEqualTo(this.mockRegion); + + verify(mockRegionService, times(1)).getRegion(eq("TestRegion")); + verifyNoInteractions(this.mockRegion); + } + + @Test + public void fromNullRegionServiceIsNullSafe() { + + RegionServiceRegionResolver regionResolver = RegionServiceRegionResolver.from((RegionService) null); + + assertThat(regionResolver).isNotNull(); + assertThat(regionResolver.getRegionServiceResolver()).isNotNull(); + assertThat(regionResolver.getRegionServiceResolver().resolve().orElse(null)).isNull(); + assertThat(regionResolver.doResolve("TestRegion")).isNull(); + } + + @Test + public void resolveCachesAndReturnsRegion() { + + RegionService mockRegionService = mock(RegionService.class); + + RegionServiceResolver mockRegionServiceResolver = mock(RegionServiceResolver.class); + + when(mockRegionService.getRegion(anyString())).thenReturn(this.mockRegion); + when(mockRegionServiceResolver.resolve()).thenReturn(Optional.of(mockRegionService)); + + RegionServiceRegionResolver regionResolver = spy(new RegionServiceRegionResolver(mockRegionServiceResolver)); + + assertThat(regionResolver.getRegionServiceResolver()).isEqualTo(mockRegionServiceResolver); + + assertThat(regionResolver.resolve("MockRegion")).isEqualTo(this.mockRegion); + assertThat(regionResolver.resolve("MockRegion")).isEqualTo(this.mockRegion); + + verify(mockRegionServiceResolver, times(1)).resolve(); + verify(mockRegionService, times(1)).getRegion("MockRegion"); + verify(regionResolver, times(1)).doResolve(eq("MockRegion")); + } + + @Test + public void resolveReturnsNullWhenRegionServiceReturnsNull() { + + RegionService mockRegionService = mock(RegionService.class); + + RegionServiceResolver mockRegionServiceResolver = mock(RegionServiceResolver.class); + + when(mockRegionService.getRegion(anyString())).thenReturn(null); + when(mockRegionServiceResolver.resolve()).thenReturn(Optional.of(mockRegionService)); + + RegionServiceRegionResolver regionResolver = spy(new RegionServiceRegionResolver(mockRegionServiceResolver)); + + assertThat(regionResolver.getRegionServiceResolver()).isEqualTo(mockRegionServiceResolver); + + assertThat(regionResolver.resolve("ProxyRegion")).isNull(); + assertThat(regionResolver.resolve("ProxyRegion")).isNull(); + + verify(mockRegionServiceResolver, times(2)).resolve(); + verify(mockRegionService, times(2)).getRegion("ProxyRegion"); + verify(regionResolver, times(2)).doResolve(eq("ProxyRegion")); + } + + @Test + public void resolveReturnsNullWhenRegionServiceResolvesToNull() { + + RegionServiceResolver mockRegionServiceResolver = mock(RegionServiceResolver.class); + + RegionServiceRegionResolver regionResolver = spy(new RegionServiceRegionResolver(mockRegionServiceResolver)); + + assertThat(regionResolver.getRegionServiceResolver()).isEqualTo(mockRegionServiceResolver); + assertThat(regionResolver.resolve("TestRegion")).isNull(); + assertThat(regionResolver.resolve("TestRegion")).isNull(); + + verify(mockRegionServiceResolver, times(2)).resolve(); + verify(regionResolver, times(2)).doResolve(eq("TestRegion")); + } +}