From b4145b17647a7c2de842c3dea30fc567efa1abc3 Mon Sep 17 00:00:00 2001 From: John Blum Date: Thu, 16 Jul 2015 18:09:28 -0700 Subject: [PATCH] SGF-409 - Modify the GemfireDataSourcePostProcessor (basis for ) to not assume a GemFire Server was configured and bootstrapped with Spring and subsequently that the SDG ListRegionsOnServerFunction was registered. Adding unit tests for the GemfireDataSourcePostProcessor class. (cherry picked from commit 0055a79a5d0119c0813f86a6504427d3d2d885a6) Signed-off-by: John Blum --- .../GemfireDataSourcePostProcessor.java | 40 +-- .../GemfireDataSourcePostProcessorTest.java | 251 ++++++++++++++++++ 2 files changed, 275 insertions(+), 16 deletions(-) create mode 100644 src/test/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessorTest.java diff --git a/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java b/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java index 50c987e5..6b9082ba 100644 --- a/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java +++ b/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java @@ -27,6 +27,7 @@ import com.gemstone.gemfire.cache.Region; import com.gemstone.gemfire.cache.client.ClientCache; import com.gemstone.gemfire.cache.client.ClientRegionFactory; import com.gemstone.gemfire.cache.client.ClientRegionShortcut; +import com.gemstone.gemfire.cache.execute.Function; import com.gemstone.gemfire.management.internal.cli.functions.ListFunctionFunction; /** @@ -38,10 +39,18 @@ import com.gemstone.gemfire.management.internal.cli.functions.ListFunctionFuncti */ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor { - private static Log logger = LogFactory.getLog(GemfireDataSourcePostProcessor.class); + protected final Log logger = LogFactory.getLog(getClass()); private final ClientCache cache; + /** + * Constructs an instance of the GemfireDataSourcePostProcessor BeanFactoryPostProcessor class initialized + * with the specified GemFire ClientCache instance for creating client PROXY Regions for all data Regions + * configured in the GemFire cluster. + * + * @param cache the GemFire ClientCache instance. + * @see com.gemstone.gemfire.cache.client.ClientCache + */ public GemfireDataSourcePostProcessor(final ClientCache cache) { this.cache = cache; } @@ -53,19 +62,14 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (isFunctionAvailable(ListRegionsOnServerFunction.ID)) { - createClientRegions(beanFactory); + createClientProxyRegions(beanFactory); } } // TODO what happens when the GemFire cluster contains a mix of "pure" GemFire Servers (non-Spring configured) // as well as Spring configured/bootstrapped GemFire Servers? boolean isFunctionAvailable(String targetFunctionId) { - GemfireOnServersFunctionTemplate functionTemplate = new GemfireOnServersFunctionTemplate(cache); - - Iterable functionIds = CollectionUtils.nullSafeIterable( - functionTemplate.>executeAndExtract(new ListFunctionFunction())); - - for (String functionId : functionIds) { + for (String functionId : this.execute(new ListFunctionFunction())) { if (functionId.equals(targetFunctionId)) { return true; } @@ -75,11 +79,8 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor } /* (non-Javadoc) */ - void createClientRegions(ConfigurableListableBeanFactory beanFactory) { - GemfireOnServersFunctionTemplate functionTemplate = new GemfireOnServersFunctionTemplate(cache); - - Iterable regionNames = CollectionUtils.nullSafeIterable( - functionTemplate.>executeAndExtract(new ListRegionsOnServerFunction())); + void createClientProxyRegions(ConfigurableListableBeanFactory beanFactory) { + Iterable regionNames = execute(new ListRegionsOnServerFunction()); if (regionNames.iterator().hasNext()) { ClientRegionFactory clientRegionFactory = cache.createClientRegionFactory(ClientRegionShortcut.PROXY); @@ -91,8 +92,8 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor Object existingBean = beanFactory.getBean(regionName); Assert.isTrue(existingBean instanceof Region, String.format( - "Cannot create a client Region bean named '%1$s'. A bean with this name of type '%2$s' already exists.", - regionName, ObjectUtils.nullSafeClassName(existingBean))); + "Cannot create a client PROXY Region bean named '%1$s'. A bean with this name of type '%2$s' already exists.", + regionName, ObjectUtils.nullSafeClassName(existingBean))); createRegion = false; } @@ -108,6 +109,14 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor } } + /* (non-Javadoc) */ + Iterable execute(Function gemfireFunction, Object... arguments) { + GemfireOnServersFunctionTemplate functionTemplate = new GemfireOnServersFunctionTemplate(cache); + + return CollectionUtils.nullSafeIterable( + functionTemplate.>executeAndExtract(gemfireFunction, arguments)); + } + /* (non-Javadoc) */ private void log(String message, Object... arguments) { if (logger.isDebugEnabled()) { @@ -116,4 +125,3 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor } } - diff --git a/src/test/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessorTest.java b/src/test/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessorTest.java new file mode 100644 index 00000000..32bed19a --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessorTest.java @@ -0,0 +1,251 @@ +/* + * Copyright 2010-2013 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.gemfire.client; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doAnswer; +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.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.client.ClientCache; +import com.gemstone.gemfire.cache.client.ClientRegionFactory; +import com.gemstone.gemfire.cache.client.ClientRegionShortcut; +import com.gemstone.gemfire.cache.execute.Function; + +/** + * The GemfireDataSourcePostProcessor class is a test suite of test cases testing the contract and functionality + * of the GemfireDataSourcePostProcessor class, which is responsible for creating client PROXY Regions + * for all data Regions on servers in a GemFire cluster, providing the ListRegion + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory + * @see org.springframework.data.gemfire.client.GemfireDataSourcePostProcessor + * @see com.gemstone.gemfire.cache.Region + * @see com.gemstone.gemfire.cache.client.ClientCache + * @see com.gemstone.gemfire.cache.client.ClientRegionFactory + * @see com.gemstone.gemfire.cache.execute.Function + * @since 1.7.0 + */ +public class GemfireDataSourcePostProcessorTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(null) { + @Override @SuppressWarnings("unchecked") Iterable execute(Function gemfireFunction, Object... arguments) { + return (Iterable) Arrays.asList("FunctionOne", "FunctionTwo"); + } + }; + + @Test + public void postProcessBeanFactoryWhenListRegionsOnServerFunctionIsAvailable() { + final AtomicBoolean createClientProxyRegionsCalled = new AtomicBoolean(false); + + final ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, + "MockStringBeanFactory"); + + GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(null) { + @Override boolean isFunctionAvailable(String targetFunctionId) { + return true; + } + + @Override void createClientProxyRegions(final ConfigurableListableBeanFactory beanFactory) { + assertThat(beanFactory, is(sameInstance(mockBeanFactory))); + createClientProxyRegionsCalled.compareAndSet(false, true); + } + }; + + postProcessor.postProcessBeanFactory(mockBeanFactory); + + assertThat(createClientProxyRegionsCalled.get(), is(true)); + } + + @Test + public void postProcessBeanFactoryWhenListRegionsOnServerFunctionIsNotAvailable() { + final AtomicBoolean createClientProxyRegionsCalled = new AtomicBoolean(false); + + final ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, + "MockStringBeanFactory"); + + GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(null) { + @Override boolean isFunctionAvailable(String targetFunctionId) { + return false; + } + + @Override void createClientProxyRegions(final ConfigurableListableBeanFactory beanFactory) { + assertThat(beanFactory, is(sameInstance(mockBeanFactory))); + createClientProxyRegionsCalled.compareAndSet(false, true); + } + }; + + postProcessor.postProcessBeanFactory(mockBeanFactory); + + assertThat(createClientProxyRegionsCalled.get(), is(false)); + } + + @Test + public void isFunctionAvailableWhenFunctionIsAvailable() { + assertThat(postProcessor.isFunctionAvailable("FunctionOne"), is(true)); + } + + @Test + public void isFunctionAvailableWhenFunctionIsNotAvailable() { + assertThat(postProcessor.isFunctionAvailable("functionOne"), is(false)); + assertThat(postProcessor.isFunctionAvailable("Functionone"), is(false)); + assertThat(postProcessor.isFunctionAvailable("FuncOne"), is(false)); + assertThat(postProcessor.isFunctionAvailable("Function1"), is(false)); + assertThat(postProcessor.isFunctionAvailable("FunctionUno"), is(false)); + } + + @Test + @SuppressWarnings("unchecked") + public void createClientProxyRegions() { + ClientCache mockClientCache = mock(ClientCache.class, "MockGemFireClientCache"); + + ClientRegionFactory mockClientRegionFactory = mock(ClientRegionFactory.class, "MockGemFireClientRegionFactory"); + + when(mockClientCache.createClientRegionFactory(eq(ClientRegionShortcut.PROXY))).thenReturn( + mockClientRegionFactory); + + Region mockRegionOne = mock(Region.class, "MockGemFireRegionOne"); + Region mockRegionTwo = mock(Region.class, "MockGemFireRegionTwo"); + + final Map> regionMap = new HashMap>(2); + + regionMap.put("RegionOne", mockRegionOne); + regionMap.put("RegionTwo", mockRegionTwo); + + doAnswer(new Answer>() { + @Override public Region answer(final InvocationOnMock invocation) throws Throwable { + String regionName = invocation.getArgumentAt(0, String.class); + assertThat(regionMap.containsKey(regionName), is(true)); + return regionMap.get(regionName); + } + }).when(mockClientRegionFactory).create(any(String.class)); + + ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, "SpringBeanFactory"); + + when(mockBeanFactory.containsBean(any(String.class))).thenReturn(false); + + GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(mockClientCache) { + @Override Iterable execute(final Function gemfireFunction, final Object... arguments) { + return (Iterable) Arrays.asList("RegionOne", "RegionTwo"); + } + }; + + postProcessor.createClientProxyRegions(mockBeanFactory); + + verify(mockClientCache, times(1)).createClientRegionFactory(eq(ClientRegionShortcut.PROXY)); + verify(mockClientRegionFactory, times(1)).create(eq("RegionOne")); + verify(mockClientRegionFactory, times(1)).create(eq("RegionTwo")); + verify(mockBeanFactory, times(1)).registerSingleton(eq("RegionOne"), same(mockRegionOne)); + verify(mockBeanFactory, times(1)).registerSingleton(eq("RegionTwo"), same(mockRegionTwo)); + } + + @Test + @SuppressWarnings("unchecked") + public void createClientProxyRegionWhenRegionBeanExists() { + ClientCache mockClientCache = mock(ClientCache.class, "MockGemFireClientCache"); + + ClientRegionFactory mockClientRegionFactory = mock(ClientRegionFactory.class, "MockGemFireClientRegionFactory"); + + when(mockClientCache.createClientRegionFactory(eq(ClientRegionShortcut.PROXY))).thenReturn( + mockClientRegionFactory); + + Region mockRegion = mock(Region.class, "MockGemFireRegion"); + + ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, "SpringBeanFactory"); + + when(mockBeanFactory.containsBean(any(String.class))).thenReturn(true); + when(mockBeanFactory.getBean(eq("Example"))).thenReturn(mockRegion); + + GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(mockClientCache) { + @Override Iterable execute(final Function gemfireFunction, final Object... arguments) { + return (Iterable) Collections.singletonList("Example"); + } + }; + + postProcessor.createClientProxyRegions(mockBeanFactory); + + verify(mockClientCache, times(1)).createClientRegionFactory(eq(ClientRegionShortcut.PROXY)); + verify(mockClientRegionFactory, never()).create(any(String.class)); + verify(mockBeanFactory, never()).registerSingleton(any(String.class), any(Region.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void createClientProxyRegionWhenBeanOfDifferentTypeWithSameNameRegistered() { + ClientCache mockClientCache = mock(ClientCache.class, "MockGemFireClientCache"); + + ClientRegionFactory mockClientRegionFactory = mock(ClientRegionFactory.class, "MockGemFireClientRegionFactory"); + + when(mockClientCache.createClientRegionFactory(eq(ClientRegionShortcut.PROXY))).thenReturn( + mockClientRegionFactory); + + ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, "SpringBeanFactory"); + + when(mockBeanFactory.containsBean(any(String.class))).thenReturn(true); + when(mockBeanFactory.getBean(eq("Example"))).thenReturn(new Object()); + + GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(mockClientCache) { + @Override Iterable execute(final Function gemfireFunction, final Object... arguments) { + return (Iterable) Collections.singletonList("Example"); + } + }; + + try { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectCause(is(nullValue(Throwable.class))); + expectedException.expectMessage(is(equalTo(String.format( + "Cannot create a client PROXY Region bean named '%1$s'. A bean with this name of type '%2$s' already exists.", + "Example", Object.class.getName())))); + postProcessor.createClientProxyRegions(mockBeanFactory); + } + finally { + verify(mockClientCache, times(1)).createClientRegionFactory(eq(ClientRegionShortcut.PROXY)); + verify(mockClientRegionFactory, never()).create(any(String.class)); + verify(mockBeanFactory, never()).registerSingleton(any(String.class), any(Region.class)); + } + } + +}