DATAGEODE-244 - Add RegionResolver implementation to compose an array or Iterable of RegionResolvers.

This commit is contained in:
John Blum
2020-04-01 19:58:12 -07:00
parent 79412d4380
commit 36561f2453
2 changed files with 425 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
/*
* 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.Arrays;
import java.util.function.Function;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionDestroyedException;
import org.springframework.data.gemfire.RegionResolver;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@link RegionResolver} implementation used to compose a collection of {@link RegionResolver RegionResolvers}.
*
* @author John Blum
* @see org.apache.geode.cache.Region
* @see org.springframework.data.gemfire.RegionResolver
* @see org.springframework.data.gemfire.support.AbstractCachingRegionResolver
* @since 2.3.0
*/
public class ComposableRegionResolver extends AbstractCachingRegionResolver {
/**
* Factory method used to compose an array of {@link RegionResolver RegionResolvers} in a composition.
*
* @param regionResolvers array of {@link RegionResolver RegionResolvers} to compose; may be {@literal null}.
* @return a composition from the array of {@link RegionResolver RegionResolvers}; may be {@literal null}.
* @see org.springframework.data.gemfire.RegionResolver
* @see #compose(Iterable)
*/
public static RegionResolver compose(@Nullable RegionResolver... regionResolvers) {
return compose(Arrays.asList(ArrayUtils.nullSafeArray(regionResolvers, RegionResolver.class)));
}
/**
* Factory method used to compose an {@literal Iterable} collection of {@link RegionResolver RegionResolvers}
* in a composition.
*
* @param regionResolvers {@link Iterable} collection of {@link RegionResolver RegionResolvers} to compose;
* may be {@literal null}.
* @return a composition from the {@link Iterable} collection of {@link RegionResolver RegionResolvers};
* may be {@literal null}.
* @see #compose(RegionResolver, RegionResolver)
* @see org.springframework.data.gemfire.RegionResolver
* @see java.lang.Iterable
*/
public static @Nullable RegionResolver compose(@Nullable Iterable<RegionResolver> regionResolvers) {
RegionResolver current = null;
for (RegionResolver regionResolver : CollectionUtils.nullSafeIterable(regionResolvers)) {
current = compose(current, regionResolver);
}
return current;
}
/**
* Composes two {@link RegionResolver RegionResolvers} in a composition.
*
* @param one first {@link RegionResolver} in the composition.
* @param two second {@link RegionResolver} in the composition.
* @return a {@link ComposableRegionResolver} composed of the {@link RegionResolver} arguments.
* Returns the first {@link RegionResolver} if the second is {@literal null}.
* Returns the second {@link RegionResolver} if the first is {@literal null}.
* Returns {@literal null} if both {@link RegionResolver} arguments are {@literal null}.
* @see #ComposableRegionResolver(RegionResolver, RegionResolver)
* @see org.springframework.data.gemfire.RegionResolver
*/
public static @Nullable RegionResolver compose(@Nullable RegionResolver one, @Nullable RegionResolver two) {
return one == null ? two : two == null ? one : new ComposableRegionResolver(one, two);
}
private final RegionResolver regionResolverOne;
private final RegionResolver regionResolverTwo;
/**
* Constructs a new instance of {@link ComposableRegionResolver} initialized and composed with
* the given {@link RegionResolver RegionResolvers} forming the composition.
*
* @param regionResolverOne first {@link RegionResolver} in the composition; must not be {@literal null}.
* @param regionResolverTwo second {@link RegionResolver} in the composition; must not be {@literal null}.
* @throws IllegalArgumentException if either {@link RegionResolver} argument is {@literal null}.
* @see org.springframework.data.gemfire.RegionResolver
*/
protected ComposableRegionResolver(@NonNull RegionResolver regionResolverOne,
@NonNull RegionResolver regionResolverTwo) {
Assert.notNull(regionResolverOne, "RegionResolver 1 must not be null");
Assert.notNull(regionResolverTwo, "RegionResolver 2 must not be null");
this.regionResolverOne = regionResolverOne;
this.regionResolverTwo = regionResolverTwo;
}
/**
* Returns a reference to the first, non-null, configured {@link RegionResolver} in the composition.
*
* @return a reference to the first {@link RegionResolver} in the composition.
* @see org.springframework.data.gemfire.RegionResolver
*/
protected @NonNull RegionResolver getRegionResolverOne() {
return this.regionResolverOne;
}
/**
* Returns a reference to the second, non-null, configured {@link RegionResolver} in the composition.
*
* @return a reference to the second {@link RegionResolver} in the composition.
* @see org.springframework.data.gemfire.RegionResolver
*/
protected @NonNull RegionResolver getRegionResolverTwo() {
return this.regionResolverTwo;
}
/**
* Resolves the first {@literal non-null} reference to cache {@link Region} identified by
* the given {@link String name}, handling any {@link Exception Exceptions} throwing by
* the composed {@link RegionResolver RegionResolvers}, such as a {@link RegionDestroyedException}.
*
* This method may ultimately still result in a thrown {@link Exception}, but it will make a best effort to
* exhaustively consult all composed {@link RegionResolver RegionResolvers}.
*
* @param <K> {@link Class type} of the {@link Region} key.
* @param <V> {@link Class type} of the {@link Region} value.
* @param regionName {@link String name} of the {@link Region} to resolve.
* @return the first, resolved reference to a cache {@link Region} identified by the given {@link String name}.
* @see org.springframework.data.gemfire.RegionResolver#resolve(String)
* @see org.apache.geode.cache.Region
* @see java.util.function.Function
* @see #getRegionResolverOne()
* @see #getRegionResolverTwo()
*/
@Nullable @Override
protected <K, V> Region<K, V> doResolve(@Nullable String regionName) {
Function<String, Region<K, V>> regionResolverFunction = getRegionResolverTwo()::resolve;
try {
Region<K, V> region = getRegionResolverOne().resolve(regionName);
return region != null ? region : regionResolverFunction.apply(regionName);
}
catch (Throwable ignore) {
return regionResolverFunction.apply(regionName);
}
}
}

View File

@@ -0,0 +1,257 @@
/*
* 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.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.function.Supplier;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.apache.geode.cache.AttributesMutator;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionDestroyedException;
import org.springframework.data.gemfire.RegionResolver;
/**
* Unit Tests for {@link ComposableRegionResolver}.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mockito
* @see org.mockito.junit.MockitoJUnitRunner
* @see org.apache.geode.cache.Region
* @see org.springframework.data.gemfire.RegionResolver
* @see org.springframework.data.gemfire.support.ComposableRegionResolver
* @since 2.3.0
*/
@RunWith(MockitoJUnitRunner.class)
@SuppressWarnings("rawtypes")
public class ComposableRegionResolverUnitTests {
@Mock
private Region mockRegion;
@Mock(name = "MockRegionResolverOne")
private RegionResolver mockRegionResolverOne;
@Mock(name = "MockRegionResolverTwo")
private RegionResolver mockRegionResolverTwo;
@Mock(name = "MockRegionResolverThree")
private RegionResolver mockRegionResolverThree;
@Before
public void setupMockRegion() {
AttributesMutator mockAttributesMutator = mock(AttributesMutator.class);
when(this.mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator);
when(mockAttributesMutator.getRegion()).thenReturn(this.mockRegion);
}
@Test
public void constructComposableRegionResolver() {
ComposableRegionResolver regionResolver =
new ComposableRegionResolver(this.mockRegionResolverOne, this.mockRegionResolverTwo);
assertThat(regionResolver).isNotNull();
assertThat(regionResolver.getRegionResolverOne()).isEqualTo(this.mockRegionResolverOne);
assertThat(regionResolver.getRegionResolverTwo()).isEqualTo(this.mockRegionResolverTwo);
}
public void testConstructRegionResolverWithNullRegionResolver(Supplier<RegionResolver> regionResolverSupplier,
String expectedMessage) {
try {
regionResolverSupplier.get();
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage(expectedMessage);
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test(expected = IllegalArgumentException.class)
public void constructComposableRegionResolverWithNullFirstArgument() {
testConstructRegionResolverWithNullRegionResolver(
() -> new ComposableRegionResolver(null, this.mockRegionResolverTwo),
"RegionResolver 1 must not be null");
}
@Test(expected = IllegalArgumentException.class)
public void constructComposableRegionResolverWithNullSecondArgument() {
testConstructRegionResolverWithNullRegionResolver(
() -> new ComposableRegionResolver(this.mockRegionResolverOne, null),
"RegionResolver 2 must not be null");
}
@Test
public void composeWithAllNullRegionResolvers() {
assertThat(ComposableRegionResolver.compose(null, null)).isNull();
}
@Test
public void composeWithSingleNullRegionResolver() {
assertThat(ComposableRegionResolver.compose(this.mockRegionResolverOne, null))
.isSameAs(this.mockRegionResolverOne);
assertThat(ComposableRegionResolver.compose(null, this.mockRegionResolverTwo))
.isSameAs(this.mockRegionResolverTwo);
}
@Test
public void composeWithTwoNonNullRegionResolvers() {
RegionResolver regionResolver =
ComposableRegionResolver.compose(this.mockRegionResolverOne, this.mockRegionResolverTwo);
assertThat(regionResolver).isNotNull();
assertThat(regionResolver).isInstanceOf(ComposableRegionResolver.class);
assertThat(((ComposableRegionResolver) regionResolver).getRegionResolverOne()).isEqualTo(this.mockRegionResolverOne);
assertThat(((ComposableRegionResolver) regionResolver).getRegionResolverTwo()).isEqualTo(this.mockRegionResolverTwo);
}
@Test
public void composeArrayOfRegionResolvers() {
RegionResolver regionResolver = ComposableRegionResolver
.compose(this.mockRegionResolverOne, this.mockRegionResolverTwo, this.mockRegionResolverThree);
assertThat(regionResolver).isNotNull();
assertThat(regionResolver).isInstanceOf(ComposableRegionResolver.class);
RegionResolver regionResolverOne = ((ComposableRegionResolver) regionResolver).getRegionResolverOne();
RegionResolver regionResolverTwo = ((ComposableRegionResolver) regionResolver).getRegionResolverTwo();
assertThat(regionResolverOne).isNotNull();
assertThat(regionResolverOne).isInstanceOf(ComposableRegionResolver.class);
assertThat(((ComposableRegionResolver) regionResolverOne).getRegionResolverOne()).isEqualTo(this.mockRegionResolverOne);
assertThat(((ComposableRegionResolver) regionResolverOne).getRegionResolverTwo()).isEqualTo(this.mockRegionResolverTwo);
assertThat(regionResolverTwo).isEqualTo(this.mockRegionResolverThree);
}
@Test
public void composeArrayWithNullAndNonNullRegionResolvers() {
RegionResolver regionResolver = ComposableRegionResolver
.compose(null, this.mockRegionResolverOne, null, null, this.mockRegionResolverTwo);
assertThat(regionResolver).isNotNull();
assertThat(regionResolver).isInstanceOf(ComposableRegionResolver.class);
assertThat(((ComposableRegionResolver) regionResolver).getRegionResolverOne()).isEqualTo(this.mockRegionResolverOne);
assertThat(((ComposableRegionResolver) regionResolver).getRegionResolverTwo()).isEqualTo(this.mockRegionResolverTwo);
}
@Test
public void composeArrayWithSingleRegionResolver() {
assertThat(ComposableRegionResolver.compose(this.mockRegionResolverOne)).isEqualTo(this.mockRegionResolverOne);
}
@Test
public void composeEmptyArrayReturnsNull() {
assertThat(ComposableRegionResolver.compose()).isNull();
}
@Test
public void composeNullArrayIsNullSafeAndReturnsNull() {
assertThat(ComposableRegionResolver.compose((RegionResolver[]) null)).isNull();
}
@Test
public void composeNullIterableIsNullSafeAndReturnsNull() {
assertThat(ComposableRegionResolver.compose((Iterable<RegionResolver>) null)).isNull();
}
@Test
@SuppressWarnings("unchecked")
public void resolveReturnsRegionFromFirstRegionResolverInComposition() {
RegionResolver regionResolver = ComposableRegionResolver
.compose(this.mockRegionResolverOne, this.mockRegionResolverTwo, this.mockRegionResolverThree);
when(this.mockRegionResolverOne.resolve(anyString())).thenReturn(this.mockRegion);
assertThat(regionResolver.resolve("MockRegion")).isEqualTo(this.mockRegion);
verify(this.mockRegionResolverOne, times(1)).resolve(eq("MockRegion"));
verify(this.mockRegionResolverTwo, never()).resolve(anyString());
verify(this.mockRegionResolverThree, never()).resolve(anyString());
}
@Test
@SuppressWarnings("unchecked")
public void resolveReturnsRegionFromLastRegionResolverInCompositionAndCachesResolvedRegion() {
RegionResolver regionResolver = ComposableRegionResolver
.compose(this.mockRegionResolverOne, null, this.mockRegionResolverTwo,
this.mockRegionResolverThree, null, null);
when(this.mockRegionResolverThree.resolve(anyString())).thenReturn(this.mockRegion);
assertThat(regionResolver.resolve("MockRegion")).isEqualTo(this.mockRegion);
assertThat(regionResolver.resolve("MockRegion")).isEqualTo(this.mockRegion);
InOrder inOrder = Mockito
.inOrder(this.mockRegionResolverOne, this.mockRegionResolverTwo, this.mockRegionResolverThree);
inOrder.verify(this.mockRegionResolverOne, times(1)).resolve(eq("MockRegion"));
inOrder.verify(this.mockRegionResolverTwo, times(1)).resolve(eq("MockRegion"));
inOrder.verify(this.mockRegionResolverThree, times(1)).resolve(eq("MockRegion"));
}
@Test
@SuppressWarnings("unchecked")
public void resolveReturnsRegionFromSecondRegionResolverInCompositionAndCachesResolvedRegion() {
RegionResolver regionResolver = ComposableRegionResolver
.compose(this.mockRegionResolverOne, this.mockRegionResolverTwo, this.mockRegionResolverThree);
when(this.mockRegionResolverOne.resolve(anyString()))
.thenThrow(new RegionDestroyedException("TEST", "/MockRegion"));
when(this.mockRegionResolverTwo.resolve(anyString())).thenReturn(this.mockRegion);
assertThat(regionResolver.resolve("MockRegion")).isEqualTo(this.mockRegion);
assertThat(regionResolver.resolve("MockRegion")).isEqualTo(this.mockRegion);
InOrder inOrder = Mockito
.inOrder(this.mockRegionResolverOne, this.mockRegionResolverTwo, this.mockRegionResolverThree);
inOrder.verify(this.mockRegionResolverOne, times(1)).resolve(eq("MockRegion"));
inOrder.verify(this.mockRegionResolverTwo, times(1)).resolve(eq("MockRegion"));
inOrder.verify(this.mockRegionResolverThree, never()).resolve(eq("MockRegion"));
}
}