From 281834bc79e74a86dba65fa03de5a069b7272ef5 Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 2 Feb 2022 11:24:49 -0800 Subject: [PATCH] Review, refactor and polish abstract base Region FactoryBeans. --- .../ConfigurableRegionFactoryBean.java | 29 ++-- .../data/gemfire/PeerRegionFactoryBean.java | 2 - .../gemfire/ResolvableRegionFactoryBean.java | 132 +++++++++++------- .../client/ClientRegionFactoryBean.java | 10 +- .../gemfire/support/GemfireFunctions.java | 51 +++++++ .../data/gemfire/util/SpringUtils.java | 6 - .../support/GemfireFunctionsUnitTests.java | 106 ++++++++++++++ 7 files changed, 258 insertions(+), 78 deletions(-) create mode 100644 spring-data-geode/src/main/java/org/springframework/data/gemfire/support/GemfireFunctions.java create mode 100644 spring-data-geode/src/test/java/org/springframework/data/gemfire/support/GemfireFunctionsUnitTests.java diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/ConfigurableRegionFactoryBean.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/ConfigurableRegionFactoryBean.java index ae71ff1f..0d84e82b 100644 --- a/spring-data-geode/src/main/java/org/springframework/data/gemfire/ConfigurableRegionFactoryBean.java +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/ConfigurableRegionFactoryBean.java @@ -15,10 +15,6 @@ */ package org.springframework.data.gemfire; -import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; -import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; -import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; - import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -29,6 +25,8 @@ import org.apache.geode.cache.Region; import org.springframework.beans.factory.FactoryBean; import org.springframework.data.gemfire.client.ClientRegionFactoryBean; import org.springframework.data.gemfire.config.annotation.RegionConfigurer; +import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.data.gemfire.util.CollectionUtils; /** * {@link ConfigurableRegionFactoryBean} is an abstract base class encapsulating functionality common @@ -53,17 +51,28 @@ public abstract class ConfigurableRegionFactoryBean extends ResolvableRegi @Override public void configure(String beanName, ClientRegionFactoryBean bean) { - nullSafeCollection(regionConfigurers) + CollectionUtils.nullSafeCollection(regionConfigurers) .forEach(regionConfigurer -> regionConfigurer.configure(beanName, bean)); } @Override public void configure(String beanName, PeerRegionFactoryBean bean) { - nullSafeCollection(regionConfigurers) + CollectionUtils.nullSafeCollection(regionConfigurers) .forEach(regionConfigurer -> regionConfigurer.configure(beanName, bean)); } }; + /** + * Applies all {@link RegionConfigurer RegionConfigurers}. + */ + @Override + public void afterPropertiesSet() throws Exception { + + applyRegionConfigurers(requireRegionName()); + + super.afterPropertiesSet(); + } + /** * Returns a reference to the Composite {@link RegionConfigurer} used to apply additional configuration * to this {@link ClientRegionFactoryBean} on Spring container initialization. @@ -85,7 +94,7 @@ public abstract class ConfigurableRegionFactoryBean extends ResolvableRegi * @see #setRegionConfigurers(List) */ public void setRegionConfigurers(RegionConfigurer... regionConfigurers) { - setRegionConfigurers(Arrays.asList(nullSafeArray(regionConfigurers, RegionConfigurer.class))); + setRegionConfigurers(Arrays.asList(ArrayUtils.nullSafeArray(regionConfigurers, RegionConfigurer.class))); } /** @@ -128,7 +137,7 @@ public abstract class ConfigurableRegionFactoryBean extends ResolvableRegi * @see #applyRegionConfigurers(String, Iterable) */ protected void applyRegionConfigurers(String regionName, RegionConfigurer... regionConfigurers) { - applyRegionConfigurers(regionName, Arrays.asList(nullSafeArray(regionConfigurers, RegionConfigurer.class))); + applyRegionConfigurers(regionName, Arrays.asList(ArrayUtils.nullSafeArray(regionConfigurers, RegionConfigurer.class))); } /** @@ -144,11 +153,11 @@ public abstract class ConfigurableRegionFactoryBean extends ResolvableRegi protected void applyRegionConfigurers(String regionName, Iterable regionConfigurers) { if (this instanceof ClientRegionFactoryBean) { - StreamSupport.stream(nullSafeIterable(regionConfigurers).spliterator(), false) + StreamSupport.stream(CollectionUtils.nullSafeIterable(regionConfigurers).spliterator(), false) .forEach(regionConfigurer -> regionConfigurer.configure(regionName, (ClientRegionFactoryBean) this)); } else if (this instanceof PeerRegionFactoryBean) { - StreamSupport.stream(nullSafeIterable(regionConfigurers).spliterator(), false) + StreamSupport.stream(CollectionUtils.nullSafeIterable(regionConfigurers).spliterator(), false) .forEach(regionConfigurer -> regionConfigurer.configure(regionName, (PeerRegionFactoryBean) this)); } } diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/PeerRegionFactoryBean.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/PeerRegionFactoryBean.java index c2f807d1..d5938780 100644 --- a/spring-data-geode/src/main/java/org/springframework/data/gemfire/PeerRegionFactoryBean.java +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/PeerRegionFactoryBean.java @@ -166,8 +166,6 @@ public abstract class PeerRegionFactoryBean extends ConfigurableRegionFact @Override protected Region createRegion(GemFireCache gemfireCache, String regionName) { - applyRegionConfigurers(regionName); - verifyLockGrantorEligibility(getAttributes(), getScope()); Cache cache = resolveCache(gemfireCache); diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/ResolvableRegionFactoryBean.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/ResolvableRegionFactoryBean.java index 455af364..fc6f8e49 100644 --- a/spring-data-geode/src/main/java/org/springframework/data/gemfire/ResolvableRegionFactoryBean.java +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/ResolvableRegionFactoryBean.java @@ -19,6 +19,7 @@ import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newR import java.io.InputStream; import java.util.Optional; +import java.util.function.Function; import org.apache.geode.cache.GemFireCache; import org.apache.geode.cache.Region; @@ -27,16 +28,21 @@ import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.Resource; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport; +import org.springframework.data.gemfire.support.GemfireFunctions; +import org.springframework.data.gemfire.util.SpringUtils; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * Spring {@link FactoryBean} for looking up {@link Region Regions}. + * Spring {@link FactoryBean} used to look up or create {@link Region Regions}. * - * If lookups are disabled or the {@link Region} does not exist, an exception is thrown. - * - * For declaring and configuring new Regions, see {@link PeerRegionFactoryBean}. + * For declaring and configuring new {@literal client} {@link Region Regions}, see {@link ClientRegionFactoryBean}. + * For declaring and configuring new {@literal peer} {@link Region Regions}, see {@link PeerRegionFactoryBean} + * and {@link Class subclasses}. * * @author Costin Leau * @author John Blum @@ -50,6 +56,10 @@ import org.springframework.util.StringUtils; public abstract class ResolvableRegionFactoryBean extends AbstractFactoryBeanSupport> implements InitializingBean { + protected static final String CREATING_REGION_LOG_MESSAGE = "Creating Region [%1$s] in Cache [%2$s]"; + protected static final String REGION_FOUND_LOG_MESSAGE = "Found Region [%1$s] in Cache [%2$s]"; + protected static final String REGION_NOT_FOUND_ERROR_MESSAGE = "Region [%1$s] in Cache [%2$s] not found"; + private Boolean lookupEnabled = false; private GemFireCache cache; @@ -80,25 +90,19 @@ public abstract class ResolvableRegionFactoryBean extends AbstractFactoryB synchronized (cache) { - setRegion(isLookupEnabled() - ? Optional.ofNullable(getParent()) - .map(parentRegion -> parentRegion.getSubregion(regionName)) - .orElseGet(() -> cache.getRegion(regionName)) - : null); + setRegion(resolveRegion(cache, regionName)); if (getRegion() != null) { - logInfo("Found Region [%1$s] in Cache [%2$s]", regionName, cache.getName()); + logInfo(REGION_FOUND_LOG_MESSAGE, regionName, cache.getName()); } else { - logInfo("Falling back to creating Region [%1$s] in Cache [%2$s]", - regionName, cache.getName()); - + logInfo(CREATING_REGION_LOG_MESSAGE, regionName, cache.getName()); setRegion(postProcess(loadSnapshot(createRegion(cache, regionName)))); } } } - private GemFireCache requireCache() { + private @NonNull GemFireCache requireCache() { GemFireCache cache = getCache(); @@ -107,7 +111,7 @@ public abstract class ResolvableRegionFactoryBean extends AbstractFactoryB return cache; } - private String requireRegionName() { + @NonNull String requireRegionName() { String regionName = resolveRegionName(); @@ -116,15 +120,29 @@ public abstract class ResolvableRegionFactoryBean extends AbstractFactoryB return regionName; } + private @Nullable Region resolveRegion(@NonNull GemFireCache cache, @NonNull String regionName) { + + return isLookupEnabled() + ? Optional.ofNullable(getParent()) + .>map(GemfireFunctions.getSubregionFromRegion(regionName)) + .orElseGet(GemfireFunctions.getRegionFromCache(cache, regionName)) + : null; + } + /** - * Resolves the {@link String name} of the {@link Region}. + * Resolves the configured {@link String name} of the {@link Region}. * - * @return a {@link String} containing the name of the {@link Region}. + * @return a {@link String} containing the {@literal name} of the {@link Region}. * @see org.apache.geode.cache.Region#getName() */ public String resolveRegionName() { - return StringUtils.hasText(this.regionName) ? this.regionName - : (StringUtils.hasText(this.name) ? this.name : getBeanName()); + + String name = this.name; + String regionName = this.regionName; + + return StringUtils.hasText(regionName) ? regionName + : StringUtils.hasText(name) ? name + : getBeanName(); } /** @@ -142,28 +160,32 @@ public abstract class ResolvableRegionFactoryBean extends AbstractFactoryB * @see org.apache.geode.cache.Region */ protected Region createRegion(GemFireCache cache, String regionName) throws Exception { - throw new BeanInitializationException( - String.format("Region [%1$s] in Cache [%2$s] not found", regionName, cache)); + throw new BeanInitializationException(String.format(REGION_NOT_FOUND_ERROR_MESSAGE, regionName, cache)); } /** - * Loads the configured data {@link Resource snapshot} into the given {@link Region}. + * Loads data from the configured {@link Resource snapshot} into the given {@link Region}. * - * @param region {@link Region} to load. + * @param region {@link Region} to load; must not be {@literal null}. * @return the given {@link Region}. - * @throws RuntimeException if the snapshot load fails. + * @throws RuntimeException if loading the snapshot fails. * @see org.apache.geode.cache.Region#loadSnapshot(InputStream) + * @see org.apache.geode.cache.Region */ - protected Region loadSnapshot(Region region) { + protected @NonNull Region loadSnapshot(@NonNull Region region) { - Optional.ofNullable(this.snapshot).ifPresent(snapshot -> { - try { - region.loadSnapshot(snapshot.getInputStream()); - } - catch (Exception cause) { - throw newRuntimeException(cause, "Failed to load snapshot [%s]", snapshot); - } - }); + Resource snapshot = this.snapshot; + + if (snapshot != null) { + + SpringUtils.VoidReturningThrowableOperation operation = + () -> region.loadSnapshot(snapshot.getInputStream()); + + Function exceptionHandler = + cause -> newRuntimeException(cause, "Failed to load snapshot [%s]", snapshot); + + SpringUtils.safeRunOperation(operation, exceptionHandler); + } return region; } @@ -198,15 +220,17 @@ public abstract class ResolvableRegionFactoryBean extends AbstractFactoryB * @see org.springframework.beans.factory.FactoryBean#getObjectType() */ @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) public Class getObjectType() { - return Optional.ofNullable(getRegion()).map(Region::getClass).orElse((Class) Region.class); + + Region region = getRegion(); + + return region != null ? region.getClass() : Region.class; } /** * Returns a reference to the {@link GemFireCache} used to create the {@link Region}. * - * @return a reference to the {@link GemFireCache} used to create the {@link Region}.. + * @return a reference to the {@link GemFireCache} used to create the {@link Region}. * @see org.apache.geode.cache.GemFireCache */ public GemFireCache getCache() { @@ -223,18 +247,18 @@ public abstract class ResolvableRegionFactoryBean extends AbstractFactoryB this.cache = cache; } - public boolean isLookupEnabled() { - return Boolean.TRUE.equals(getLookupEnabled()); - } - - public void setLookupEnabled(Boolean lookupEnabled) { + public void setLookupEnabled(@Nullable Boolean lookupEnabled) { this.lookupEnabled = lookupEnabled; } - public Boolean getLookupEnabled() { + public @Nullable Boolean getLookupEnabled() { return this.lookupEnabled; } + public boolean isLookupEnabled() { + return Boolean.TRUE.equals(getLookupEnabled()); + } + /** * Sets the name of the cache {@link Region} based on the bean 'name' attribute. If no {@link Region} is found * with the given name, a new one will be created. If no name is given, the value of the 'beanName' property @@ -244,30 +268,30 @@ public abstract class ResolvableRegionFactoryBean extends AbstractFactoryB * @see #setBeanName(String) * @see org.apache.geode.cache.Region#getFullPath() */ - public void setName(String name) { + public void setName(@NonNull String name) { this.name = name; } /** - * Sets a reference to the parent {@link Region} to indicated this {@link FactoryBean} represents a GemFire cache - * {@link Region Sub-Region}. + * Sets a reference to the parent {@link Region} making this {@link FactoryBean} + * represent a cache {@link Region Sub-Region}. * * @param parent reference to the parent {@link Region}. * @see org.apache.geode.cache.Region */ - public void setParent(Region parent) { + public void setParent(@Nullable Region parent) { this.parent = parent; } /** - * Returns a reference to the parent {@link Region} indicating this {@link FactoryBean} represents a GemFire cache - * {@link Region Sub-Region}. + * Returns a reference to the parent {@link Region} making this {@link FactoryBean} + * represent a cache {@link Region Sub-Region}. * - * @return a reference to the parent {@link Region} or {@literal null} if this {@link Region} + * @return a reference to the parent {@link Region}, or {@literal null} if this {@link Region} * is not a {@link Region Sub-Region}. * @see org.apache.geode.cache.Region */ - protected Region getParent() { + protected @Nullable Region getParent() { return this.parent; } @@ -277,7 +301,7 @@ public abstract class ResolvableRegionFactoryBean extends AbstractFactoryB * @param region reference to the resolvable {@link Region}. * @see org.apache.geode.cache.Region */ - protected void setRegion(Region region) { + protected void setRegion(@Nullable Region region) { this.region = region; } @@ -288,7 +312,7 @@ public abstract class ResolvableRegionFactoryBean extends AbstractFactoryB * @return a reference to the {@link Region} resolved during lookup. * @see org.apache.geode.cache.Region */ - public Region getRegion() { + public @Nullable Region getRegion() { return this.region; } @@ -300,7 +324,7 @@ public abstract class ResolvableRegionFactoryBean extends AbstractFactoryB * @see #setName(String) * @see org.apache.geode.cache.Region#getName() */ - public void setRegionName(String regionName) { + public void setRegionName(@Nullable String regionName) { this.regionName = regionName; } @@ -312,7 +336,7 @@ public abstract class ResolvableRegionFactoryBean extends AbstractFactoryB * @see #setName(String) * @param snapshot the snapshot to set */ - public void setSnapshot(Resource snapshot) { + public void setSnapshot(@Nullable Resource snapshot) { this.snapshot = snapshot; } } diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java index 2ace37fc..26d63008 100644 --- a/spring-data-geode/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java @@ -162,8 +162,8 @@ public class ClientRegionFactoryBean extends ConfigurableRegionFactoryBean */ void initializePoolResolver() { - this.defaultPoolResolver = - ComposablePoolResolver.compose(new BeanFactoryPoolResolver(getBeanFactory()), new PoolManagerPoolResolver()); + this.defaultPoolResolver = ComposablePoolResolver + .compose(new BeanFactoryPoolResolver(getBeanFactory()), new PoolManagerPoolResolver()); this.poolResolver = this.poolResolver != null ? this.poolResolver : this.defaultPoolResolver; } @@ -182,8 +182,6 @@ public class ClientRegionFactoryBean extends ConfigurableRegionFactoryBean @Override protected Region createRegion(GemFireCache gemfireCache, String regionName) { - applyRegionConfigurers(regionName); - ClientCache clientCache = resolveCache(gemfireCache); ClientRegionFactory clientRegionFactory = @@ -209,7 +207,7 @@ public class ClientRegionFactoryBean extends ConfigurableRegionFactoryBean if (parent != null) { - logInfo("Creating client sub-Region [%1$s] with parent Region [%2$s]", + logInfo("Creating client Subregion [%1$s] with parent Region [%2$s]", regionName, parent.getName()); return clientRegionFactory.createSubregion(parent, regionName); @@ -226,7 +224,7 @@ public class ClientRegionFactoryBean extends ConfigurableRegionFactoryBean return Optional.ofNullable(gemfireCache) .filter(GemfireUtils::isClient) - .map(cache -> (ClientCache) cache) + .map(ClientCache.class::cast) .orElseThrow(() -> newIllegalArgumentException("ClientCache is required")); } diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/support/GemfireFunctions.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/support/GemfireFunctions.java new file mode 100644 index 00000000..7b664521 --- /dev/null +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/support/GemfireFunctions.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021 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.function.Function; +import java.util.function.Supplier; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; + +import org.springframework.lang.NonNull; + +/** + * Abstract class defining useful Java {@link Function Functions} for Apache Geode + * + * @author John Blum + * @see java.util.function.Function + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @since 2.7.0 + */ +@SuppressWarnings("unused") +public abstract class GemfireFunctions { + + public static @NonNull Function> getRegionFromCache(String regionName) { + return cache -> cache.getRegion(regionName); + } + + public static @NonNull Supplier> getRegionFromCache(@NonNull GemFireCache cache, + String regionName) { + + return () -> cache.getRegion(regionName); + } + + public static @NonNull Function, Region> getSubregionFromRegion(String regionName) { + return parentRegion -> parentRegion.getSubregion(regionName); + } +} diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java index 275b54dc..a61819b2 100644 --- a/spring-data-geode/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/util/SpringUtils.java @@ -463,12 +463,6 @@ public abstract class SpringUtils { T get() throws Throwable; } - /** - * @deprecated use {@link VoidReturningThrowableOperation}. - */ - @Deprecated - public interface VoidReturningExceptionThrowingOperation extends VoidReturningThrowableOperation { } - @FunctionalInterface public interface VoidReturningThrowableOperation { void run() throws Throwable; diff --git a/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/GemfireFunctionsUnitTests.java b/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/GemfireFunctionsUnitTests.java new file mode 100644 index 00000000..00ba9dcc --- /dev/null +++ b/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/GemfireFunctionsUnitTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2021 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.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.function.Function; +import java.util.function.Supplier; + +import org.junit.Test; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; + +/** + * Unit Tests for {@link GemfireFunctions} + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @see org.springframework.data.gemfire.support.GemfireFunctions + * @since 2.7.0 + */ +public class GemfireFunctionsUnitTests { + + @Test + public void getRegionFromCacheFunctionReturnsRegion() { + + GemFireCache mockCache = mock(GemFireCache.class); + + Region mockRegion = mock(Region.class); + + doReturn(mockRegion).when(mockCache).getRegion(eq("TestRegion")); + + Function> function = + GemfireFunctions.getRegionFromCache("TestRegion"); + + assertThat(function).isNotNull(); + assertThat(function.apply(mockCache)).isEqualTo(mockRegion); + + verify(mockCache, times(1)).getRegion(eq("TestRegion")); + verifyNoMoreInteractions(mockCache); + verifyNoInteractions(mockRegion); + } + + @Test + public void getRegionFromCacheSupplierReturnsRegions() { + + GemFireCache mockCache = mock(GemFireCache.class); + + Region mockRegion = mock(Region.class); + + doReturn(mockRegion).when(mockCache).getRegion(eq("TestRegion")); + + Supplier> supplier = + GemfireFunctions.getRegionFromCache(mockCache, "TestRegion"); + + assertThat(supplier).isNotNull(); + assertThat(supplier.get()).isEqualTo(mockRegion); + + verify(mockCache, times(1)).getRegion(eq("TestRegion")); + verifyNoMoreInteractions(mockCache); + verifyNoInteractions(mockRegion); + } + + @Test + public void getSubregionFromRegionFunctionReturnsRegion() { + + Region mockParentRegion = mock(Region.class); + Region mockSubregion = mock(Region.class); + + doReturn(mockSubregion).when(mockParentRegion).getSubregion(eq("TestSubregion")); + + Function, Region> function = + GemfireFunctions.getSubregionFromRegion("TestSubregion"); + + assertThat(function).isNotNull(); + assertThat(function.apply(mockParentRegion)).isEqualTo(mockSubregion); + + verify(mockParentRegion, times(1)).getSubregion(eq("TestSubregion")); + verifyNoMoreInteractions(mockParentRegion); + verifyNoInteractions(mockSubregion); + } +}