diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/support/SmartCacheResolverFactoryBean.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/support/SmartCacheResolverFactoryBean.java new file mode 100644 index 00000000..34ca8f3d --- /dev/null +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/support/SmartCacheResolverFactoryBean.java @@ -0,0 +1,394 @@ +/* + * 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.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.CacheFactory; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.client.ClientCacheFactory; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.gemfire.CacheResolver; +import org.springframework.data.gemfire.client.support.ClientCacheFactoryCacheResolver; +import org.springframework.data.gemfire.util.CacheUtils; +import org.springframework.data.gemfire.util.CollectionUtils; +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} used to construct a custom, determined {@link CacheResolver} that strategically + * and lazily resolves a cache instance. + * + * @author John Blum + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.beans.factory.InitializingBean + * @see org.springframework.data.gemfire.CacheResolver + * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport + * @since 2.3.0 + */ +@SuppressWarnings({ "rawtypes", "unused" }) +public class SmartCacheResolverFactoryBean extends AbstractFactoryBeanSupport> + implements InitializingBean { + + /** + * Factory method used to construct a new instance of the {@link SmartCacheResolverFactoryBean}. + * + * @return a new instance of {@link SmartCacheResolverFactoryBean}. + * @see org.springframework.data.gemfire.support.SmartCacheResolverFactoryBean + */ + public static @NonNull SmartCacheResolverFactoryBean create() { + return new SmartCacheResolverFactoryBean(); + } + + private CacheResolver cacheResolver; + + private CompositionStrategy compositionStrategy = CompositionStrategy.DEFAULT; + + private GemFireCache cache; + + private List configuredCacheResolvers; + + private String cacheBeanName; + + /** + * Initializes the {@link CacheResolver} [composition] created by this {@link FactoryBean}. + * + * @see org.springframework.data.gemfire.support.SmartCacheResolverFactoryBean.CompositionStrategy#use(SmartCacheResolverFactoryBean) + * @see #getCompositionStrategy() + */ + @Override + public void afterPropertiesSet() { + + this.cacheResolver = getCompositionStrategy().use(this); + + Assert.state(this.cacheResolver != null, + () -> String.format("No CacheResolver was composed with CompositionStrategy [%s]", + getCompositionStrategy())); + + } + + /** + * Returns a reference to the constructed {@link CacheResolver} instance created by this {@link FactoryBean}. + * + * @return a reference to the constructed {@link CacheResolver} instance created by this {@link FactoryBean}. + * @see org.springframework.data.gemfire.CacheResolver + * @see org.apache.geode.cache.GemFireCache + */ + CacheResolver getCacheResolver() { + return this.cacheResolver; + } + + /** + * Returns the fully constructed and initialized instance of the {@link CacheResolver} + * created by this {@link FactoryBean}. + * + * If the {@link #afterPropertiesSet()} method has not yet been called, then this method returns a {@literal Proxy} + * to the {@link CacheResolver} instance that will eventually be created by this {@link FactoryBean}. + * + * @return the {@link CacheResolver} instance constructed and initialized by this {@link FactoryBean}. + * @see org.springframework.beans.factory.FactoryBean#getObject() + * @see CacheResolverProxy + */ + @NonNull + public CacheResolver getObject() { + return new CacheResolverProxy(); + } + + /** + * Returns the {@link Class type} of the {@link CacheResolver} returned by this {@link FactoryBean}. + * + * @return the {@link Class type} of the {@link CacheResolver} returned by this {@link FactoryBean}. + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + * @see java.lang.Class + */ + @Nullable @Override + public Class getObjectType() { + + CacheResolver cacheResolver = getCacheResolver(); + + return cacheResolver != null ? cacheResolver.getClass() : CacheResolver.class; + } + + /** + * Constructs a {@link CacheResolver} capable of resolving a cache instance from the Spring {@link BeanFactory}. + * + * Returns {@literal null} if the {@link BeanFactory} reference is {@literal null} (i.e. not configured). + * + * @return a {@link CacheResolver} capable of resolving a cache instance from the Spring {@link BeanFactory}. + * @see org.springframework.data.gemfire.support.BeanFactoryCacheResolver + * @see org.springframework.data.gemfire.CacheResolver + * @see #getBeanFactory() + */ + protected @Nullable CacheResolver newBeanFactoryCacheResolver() { + + // Depending on the application context, there may or may not be a Spring ApplicationContext... + // Think Spring on the GemFire/Geode server-side with no Spring container + return Optional.ofNullable(getBeanFactory()) + .map(BeanFactoryCacheResolver::new) + .map(cacheResolver -> { + getCacheBeanName().ifPresent(cacheResolver::setCacheBeanName); + return cacheResolver; + }) + .orElse(null); + } + + /** + * Constructs a {@link CacheResolver} capable of resolving a {@link ClientCache} instance using + * the Apache Geode {@link ClientCacheFactory} API. + * + * @return a {@link CacheResolver} resolving a {@link ClientCache} instance. + * @see org.springframework.data.gemfire.client.support.ClientCacheFactoryCacheResolver + * @see org.springframework.data.gemfire.CacheResolver + */ + protected @NonNull CacheResolver newClientCacheFactoryCacheResolver() { + return new ClientCacheFactoryCacheResolver(); + } + + /** + * Constructs a {@link CacheResolver} capable of resolving a {@literal peer} {@link Cache} instance using + * the Apache Geode {@link CacheFactory} API. + * + * @return a {@link CacheResolver} resolving a {@literal peer} {@link Cache} instance. + * @see org.springframework.data.gemfire.support.CacheFactoryCacheResolver + * @see org.springframework.data.gemfire.CacheResolver + */ + protected @NonNull CacheResolver newPeerCacheFactoryCacheResolver() { + return new CacheFactoryCacheResolver(); + } + + /** + * Constructs a {@link CacheResolver} capable of resolving a single, configured cache instance. + * + * Returns {@literal null} if a cache instance was not {@link #setCache(GemFireCache) configured} + * on this {@link FactoryBean}. + * + * @return a {@link CacheResolver} capable of resolving a single, configured cache instance. + * @see org.springframework.data.gemfire.support.SingleCacheCacheResolver + * @see org.springframework.data.gemfire.CacheResolver + * @see #getCache() + */ + protected @Nullable CacheResolver newSingleCacheCacheResolver() { + + return getCache() + .map(this::newSingleCacheCacheResolver) + .orElse(null); + } + + private @NonNull CacheResolver newSingleCacheCacheResolver(@NonNull GemFireCache cache) { + + return CacheUtils.isClient(cache) + ? SingleCacheCacheResolver.from((ClientCache) cache) + : SingleCacheCacheResolver.from((Cache) cache); + } + + public final void setCache(@Nullable GemFireCache cache) { + this.cache = cache; + } + + protected Optional getCache() { + return Optional.ofNullable(this.cache); + } + + public final void setCacheBeanName(@Nullable String cacheBeanName) { + this.cacheBeanName = cacheBeanName; + } + + protected Optional getCacheBeanName() { + + return Optional.ofNullable(this.cacheBeanName) + .filter(StringUtils::hasText); + } + + public final void setCompositionStrategy(CompositionStrategy compositionStrategy) { + this.compositionStrategy = compositionStrategy; + } + + protected CompositionStrategy getCompositionStrategy() { + return this.compositionStrategy != null ? this.compositionStrategy : CompositionStrategy.DEFAULT; + } + + @Autowired(required = false) + public final void setConfiguredCacheResolvers(List cacheResolvers) { + + // NOTE: Be careful not to add the CacheResolverProxy from this SmartCacheResolverFactoryBean + this.configuredCacheResolvers = CollectionUtils.nullSafeList(cacheResolvers).stream() + .filter(Objects::nonNull) + .filter(this::isNotCacheResolverProxyForThisFactoryBean) + .collect(Collectors.toList()); + } + + protected List getConfiguredCacheResolvers() { + return this.configuredCacheResolvers; + } + + private boolean isCacheResolverProxyForThisFactoryBean(CacheResolver cacheResolver) { + + return cacheResolver instanceof CacheResolverProxy + && ((CacheResolverProxy) cacheResolver).getCacheResolverFactoryBean().equals(this); + } + + private boolean isNotCacheResolverProxyForThisFactoryBean(CacheResolver cacheResolver) { + return !isCacheResolverProxyForThisFactoryBean(cacheResolver); + } + + private List resolveCacheResolvers() { + + BeanFactory beanFactory = getBeanFactory(); + + Assert.state(beanFactory != null, + () -> String.format("A Spring context is not present and is required to use [%1$s.%2$s.%3$s]", + CompositionStrategy.class.getEnclosingClass().getSimpleName(), + CompositionStrategy.class.getSimpleName(), CompositionStrategy.USER_DEFINED.name())); + + List orderedCacheResolvers = + SpringUtils.getOrderedStreamOfBeansByType(beanFactory, CacheResolver.class) + .collect(Collectors.toList()); + + setConfiguredCacheResolvers(orderedCacheResolvers); + + return orderedCacheResolvers; + } + + public SmartCacheResolverFactoryBean withCache(GemFireCache cache) { + setCache(cache); + return this; + } + + public SmartCacheResolverFactoryBean withCacheBeanName(String cacheBeanName) { + setCacheBeanName(cacheBeanName); + return this; + } + + public SmartCacheResolverFactoryBean usingCompositionStrategy(CompositionStrategy compositionStrategy) { + setCompositionStrategy(compositionStrategy); + return this; + } + + class CacheResolverProxy implements CacheResolver { + + Optional> getCacheResolver() { + return Optional.ofNullable(getCacheResolverFactoryBean().getCacheResolver()); + } + + SmartCacheResolverFactoryBean getCacheResolverFactoryBean() { + return SmartCacheResolverFactoryBean.this; + } + + @Override + public GemFireCache resolve() { + + return getCacheResolver() + .map(CacheResolver::resolve) + .orElseThrow(() -> newIllegalStateException("CacheResolver was not initialized")); + } + } + + /** + * Specifies the declaration of the algorithm to use to compose {@link CacheResolver CacheResolvers} + * into a composition functioning as a single, complete and cohesive cache resolution strategy. + */ + enum CompositionStrategy { + + DEFAULT { + + @Override @SuppressWarnings("unchecked") + CacheResolver use(SmartCacheResolverFactoryBean factoryBean) { + + return ComposableCacheResolver.compose( + factoryBean.newSingleCacheCacheResolver(), + factoryBean.newBeanFactoryCacheResolver(), + factoryBean.newClientCacheFactoryCacheResolver(), + factoryBean.newPeerCacheFactoryCacheResolver()); + } + }, + + GEODE { + + @Override @SuppressWarnings("unchecked") + CacheResolver use(SmartCacheResolverFactoryBean factoryBean) { + + return ComposableCacheResolver.compose( + factoryBean.newSingleCacheCacheResolver(), + factoryBean.newClientCacheFactoryCacheResolver(), + factoryBean.newPeerCacheFactoryCacheResolver() + ); + } + }, + + SPRING { + + @Override @SuppressWarnings("unchecked") + CacheResolver use(SmartCacheResolverFactoryBean factoryBean) { + + return ComposableCacheResolver.compose( + factoryBean.newSingleCacheCacheResolver(), + factoryBean.newBeanFactoryCacheResolver() + ); + } + }, + + USER_DEFINED { + + @Override @SuppressWarnings("unchecked") + CacheResolver use(SmartCacheResolverFactoryBean factoryBean) { + + List cacheResolvers = computeIfAbsent(factoryBean); + + Assert.state(isNotEmpty(cacheResolvers), "No CacheResolver beans were defined"); + + CacheResolver current = null; + + for (CacheResolver cacheResolver : cacheResolvers) { + current = ComposableCacheResolver.compose(current, cacheResolver); + } + + return current; + } + + private synchronized List computeIfAbsent(SmartCacheResolverFactoryBean factoryBean) { + + return Optional.ofNullable(factoryBean.getConfiguredCacheResolvers()) + .filter(this::isNotEmpty) + .orElseGet(factoryBean::resolveCacheResolvers); + } + + private boolean isNotEmpty(List list) { + return !(list == null || list.isEmpty()); + } + }; + + abstract CacheResolver use(SmartCacheResolverFactoryBean factoryBean); + + } +} diff --git a/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/SmartCacheResolverFactoryBeanIntegrationTests.java b/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/SmartCacheResolverFactoryBeanIntegrationTests.java new file mode 100644 index 00000000..24e7cc22 --- /dev/null +++ b/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/SmartCacheResolverFactoryBeanIntegrationTests.java @@ -0,0 +1,245 @@ +/* + * 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.Mockito.atLeastOnce; +import static org.mockito.Mockito.inOrder; +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.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; + +import org.apache.geode.cache.CacheClosedException; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.client.ClientCache; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.data.gemfire.CacheResolver; +import org.springframework.data.gemfire.support.SmartCacheResolverFactoryBean.CacheResolverProxy; +import org.springframework.data.gemfire.support.SmartCacheResolverFactoryBean.CompositionStrategy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration Tests for {@link SmartCacheResolverFactoryBean}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.context.ApplicationContext + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.core.annotation.Order + * @see org.springframework.data.gemfire.CacheResolver + * @see org.springframework.data.gemfire.support.SmartCacheResolverFactoryBean + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 2.3.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@SuppressWarnings({ "rawtypes", "unused" }) +public class SmartCacheResolverFactoryBeanIntegrationTests { + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + // Primary (SUT) + private CacheResolver cacheResolver; + + @Autowired + @Qualifier("A") + private CacheResolver a; + + @Autowired + @Qualifier("B") + private CacheResolver b; + + @Autowired + @Qualifier("C") + private CacheResolver c; + + @Autowired + @Qualifier("D") + private CacheResolver d; + + @Autowired + @Qualifier("U") + private CacheResolver u; + + @Autowired + @Qualifier("X") + private CacheResolver x; + + @Autowired + @Qualifier("Y") + private CacheResolver y; + + @Autowired + @Qualifier("Z") + private CacheResolver z; + + @Autowired + private CacheResolver[] cacheResolvers; + + @Autowired + private ClientCache clientCache; + + @Test + public void cacheResolversAreInjectedInOrder() { + assertThat(this.cacheResolvers).containsExactly(this.y, this.x, this.z, this.b, this.c, this.a, this.d, + this.cacheResolver, this.u); + } + + @Test + public void resolvesCache() { + + assertThat(this.cacheResolver).isInstanceOf(CacheResolverProxy.class); + assertThat(((CacheResolverProxy) this.cacheResolver).getCacheResolver().orElse(null)) + .isInstanceOf(ComposableCacheResolver.class); + assertThat(this.cacheResolver.resolve()).isEqualTo(this.clientCache); + + InOrder inOrder = inOrder(this.a, this.b, this.c, this.d, this.u, this.x, this.y, this.z); + + inOrder.verify(this.y, times(1)).resolve(); + inOrder.verify(this.x, times(1)).resolve(); + inOrder.verify(this.z, times(1)).resolve(); + inOrder.verify(this.b, atLeastOnce()).resolve(); + inOrder.verify(this.c, times(1)).resolve(); + inOrder.verify(this.a, times(1)).resolve(); + inOrder.verify(this.d, never()).resolve(); + inOrder.verify(this.u, never()).resolve(); + } + + @Configuration + static class TestConfiguration { + + @Bean + ClientCache mockClientCache() { + return mock(ClientCache.class); + } + + @Primary + @Bean("SmartCacheResolver") + SmartCacheResolverFactoryBean smartCacheResolver() { + return SmartCacheResolverFactoryBean.create() + .usingCompositionStrategy(CompositionStrategy.USER_DEFINED); + } + + @Bean("A") + @Order(3) + CacheResolver a(ClientCache clientCache) { + + CacheResolver mockCacheResolver = mock(CacheResolver.class, "A"); + + when(mockCacheResolver.resolve()).thenReturn(clientCache); + + return mockCacheResolver; + } + + @Bean("B") + @Order(1) + CacheResolver b() { + + CacheResolver mockCacheResolver = mock(CacheResolver.class, "B"); + + when(mockCacheResolver.resolve()).thenThrow(new CacheClosedException("TEST")); + + return mockCacheResolver; + } + + @Bean("C") + @Order(2) + CacheResolver c() { + return mock(CacheResolver.class, "C"); + } + + @Bean("D") + @Order(4) + CacheResolver d() { + return mock(CacheResolver.class, "D"); + } + + @Bean("U") + CacheResolver unorderedCacheResolver() { + return mock(CacheResolver.class, "U"); + } + + @Bean("X") + CacheResolver x() { + return spy(new AtOrderCacheResolver()); + } + + @Bean("Y") + CacheResolver y() { + return spy(new OrderedCacheResolver()); + } + + @Bean("Z") + CacheResolver z() { + return spy(new Z()); + } + } + + @Order(-1) + static class AtOrderCacheResolver implements CacheResolver { + + @Override + public GemFireCache resolve() { + return null; + } + } + + static class OrderedCacheResolver implements CacheResolver, Ordered { + + @Override + public int getOrder() { + return -2; + } + + @Override + public GemFireCache resolve() { + return null; + } + } + + @Order(0) + static abstract class ZeroOrderedCacheResolver implements CacheResolver { + + @Override + public GemFireCache resolve() { + return null; + } + } + + static class Z extends ZeroOrderedCacheResolver { } + +} diff --git a/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/SmartCacheResolverFactoryBeanUnitTests.java b/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/SmartCacheResolverFactoryBeanUnitTests.java new file mode 100644 index 00000000..ba9979b9 --- /dev/null +++ b/spring-data-geode/src/test/java/org/springframework/data/gemfire/support/SmartCacheResolverFactoryBeanUnitTests.java @@ -0,0 +1,831 @@ +/* + * 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.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +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 java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.InOrder; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.CacheClosedException; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.client.ClientCache; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.Order; +import org.springframework.data.gemfire.CacheResolver; +import org.springframework.data.gemfire.client.support.ClientCacheFactoryCacheResolver; +import org.springframework.data.gemfire.support.SmartCacheResolverFactoryBean.CacheResolverProxy; +import org.springframework.data.gemfire.support.SmartCacheResolverFactoryBean.CompositionStrategy; + +/** + * Unit Tests for {@link SmartCacheResolverFactoryBean}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.data.gemfire.CacheResolver + * @see org.springframework.data.gemfire.support.SmartCacheResolverFactoryBean + * @since 2.3.0 + */ +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class SmartCacheResolverFactoryBeanUnitTests { + + private CacheResolver resolveCacheResolverFromProxy(CacheResolver cacheResolver) { + + return cacheResolver instanceof CacheResolverProxy + ? ((CacheResolverProxy) cacheResolver).getCacheResolver().orElse(null) + : null; + } + + @Test + public void factoryBeanUsingDefaultCompositionStrategyIsFullyLoaded() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + ClientCache mockClientCache = mock(ClientCache.class); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setBeanFactory(mockBeanFactory); + factoryBean.setCache(mockClientCache); + factoryBean.setCacheBeanName("TestCacheBeanName"); + + assertThat(factoryBean.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(factoryBean.getCache().orElse(null)).isEqualTo(mockClientCache); + assertThat(factoryBean.getCacheBeanName().orElse(null)).isEqualTo("TestCacheBeanName"); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.DEFAULT); + + factoryBean.afterPropertiesSet(); + + CacheResolver cacheResolver = resolveCacheResolverFromProxy(factoryBean.getObject()); + + assertThat(cacheResolver).isNotNull(); + assertThat(cacheResolver).isInstanceOf(ComposableCacheResolver.class); + + ComposableCacheResolver composed = (ComposableCacheResolver) cacheResolver; + + CacheResolver one = composed.getCacheResolverOne(); + CacheResolver two = composed.getCacheResolverTwo(); + + assertThat(one).isInstanceOf(ComposableCacheResolver.class); + assertThat(two).isInstanceOf(CacheFactoryCacheResolver.class); + + two = ((ComposableCacheResolver) one).getCacheResolverTwo(); + one = ((ComposableCacheResolver) one).getCacheResolverOne(); + + assertThat(one).isInstanceOf(ComposableCacheResolver.class); + assertThat(two).isInstanceOf(ClientCacheFactoryCacheResolver.class); + + two = ((ComposableCacheResolver) one).getCacheResolverTwo(); + one = ((ComposableCacheResolver) one).getCacheResolverOne(); + + assertThat(one.getClass().getName()).startsWith(SingleCacheCacheResolver.class.getName()); + assertThat(one.resolve()).isEqualTo(mockClientCache); + assertThat(two).isInstanceOf(BeanFactoryCacheResolver.class); + assertThat(((BeanFactoryCacheResolver) two).getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(((BeanFactoryCacheResolver) two).getCacheBeanName().orElse(null)).isEqualTo("TestCacheBeanName"); + } + + @Test + public void factoryBeanUsingDefaultCompositionStrategyIsConditionallyComposed() { + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + assertThat(factoryBean.getBeanFactory()).isNull(); + assertThat(factoryBean.getCache().orElse(null)).isNull(); + assertThat(factoryBean.getCacheBeanName().orElse(null)).isNull(); + + factoryBean.afterPropertiesSet(); + + CacheResolver cacheResolver = resolveCacheResolverFromProxy(factoryBean.getObject()); + + assertThat(cacheResolver).isNotNull(); + assertThat(cacheResolver).isInstanceOf(ComposableCacheResolver.class); + assertThat(((ComposableCacheResolver) cacheResolver).getCacheResolverOne()) + .isInstanceOf(ClientCacheFactoryCacheResolver.class); + assertThat(((ComposableCacheResolver) cacheResolver).getCacheResolverTwo()) + .isInstanceOf(CacheFactoryCacheResolver.class); + } + + @Test + public void factoryBeanUsingDefaultCompositionStrategyResolvesCacheInOrder() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + Cache mockPeerCache = mock(Cache.class); + + CacheResolver mockBeanFactoryCacheResolver = mock(CacheResolver.class); + CacheResolver mockClientCacheFactoryCacheResolver = mock(CacheResolver.class); + CacheResolver mockPeerCacheFactoryCacheResolver = mock(CacheResolver.class); + CacheResolver mockSingleCacheCacheResolver = mock(CacheResolver.class); + + SmartCacheResolverFactoryBean factoryBean = spy(new SmartCacheResolverFactoryBean()); + + doReturn(mockBeanFactoryCacheResolver).when(factoryBean).newBeanFactoryCacheResolver(); + doReturn(mockClientCacheFactoryCacheResolver).when(factoryBean).newClientCacheFactoryCacheResolver(); + doReturn(mockPeerCacheFactoryCacheResolver).when(factoryBean).newPeerCacheFactoryCacheResolver(); + doReturn(mockSingleCacheCacheResolver).when(factoryBean).newSingleCacheCacheResolver(); + + doThrow(new NoSuchBeanDefinitionException("TEST")).when(mockBeanFactoryCacheResolver).resolve(); + doReturn(mockPeerCache).when(mockPeerCacheFactoryCacheResolver).resolve(); + + factoryBean.setBeanFactory(mockBeanFactory); + factoryBean.setCache(mockPeerCache); + factoryBean.setCacheBeanName("TestCacheBeanName"); + + assertThat(factoryBean.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(factoryBean.getCache().orElse(null)).isEqualTo(mockPeerCache); + assertThat(factoryBean.getCacheBeanName().orElse(null)).isEqualTo("TestCacheBeanName"); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.DEFAULT); + + factoryBean.afterPropertiesSet(); + + CacheResolver cacheResolver = resolveCacheResolverFromProxy(factoryBean.getObject()); + + assertThat(cacheResolver).isNotNull(); + assertThat(cacheResolver.resolve()).isEqualTo(mockPeerCache); + + InOrder inOrder = inOrder(mockBeanFactoryCacheResolver, mockClientCacheFactoryCacheResolver, + mockPeerCacheFactoryCacheResolver, mockSingleCacheCacheResolver); + + inOrder.verify(mockSingleCacheCacheResolver, times(1)).resolve(); + inOrder.verify(mockBeanFactoryCacheResolver, atLeastOnce()).resolve(); + inOrder.verify(mockClientCacheFactoryCacheResolver, times(1)).resolve(); + inOrder.verify(mockPeerCacheFactoryCacheResolver, times(1)).resolve(); + } + + @Test + public void factoryBeanUsingGeodeCompositionStrategyIsFullyLoaded() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + ClientCache mockClientCache = mock(ClientCache.class); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setBeanFactory(mockBeanFactory); + factoryBean.setCache(mockClientCache); + factoryBean.setCompositionStrategy(CompositionStrategy.GEODE); + + assertThat(factoryBean.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(factoryBean.getCache().orElse(null)).isEqualTo(mockClientCache); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.GEODE); + + factoryBean.afterPropertiesSet(); + + CacheResolver cacheResolver = resolveCacheResolverFromProxy(factoryBean.getObject()); + + assertThat(cacheResolver).isNotNull(); + assertThat(cacheResolver).isInstanceOf(ComposableCacheResolver.class); + + CacheResolver one = ((ComposableCacheResolver) cacheResolver).getCacheResolverOne(); + CacheResolver two = ((ComposableCacheResolver) cacheResolver).getCacheResolverTwo(); + + assertThat(one).isInstanceOf(ComposableCacheResolver.class); + assertThat(two).isInstanceOf(CacheFactoryCacheResolver.class); + + two = ((ComposableCacheResolver) one).getCacheResolverTwo(); + one = ((ComposableCacheResolver) one).getCacheResolverOne(); + + assertThat(one.getClass().getName()).startsWith(SingleCacheCacheResolver.class.getName()); + assertThat(two).isInstanceOf(ClientCacheFactoryCacheResolver.class); + } + + @Test + public void factoryBeanUsingGeodeCompositionStrategyIsConditionallyComposed() { + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setCompositionStrategy(CompositionStrategy.GEODE); + + assertThat(factoryBean.getCache().orElse(null)).isNull(); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.GEODE); + + factoryBean.afterPropertiesSet(); + + CacheResolver cacheResolver = resolveCacheResolverFromProxy(factoryBean.getObject()); + + assertThat(cacheResolver).isNotNull(); + assertThat(cacheResolver).isInstanceOf(ComposableCacheResolver.class); + + CacheResolver one = ((ComposableCacheResolver) cacheResolver).getCacheResolverOne(); + CacheResolver two = ((ComposableCacheResolver) cacheResolver).getCacheResolverTwo(); + + assertThat(one).isInstanceOf(ClientCacheFactoryCacheResolver.class); + assertThat(two).isInstanceOf(CacheFactoryCacheResolver.class); + } + + @Test + public void factoryBeanUsingGeodeCompositionStrategyResolvesCacheInOrder() { + + Cache mockPeerCache = mock(Cache.class); + + CacheResolver mockClientCacheFactoryCacheResolver = mock(CacheResolver.class); + CacheResolver mockPeerCacheFactoryCacheResolver = mock(CacheResolver.class); + CacheResolver mockSingleCacheCacheResolver = mock(CacheResolver.class); + + SmartCacheResolverFactoryBean factoryBean = spy(new SmartCacheResolverFactoryBean()); + + doReturn(mockClientCacheFactoryCacheResolver).when(factoryBean).newClientCacheFactoryCacheResolver(); + doReturn(mockPeerCacheFactoryCacheResolver).when(factoryBean).newPeerCacheFactoryCacheResolver(); + doReturn(mockSingleCacheCacheResolver).when(factoryBean).newSingleCacheCacheResolver(); + + doThrow(new CacheClosedException("TEST")).when(mockClientCacheFactoryCacheResolver).resolve(); + doReturn(mockPeerCache).when(mockPeerCacheFactoryCacheResolver).resolve(); + + factoryBean.setCache(mockPeerCache); + factoryBean.setCompositionStrategy(CompositionStrategy.GEODE); + + assertThat(factoryBean.getCache().orElse(null)).isEqualTo(mockPeerCache); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.GEODE); + + factoryBean.afterPropertiesSet(); + + CacheResolver cacheResolver = resolveCacheResolverFromProxy(factoryBean.getObject()); + + assertThat(cacheResolver.resolve()).isEqualTo(mockPeerCache); + + InOrder inOrder = inOrder(mockClientCacheFactoryCacheResolver, + mockPeerCacheFactoryCacheResolver, mockSingleCacheCacheResolver); + + inOrder.verify(mockSingleCacheCacheResolver, times(1)).resolve(); + inOrder.verify(mockClientCacheFactoryCacheResolver, atLeastOnce()).resolve(); + inOrder.verify(mockPeerCacheFactoryCacheResolver, times(1)).resolve(); + } + + @Test + public void factoryBeanUsingSpringCompositionStrategyIsFullyLoaded() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + ClientCache mockClientCache = mock(ClientCache.class); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setBeanFactory(mockBeanFactory); + factoryBean.setCache(mockClientCache); + factoryBean.setCacheBeanName("TestCacheBeanName"); + factoryBean.setCompositionStrategy(CompositionStrategy.SPRING); + + assertThat(factoryBean.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(factoryBean.getCache().orElse(null)).isEqualTo(mockClientCache); + assertThat(factoryBean.getCacheBeanName().orElse(null)).isEqualTo("TestCacheBeanName"); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.SPRING); + + factoryBean.afterPropertiesSet(); + + CacheResolver cacheResolver = resolveCacheResolverFromProxy(factoryBean.getObject()); + + assertThat(cacheResolver).isNotNull(); + assertThat(cacheResolver).isInstanceOf(ComposableCacheResolver.class); + + CacheResolver one = ((ComposableCacheResolver) cacheResolver).getCacheResolverOne(); + CacheResolver two = ((ComposableCacheResolver) cacheResolver).getCacheResolverTwo(); + + assertThat(one.getClass().getName()).startsWith(SingleCacheCacheResolver.class.getName()); + assertThat(two).isInstanceOf(BeanFactoryCacheResolver.class); + assertThat(((BeanFactoryCacheResolver) two).getCacheBeanName().orElse(null)) + .isEqualTo("TestCacheBeanName"); + } + + @Test + public void factoryBeanUsingSpringCompositionStrategyIsConditionallyComposedWithBeanFactoryCacheLoader() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setBeanFactory(mockBeanFactory); + factoryBean.setCompositionStrategy(CompositionStrategy.SPRING); + + assertThat(factoryBean.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(factoryBean.getCache().orElse(null)).isNull(); + assertThat(factoryBean.getCacheBeanName().orElse(null)).isNull(); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.SPRING); + + factoryBean.afterPropertiesSet(); + + CacheResolver cacheResolver = resolveCacheResolverFromProxy(factoryBean.getObject()); + + assertThat(cacheResolver).isInstanceOf(BeanFactoryCacheResolver.class); + assertThat(((BeanFactoryCacheResolver) cacheResolver).getCacheBeanName().orElse(null)).isNull(); + } + + @Test + public void factoryBeanUsingSpringCompositionStrategyIsConditionallyComposedWithSingleCacheCacheLoader() { + + ClientCache mockClientCache = mock(ClientCache.class); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setCache(mockClientCache); + factoryBean.setCacheBeanName("TestCacheBeanName"); + factoryBean.setCompositionStrategy(CompositionStrategy.SPRING); + + assertThat(factoryBean.getBeanFactory()).isNull(); + assertThat(factoryBean.getCache().orElse(null)).isEqualTo(mockClientCache); + assertThat(factoryBean.getCacheBeanName().orElse(null)).isEqualTo("TestCacheBeanName"); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.SPRING); + + factoryBean.afterPropertiesSet(); + + CacheResolver cacheResolver = resolveCacheResolverFromProxy(factoryBean.getObject()); + + assertThat(cacheResolver).isNotNull(); + assertThat(cacheResolver.getClass().getName()).startsWith(SingleCacheCacheResolver.class.getName()); + } + + @Test(expected = IllegalStateException.class) + public void factoryBeanUsingSpringCompositionStrategyComposesNoCacheResolverThrowsIllegalStateException() { + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setCacheBeanName("TestCacheBeanName"); + factoryBean.setCompositionStrategy(CompositionStrategy.SPRING); + + assertThat(factoryBean.getBeanFactory()).isNull(); + assertThat(factoryBean.getCache().orElse(null)).isNull(); + assertThat(factoryBean.getCacheBeanName().orElse(null)).isEqualTo("TestCacheBeanName"); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.SPRING); + + try { + factoryBean.afterPropertiesSet(); + } + catch (IllegalStateException expected) { + + assertThat(expected).hasMessage("No CacheResolver was composed with CompositionStrategy [%s]", + CompositionStrategy.SPRING); + + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + assertThat(resolveCacheResolverFromProxy(factoryBean.getObject())).isNull(); + } + } + + @Test + public void factoryBeanUsingSpringCompositionStrategyResolvesCacheInOrder() { + + Cache mockCache = mock(Cache.class); + + CacheResolver mockBeanFactoryCacheResolver = mock(CacheResolver.class); + CacheResolver mockSingleCacheCacheResolver = mock(CacheResolver.class); + + doReturn(mockCache).when(mockBeanFactoryCacheResolver).resolve(); + + SmartCacheResolverFactoryBean factoryBean = spy(new SmartCacheResolverFactoryBean()); + + doReturn(mockBeanFactoryCacheResolver).when(factoryBean).newBeanFactoryCacheResolver(); + doReturn(mockSingleCacheCacheResolver).when(factoryBean).newSingleCacheCacheResolver(); + + factoryBean.setCompositionStrategy(CompositionStrategy.SPRING); + + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.SPRING); + + factoryBean.afterPropertiesSet(); + + CacheResolver cacheResolver = resolveCacheResolverFromProxy(factoryBean.getObject()); + + assertThat(cacheResolver).isInstanceOf(ComposableCacheResolver.class); + assertThat(cacheResolver.resolve()).isEqualTo(mockCache); + + InOrder inOrder = inOrder(mockBeanFactoryCacheResolver, mockSingleCacheCacheResolver); + + inOrder.verify(mockSingleCacheCacheResolver, times(1)).resolve(); + inOrder.verify(mockBeanFactoryCacheResolver, times(1)).resolve(); + } + + @Test + public void factoryBeanUsingUserDefinedCompositionStrategy() { + + // Questionable?? Simulate Spring container behavior (i.e. BeanFactory.getBeanProvider(..).orderedStream()) + // by returning an ordered (sorted) Stream based on @Order annotation/Ordered interface. + Stream> cacheResolverStream = Stream.of(spy(new A()), spy(new B())) + .sorted(AnnotationAwareOrderComparator.INSTANCE); + + ObjectProvider> mockObjectProvider = mock(ObjectProvider.class); + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + when(mockObjectProvider.orderedStream()).thenReturn(cacheResolverStream); + when(mockBeanFactory.getBeanProvider(ArgumentMatchers.any())).thenReturn(mockObjectProvider); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setBeanFactory(mockBeanFactory); + factoryBean.setCompositionStrategy(CompositionStrategy.USER_DEFINED); + + assertThat(factoryBean.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.USER_DEFINED); + + factoryBean.afterPropertiesSet(); + + CacheResolver cacheResolver = resolveCacheResolverFromProxy(factoryBean.getObject()); + + assertThat(cacheResolver).isNotNull(); + assertThat(cacheResolver).isInstanceOf(ComposableCacheResolver.class); + + CacheResolver one = ((ComposableCacheResolver) cacheResolver).getCacheResolverOne(); + CacheResolver two = ((ComposableCacheResolver) cacheResolver).getCacheResolverTwo(); + + assertThat(one).isInstanceOf(B.class); + assertThat(two).isInstanceOf(A.class); + assertThat(cacheResolver.resolve()).isEqualTo(B.MOCK_CACHE); + + InOrder inOrder = inOrder(one, two); + + inOrder.verify(one, times(1)).resolve(); + inOrder.verify(two, never()).resolve(); + + verify(mockBeanFactory, times(1)).getBeanProvider(eq(CacheResolver.class)); + verify(mockObjectProvider, times(1)).orderedStream(); + } + + @Test(expected = IllegalStateException.class) + public void factoryBeanUsingUserDefinedCompositionStrategyWithNoBeanFactory() { + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setCompositionStrategy(CompositionStrategy.USER_DEFINED); + + assertThat(factoryBean.getBeanFactory()).isNull(); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.USER_DEFINED); + + try { + factoryBean.afterPropertiesSet(); + } + catch (IllegalStateException expected) { + + String expectedMessage = "A Spring context is not present and is required to use [%1$s.%2$s.%3$s]"; + + assertThat(expected).hasMessage(expectedMessage, SmartCacheResolverFactoryBean.class.getSimpleName(), + CompositionStrategy.class.getSimpleName(), CompositionStrategy.USER_DEFINED); + + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + assertThat(resolveCacheResolverFromProxy(factoryBean.getObject())).isNull(); + } + } + + @Test(expected = IllegalStateException.class) + public void factoryBeanUsingUserDefinedCompositionStrategyWithNoCacheResolverBeans() { + + Stream mockStream = mock(Stream.class); + + ObjectProvider mockBeanProvider = mock(ObjectProvider.class); + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + when(mockBeanProvider.orderedStream()).thenReturn(mockStream); + when(mockBeanFactory.getBeanProvider(eq(CacheResolver.class))).thenReturn(mockBeanProvider); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setBeanFactory(mockBeanFactory); + factoryBean.setCompositionStrategy(CompositionStrategy.USER_DEFINED); + + assertThat(factoryBean.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.USER_DEFINED); + + try { + factoryBean.afterPropertiesSet(); + } + catch (IllegalStateException expected) { + + assertThat(expected).hasMessage("No CacheResolver beans were defined"); + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + + assertThat(resolveCacheResolverFromProxy(factoryBean.getObject())).isNull(); + + verify(mockBeanFactory, times(1)).getBeanProvider(eq(CacheResolver.class)); + verify(mockBeanProvider, times(1)).orderedStream(); + } + } + + @Test + public void getObjectReturnsProxy() { + + CacheResolver mockCacheResolver = mock(CacheResolver.class); + + SmartCacheResolverFactoryBean factoryBean = spy(new SmartCacheResolverFactoryBean()); + + CacheResolver cacheResolver = factoryBean.getObject(); + + assertThat(cacheResolver).isNotNull(); + assertThat(cacheResolver).isInstanceOf(CacheResolverProxy.class); + assertThat(((CacheResolverProxy) cacheResolver).getCacheResolver().orElse(null)).isNull(); + assertThat(((CacheResolverProxy) cacheResolver).getCacheResolverFactoryBean()).isSameAs(factoryBean); + + doReturn(mockCacheResolver).when(factoryBean).getCacheResolver(); + + assertThat(((CacheResolverProxy) cacheResolver).getCacheResolver().orElse(null)) + .isEqualTo(mockCacheResolver); + } + + @Test + public void getObjectTypeReturnsCacheResolverClassType() { + + SmartCacheResolverFactoryBean factoryBean = spy(new SmartCacheResolverFactoryBean()); + + doReturn(null).when(factoryBean).getCacheResolver(); + + assertThat(factoryBean.getObjectType()).isEqualTo(CacheResolver.class); + + verify(factoryBean, never()).getObject(); + } + + @Test + public void getObjectTypeReturnsCacheResolverInstanceType() { + + CacheResolver mockCacheResolver = mock(CacheResolver.class); + + SmartCacheResolverFactoryBean factoryBean = spy(new SmartCacheResolverFactoryBean()); + + doReturn(mockCacheResolver).when(factoryBean).getCacheResolver(); + + assertThat(factoryBean.getObjectType()).isEqualTo(mockCacheResolver.getClass()); + + verify(factoryBean, never()).getObject(); + } + + @Test + public void newBeanFactoryCacheResolverWithCacheBeanName() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setBeanFactory(mockBeanFactory); + factoryBean.setCacheBeanName("TestCacheBeanName"); + + assertThat(factoryBean.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(factoryBean.getCacheBeanName().orElse(null)).isEqualTo("TestCacheBeanName"); + + CacheResolver beanFactoryCacheResolver = factoryBean.newBeanFactoryCacheResolver(); + + assertThat(beanFactoryCacheResolver).isInstanceOf(BeanFactoryCacheResolver.class); + assertThat(((BeanFactoryCacheResolver) beanFactoryCacheResolver).getCacheBeanName().orElse(null)) + .isEqualTo("TestCacheBeanName"); + + verifyNoInteractions(mockBeanFactory); + } + + @Test + public void newBeanFactoryCacheResolverWithNoCacheBeanName() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setBeanFactory(mockBeanFactory); + + assertThat(factoryBean.getBeanFactory()).isEqualTo(mockBeanFactory); + assertThat(factoryBean.getCacheBeanName().orElse(null)).isNull(); + + CacheResolver beanFactoryCacheResolver = factoryBean.newBeanFactoryCacheResolver(); + + assertThat(beanFactoryCacheResolver).isInstanceOf(BeanFactoryCacheResolver.class); + assertThat(((BeanFactoryCacheResolver) beanFactoryCacheResolver).getCacheBeanName().orElse(null)).isNull(); + + verifyNoInteractions(mockBeanFactory); + } + + @Test + public void newBeanFactoryCacheResolverWithNullBeanFactory() { + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + assertThat(factoryBean.getBeanFactory()).isNull(); + assertThat(factoryBean.newBeanFactoryCacheResolver()).isNull(); + } + + @Test + public void newClientCacheFactoryCacheResolverIsNotNull() { + assertThat(new SmartCacheResolverFactoryBean().newClientCacheFactoryCacheResolver()) + .isInstanceOf(ClientCacheFactoryCacheResolver.class); + } + + @Test + public void newPeerCacheFactoryCacheResolverIsNotNull() { + assertThat(new SmartCacheResolverFactoryBean().newPeerCacheFactoryCacheResolver()) + .isInstanceOf(CacheFactoryCacheResolver.class); + } + + @Test + public void newSingleCacheCacheResolverWithClientCache() { + + ClientCache mockClientCache = mock(ClientCache.class); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setCache(mockClientCache); + + assertThat(factoryBean.getCache().orElse(null)).isEqualTo(mockClientCache); + assertThat(factoryBean.newSingleCacheCacheResolver().getClass().getName()) + .startsWith(SingleCacheCacheResolver.class.getName()); + } + + @Test + public void newSingleCacheCacheResolverWithPeerCache() { + + Cache mockPeerCache = mock(Cache.class); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + factoryBean.setCache(mockPeerCache); + + assertThat(factoryBean.getCache().orElse(null)).isEqualTo(mockPeerCache); + assertThat(factoryBean.newSingleCacheCacheResolver().getClass().getName()) + .startsWith(SingleCacheCacheResolver.class.getName()); + } + + @Test + public void newSingleCacheCacheResolverWithNullCache() { + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + assertThat(factoryBean.getCache().orElse(null)).isNull(); + assertThat(factoryBean.newSingleCacheCacheResolver()).isNull(); + } + + @Test + public void setAndGetCache() { + + GemFireCache mockCache = mock(GemFireCache.class); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + assertThat(factoryBean.getCache().orElse(null)).isNull(); + + factoryBean.setCache(mockCache); + + assertThat(factoryBean.getCache().orElse(null)).isEqualTo(mockCache); + + factoryBean.setCache(null); + + assertThat(factoryBean.getCache().orElse(null)).isNull(); + } + + @Test + public void setAndGetCacheBeanName() { + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + assertThat(factoryBean.getCacheBeanName().orElse(null)).isNull(); + + factoryBean.setCacheBeanName("TestCache"); + + assertThat(factoryBean.getCacheBeanName().orElse(null)).isEqualTo("TestCache"); + + factoryBean.setCacheBeanName(" "); + + assertThat(factoryBean.getCacheBeanName().orElse(null)).isNull(); + + factoryBean.setCacheBeanName(""); + + assertThat(factoryBean.getCacheBeanName().orElse(null)).isNull(); + + factoryBean.setCacheBeanName(null); + + assertThat(factoryBean.getCacheBeanName().orElse(null)).isNull(); + } + + @Test + public void setAndGetCompositionStrategy() { + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.DEFAULT); + + factoryBean.setCompositionStrategy(CompositionStrategy.SPRING); + + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.SPRING); + + factoryBean.setCompositionStrategy(null); + + assertThat(factoryBean.getCompositionStrategy()).isEqualTo(CompositionStrategy.DEFAULT); + } + + @Test + public void setAndGetConfiguredCacheResolvers() { + + CacheResolver a = mock(CacheResolver.class); + CacheResolver b = mock(CacheResolver.class); + CacheResolver c = mock(CacheResolver.class); + + List cacheResolvers = Arrays.asList(a, null, b, c, null, null); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + assertThat(factoryBean.getConfiguredCacheResolvers()).isNull(); + + factoryBean.setConfiguredCacheResolvers(cacheResolvers); + + assertThat(factoryBean.getConfiguredCacheResolvers()).containsExactly(a, b, c); + } + + @Test + public void setAndGetConfiguredCacheResolversWithDifferentFactoryBeanCacheResolverReference() { + + SmartCacheResolverFactoryBean factoryBeanOne = new SmartCacheResolverFactoryBean(); + + CacheResolver a = mock(CacheResolver.class); + CacheResolver b = mock(CacheResolver.class); + CacheResolver c = factoryBeanOne.getObject(); + + SmartCacheResolverFactoryBean factoryBeanTwo = new SmartCacheResolverFactoryBean(); + + List cacheResolvers = Arrays.asList(a, b, c); + + factoryBeanTwo.setConfiguredCacheResolvers(cacheResolvers); + + assertThat(factoryBeanTwo.getConfiguredCacheResolvers()).containsExactly(a, b, c); + } + + @Test + public void setAndGetConfiguredCacheResolversWithSelfReference() { + + CacheResolver a = mock(CacheResolver.class); + CacheResolver b = mock(CacheResolver.class); + + SmartCacheResolverFactoryBean factoryBean = new SmartCacheResolverFactoryBean(); + + List cacheResolvers = Arrays.asList(a, b, factoryBean.getObject()); + + factoryBean.setConfiguredCacheResolvers(cacheResolvers); + + assertThat(factoryBean.getConfiguredCacheResolvers()).containsExactly(a, b); + } + + static class A implements CacheResolver, Ordered { + + @Override + public ClientCache resolve() { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public int getOrder() { + return 2; + } + } + + @Order(1) + static class B implements CacheResolver { + + static final Cache MOCK_CACHE = mock(Cache.class); + + @Override + public Cache resolve() { + return MOCK_CACHE; + } + } +}