From 1ffc427c2d0bfe51871d6bb12e8d8552b3950647 Mon Sep 17 00:00:00 2001 From: John Blum Date: Thu, 5 Dec 2019 15:08:52 -0800 Subject: [PATCH] DATAGEODE-281 - Introduce a PoolResolver strategy interface to resolve Pool instances and provide a layer of indirection between SDG and Apache Geode's static PoolManager class. --- .../data/gemfire/client/PoolResolver.java | 79 +++++++++ .../support/PoolManagerPoolResolver.java | 65 +++++++ .../gemfire/client/PoolResolverUnitTests.java | 130 ++++++++++++++ ...olManagerPoolResolverIntegrationTests.java | 167 ++++++++++++++++++ 4 files changed, 441 insertions(+) create mode 100644 spring-data-geode/src/main/java/org/springframework/data/gemfire/client/PoolResolver.java create mode 100644 spring-data-geode/src/main/java/org/springframework/data/gemfire/client/support/PoolManagerPoolResolver.java create mode 100644 spring-data-geode/src/test/java/org/springframework/data/gemfire/client/PoolResolverUnitTests.java create mode 100644 spring-data-geode/src/test/java/org/springframework/data/gemfire/client/support/PoolManagerPoolResolverIntegrationTests.java diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/client/PoolResolver.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/client/PoolResolver.java new file mode 100644 index 00000000..797038b8 --- /dev/null +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/client/PoolResolver.java @@ -0,0 +1,79 @@ +/* + * Copyright 2019 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.client; + +import java.util.Optional; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionAttributes; +import org.apache.geode.cache.client.Pool; + +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + +/** + * {@link PoolResolver} is a strategy interface for resolving references to Apache Geode {@link Pool} instances. + * + * This is used throughout SDG's codebase to separate SDG's {@link Pool} resolution logic from being explicitly tied to + * to Apache Geode's static {@link org.apache.geode.cache.client.PoolManager} class. This interfaces also serves + * as an SPI for different strategies when resolving a {@link Pool}. + * + * @author John Blum + * @see java.lang.FunctionalInterface + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.client.Pool + * @since 2.3.0 + */ +@FunctionalInterface +public interface PoolResolver { + + /** + * Resolves the {@link Pool} instance used by the given {@link Region}. + * + * If the {@link Region} is a {@literal client} {@link Region} but does not explicitly configure + * a specific {@link Pool} reference, then the {@literal DEFAULT} {@link Pool} is returned. + * + * If the {@link Region} is {@literal local} or a {@literal peer} {@link Region}, then {@literal null} + * is returned. + * + * @param region {@link Region} from which to resolve the associated {@link Pool}. + * @return the {@link Pool} instance associated with the given {@link Region}, + * or the {@literal DEFAULT} {@link Pool} if the {@link Region} is a {@literal client} {@link Region}, + * or {@literal null} if the {@link Region} is not a {@literal client} {@link Region}. + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.client.Pool + */ + default @Nullable Pool resolve(@Nullable Region region) { + + return Optional.ofNullable(region) + .map(Region::getAttributes) + .map(RegionAttributes::getPoolName) + .filter(StringUtils::hasText) + .map(this::resolve) + .orElse(null); + } + + /** + * Resolves a {@link Pool} with the given {@link String name}. + * + * @param poolName {@link String name} of the {@link Pool} to resolve. + * @return the {@link Pool} with the given {@link String name} or {@link null} if no {@link Pool} exists with + * the {@link String name}. + * @see org.apache.geode.cache.client.Pool + */ + @Nullable Pool resolve(@Nullable String poolName); + +} diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/client/support/PoolManagerPoolResolver.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/client/support/PoolManagerPoolResolver.java new file mode 100644 index 00000000..4693ddb2 --- /dev/null +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/client/support/PoolManagerPoolResolver.java @@ -0,0 +1,65 @@ +/* + * Copyright 2019 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.client.support; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.client.Pool; +import org.apache.geode.cache.client.PoolManager; + +import org.springframework.data.gemfire.client.PoolResolver; +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + +/** + * {@link PoolManagerPoolResolver} is an implementation of {@link PoolResolver} that delegates all {@link Pool} + * resolution logic to the Apache Geode {@link PoolManager}. + * + * @author John Blum + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.client.Pool + * @see org.apache.geode.cache.client.PoolManager + * @see org.springframework.data.gemfire.client.PoolResolver + * @since 2.3.0 + */ +public class PoolManagerPoolResolver implements PoolResolver { + + /** + * Resolves the {@link Pool} used by the given {@link Region} by delegating to {@link PoolManager#find(Region)}. + * + * @param region {@link Region} from which to resolve the associated {@link Pool}. + * @return the {@link Pool} used by the given {@link Region}. + * @see org.apache.geode.cache.client.PoolManager#find(Region) + * @see org.apache.geode.cache.client.Pool + */ + @Override + public @Nullable Pool resolve(@Nullable Region region) { + return region != null ? PoolManager.find(region) : null; + } + + /** + * Resolves the {@link Pool} with the given {@link String name} by delegating to {@link PoolManager#find(String)}. + * + * @param poolName {@link String name} of the {@link Pool} to resolve. + * @return the {@link Pool} with the given {@link String name} or {@link null} if no {@link Pool} exists with + * the {@link String name}. + * @see org.apache.geode.cache.client.PoolManager#find(String) + * @see org.apache.geode.cache.client.Pool + */ + @Override + public @Nullable Pool resolve(@Nullable String poolName) { + return StringUtils.hasText(poolName) ? PoolManager.find(poolName) : null; + } +} diff --git a/spring-data-geode/src/test/java/org/springframework/data/gemfire/client/PoolResolverUnitTests.java b/spring-data-geode/src/test/java/org/springframework/data/gemfire/client/PoolResolverUnitTests.java new file mode 100644 index 00000000..61a78bd5 --- /dev/null +++ b/spring-data-geode/src/test/java/org/springframework/data/gemfire/client/PoolResolverUnitTests.java @@ -0,0 +1,130 @@ +/* + * Copyright 2019 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.client; + +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.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; +import static org.mockito.Mockito.when; + +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.Region; +import org.apache.geode.cache.RegionAttributes; +import org.apache.geode.cache.client.Pool; + +/** + * Unit Tests for {@link PoolResolver}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.junit.MockitoJUnitRunner + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.client.Pool + * @since 2.3.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class PoolResolverUnitTests { + + @Mock + private TestPoolResolver testPoolResolver; + + @Before + public void setup() { + when(this.testPoolResolver.resolve(any(Region.class))).thenCallRealMethod(); + } + + @Test + public void resolvePoolFromRegionWithPoolReturnsPool() { + + Pool mockPool = mock(Pool.class); + + Region mockRegion = mock(Region.class); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes); + when(mockRegionAttributes.getPoolName()).thenReturn("TestPool"); + when(this.testPoolResolver.resolve(eq("TestPool"))).thenReturn(mockPool); + + assertThat(this.testPoolResolver.resolve(mockRegion)).isEqualTo(mockPool); + + verifyNoInteractions(mockPool); + verify(mockRegion, times(1)).getAttributes(); + verify(mockRegionAttributes, times(1)).getPoolName(); + verify(this.testPoolResolver, times(1)).resolve(eq("TestPool")); + } + + private void testResolvePoolFromRegionWithNoPoolReturnsNull(String poolName) { + + Region mockRegion = mock(Region.class); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes); + when(mockRegionAttributes.getPoolName()).thenReturn(poolName); + + assertThat(this.testPoolResolver.resolve(mockRegion)).isNull(); + + verify(mockRegion, times(1)).getAttributes(); + verify(mockRegionAttributes, times(1)).getPoolName(); + verify(this.testPoolResolver, never()).resolve(eq(poolName)); + } + + @Test + public void resolvePoolWithRegionWithBlankPoolNameReturnsNull() { + testResolvePoolFromRegionWithNoPoolReturnsNull(" "); + } + + @Test + public void resolvePoolWithRegionWithEmptyPoolNameReturnsNull() { + testResolvePoolFromRegionWithNoPoolReturnsNull(""); + } + + @Test + public void resolvePoolWithRegionWithNullPoolNameReturnsNull() { + testResolvePoolFromRegionWithNoPoolReturnsNull(null); + } + + @Test + public void resolvePoolWithNullRegionIsNullSafeAndReturnsNull() { + assertThat(this.testPoolResolver.resolve((Region) null)).isNull(); + } + + @Test + public void resolvePoolWithRegionHavingNullRegionAttributesIsNullSafeAndReturnsNull() { + + Region mockRegion = mock(Region.class); + + assertThat(this.testPoolResolver.resolve(mockRegion)).isNull(); + + verify(mockRegion, times(1)).getAttributes(); + } + + private static abstract class TestPoolResolver implements PoolResolver { } + +} diff --git a/spring-data-geode/src/test/java/org/springframework/data/gemfire/client/support/PoolManagerPoolResolverIntegrationTests.java b/spring-data-geode/src/test/java/org/springframework/data/gemfire/client/support/PoolManagerPoolResolverIntegrationTests.java new file mode 100644 index 00000000..4ac10d3d --- /dev/null +++ b/spring-data-geode/src/test/java/org/springframework/data/gemfire/client/support/PoolManagerPoolResolverIntegrationTests.java @@ -0,0 +1,167 @@ +/* + * Copyright 2019 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.client.support; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.annotation.Resource; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.cache.client.Pool; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.DependsOn; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.client.PoolResolver; +import org.springframework.data.gemfire.config.annotation.ClientCacheApplication; +import org.springframework.data.gemfire.config.annotation.EnablePool; +import org.springframework.data.gemfire.config.annotation.EnablePools; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration Tests for {@link PoolManagerPoolResolver}. + * + * @author John Blum + * @see org.junit.Test + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.client.Pool + * @see org.apache.geode.cache.client.PoolManager + * @see org.springframework.data.gemfire.client.PoolResolver + * @see org.springframework.data.gemfire.client.support.PoolManagerPoolResolver + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 2.3.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@SuppressWarnings("unused") +public class PoolManagerPoolResolverIntegrationTests { + + @Autowired + private ClientCache clientCache; + + @Autowired + @Qualifier("CarPool") + private Pool carPool; + + private Pool defaultPool; + + @Autowired + @Qualifier("SwimmingPool") + private Pool swimmingPool; + + private PoolResolver poolResolver = new PoolManagerPoolResolver(); + + @Resource(name = "RegionWithDefaultPool") + private Region regionWithDefaultPool; + + @Resource(name = "RegionWithSwimmingPool") + private Region regionWithSwimmingPool; + + @Before + public void setup() { + + assertThat(this.clientCache).isNotNull(); + assertThat(this.clientCache.getName()).isEqualTo(PoolManagerPoolResolverIntegrationTests.class.getSimpleName()); + assertThat(this.carPool).isNotNull(); + assertThat(this.carPool.getName()).isEqualTo("CarPool"); + assertThat(this.swimmingPool).isNotNull(); + assertThat(this.swimmingPool.getName()).isEqualTo("SwimmingPool"); + + this.defaultPool = this.clientCache.getDefaultPool(); + + assertThat(this.defaultPool).isNotNull(); + assertThat(this.defaultPool).isNotSameAs(this.carPool); + assertThat(this.defaultPool).isNotSameAs(this.swimmingPool); + } + + @Test + public void resolvePoolFromName() { + + assertThat(this.poolResolver.resolve("DEFAULT")).isEqualTo(this.defaultPool); + assertThat(this.poolResolver.resolve("CarPool")).isEqualTo(this.carPool); + assertThat(this.poolResolver.resolve("SwimmingPool")).isEqualTo(this.swimmingPool); + } + + @Test + public void resolvePoolFromBlankPoolNameReturnsNull() { + assertThat(this.poolResolver.resolve(" ")).isNull(); + } + + @Test + public void resolvePoolFromEmptyPoolNameReturnsNull() { + assertThat(this.poolResolver.resolve("")).isNull(); + } + + @Test + public void resolvePoolFromNullPoolNameReturnsNull() { + assertThat(this.poolResolver.resolve((String) null)).isNull(); + } + + @Test + public void resolvePoolFromRegion() { + + assertThat(this.poolResolver.resolve(this.regionWithDefaultPool)).isEqualTo(this.defaultPool); + assertThat(this.poolResolver.resolve(this.regionWithSwimmingPool)).isEqualTo(this.swimmingPool); + } + + @Test + public void resolvePoolFromNullRegionIsNullSafeAndReturnsNull() { + assertThat(this.poolResolver.resolve((Region) null)).isNull(); + } + + @ClientCacheApplication(name = "PoolManagerPoolResolverIntegrationTests") + @EnablePools(pools = { + @EnablePool(name = "DEFAULT", servers = @EnablePool.Server), + @EnablePool(name = "CarPool", locators = @EnablePool.Locator), + @EnablePool(name = "SwimmingPool", locators = @EnablePool.Locator) + }) + static class TestConfiguration { + + @Bean("RegionWithDefaultPool") + ClientRegionFactoryBean regionWithDefaultPool(ClientCache clientCache) { + + ClientRegionFactoryBean clientRegion = new ClientRegionFactoryBean<>(); + + clientRegion.setCache(clientCache); + clientRegion.setShortcut(ClientRegionShortcut.PROXY); + + return clientRegion; + } + + @Bean("RegionWithSwimmingPool") + @DependsOn("SwimmingPool") + ClientRegionFactoryBean regionWithNamedPool(ClientCache clientCache) { + + ClientRegionFactoryBean clientRegion = new ClientRegionFactoryBean<>(); + + clientRegion.setCache(clientCache); + clientRegion.setPoolName("SwimmingPool"); + clientRegion.setShortcut(ClientRegionShortcut.PROXY); + + return clientRegion; + } + } +}