DATAGEODE-244 - Add RegionResolver implementation to compose an array or Iterable of RegionResolvers.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user