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 6b9082ba..4bb4638d 100644 --- a/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java +++ b/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java @@ -12,6 +12,10 @@ */ package org.springframework.data.gemfire.client; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; @@ -19,7 +23,6 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.data.gemfire.function.execution.GemfireOnServersFunctionTemplate; import org.springframework.data.gemfire.support.ListRegionsOnServerFunction; -import org.springframework.data.gemfire.util.CollectionUtils; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -28,31 +31,42 @@ 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; +import com.gemstone.gemfire.management.internal.cli.domain.RegionInformation; +import com.gemstone.gemfire.management.internal.cli.functions.GetRegionsFunction; /** - * A {@link BeanFactoryPostProcessor} to register a Client Region bean, if necessary, for each Region accessible - * to a Gemfire data source. If the Region is already defined, the definition will not be overridden. + * A Spring {@link BeanFactoryPostProcessor} used to register a Client Region Proxy bean for each Region + * accessible to a GemFire DataSource. If the Region is already defined, the bean definition will not be overridden. * * @author David Turanski * @author John Blum + * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor + * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory + * @see org.springframework.data.gemfire.function.execution.GemfireOnServersFunctionTemplate + * @see org.springframework.data.gemfire.support.ListRegionsOnServerFunction + * @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 + * @see com.gemstone.gemfire.management.internal.cli.functions.GetRegionsFunction + * @since 1.2.0 */ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor { protected final Log logger = LogFactory.getLog(getClass()); - private final ClientCache cache; + private final ClientCache clientCache; /** * 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. + * @param clientCache the GemFire ClientCache instance. * @see com.gemstone.gemfire.cache.client.ClientCache */ - public GemfireDataSourcePostProcessor(final ClientCache cache) { - this.cache = cache; + public GemfireDataSourcePostProcessor(final ClientCache clientCache) { + this.clientCache = clientCache; } /* @@ -61,29 +75,54 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor */ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - if (isFunctionAvailable(ListRegionsOnServerFunction.ID)) { - 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) { - for (String functionId : this.execute(new ListFunctionFunction())) { - if (functionId.equals(targetFunctionId)) { - return true; - } - } - - return false; + createClientRegionProxies(beanFactory, regionNames()); } /* (non-Javadoc) */ - void createClientProxyRegions(ConfigurableListableBeanFactory beanFactory) { - Iterable regionNames = execute(new ListRegionsOnServerFunction()); + Iterable regionNames() { + try { + return execute(new ListRegionsOnServerFunction()); + } + catch (Exception ignore) { + try { + Object results = execute(new GetRegionsFunction()); + List regionNames = Collections.emptyList(); + if (containsRegionInformation(results)) { + Object[] resultsArray = (Object[]) results; + + regionNames = new ArrayList(resultsArray.length); + + for (Object result : resultsArray) { + regionNames.add(((RegionInformation) result).getName()); + } + } + + return regionNames; + } + catch (Exception e) { + log("Failed to determine the Regions available on the Server: %n%1$s", e); + return Collections.emptyList(); + } + } + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + T execute(Function gemfireFunction, Object... arguments) { + return new GemfireOnServersFunctionTemplate(clientCache).executeAndExtract(gemfireFunction, arguments); + } + + /* (non-Javadoc) */ + boolean containsRegionInformation(Object results) { + return (results instanceof Object[] && ((Object[]) results).length > 0 + && ((Object[]) results)[0] instanceof RegionInformation); + } + + /* (non-Javadoc) */ + void createClientRegionProxies(ConfigurableListableBeanFactory beanFactory, Iterable regionNames) { if (regionNames.iterator().hasNext()) { - ClientRegionFactory clientRegionFactory = cache.createClientRegionFactory(ClientRegionShortcut.PROXY); + ClientRegionFactory clientRegionFactory = clientCache.createClientRegionFactory(ClientRegionShortcut.PROXY); for (String regionName : regionNames) { boolean createRegion = true; @@ -110,15 +149,7 @@ 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) { + void log(String message, Object... arguments) { if (logger.isDebugEnabled()) { logger.debug(String.format(message, arguments)); } diff --git a/src/test/java/org/springframework/data/gemfire/client/GemFireDataSourceIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/client/GemFireDataSourceIntegrationTest.java new file mode 100644 index 00000000..ae6417a5 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/client/GemFireDataSourceIntegrationTest.java @@ -0,0 +1,147 @@ +/* + * 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.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.data.gemfire.fork.ServerProcess; +import org.springframework.data.gemfire.process.ProcessExecutor; +import org.springframework.data.gemfire.process.ProcessWrapper; +import org.springframework.data.gemfire.test.support.FileSystemUtils; +import org.springframework.data.gemfire.test.support.ThreadUtils; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.Assert; + +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.client.ClientCache; + +/** + * The GemFireDataSourceIntegrationTest class is a test suite of test cases testing the contract and functionality + * of the <gfe-data:datasource> element in the context of a GemFire cluster running both native, + * non-Spring configured GemFire Servers in a addition to Spring configured and bootstrapped GemFire Server. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.springframework.data.gemfire.client.GemfireDataSourcePostProcessor + * @see org.springframework.context.ApplicationContext + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @see com.gemstone.gemfire.cache.Region + * @see com.gemstone.gemfire.cache.client.ClientCache + * @since 1.7.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@SuppressWarnings({ "rawtypes", "unused"}) +public class GemFireDataSourceIntegrationTest { + + private static ProcessWrapper serverProcess; + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private ClientCache gemfireClientCache; + + @Resource(name = "LocalRegion") + private Region localRegion; + + @Resource(name = "ServerRegion") + private Region serverRegion; + + @Resource(name = "ExclusiveServerRegion") + private Region exclusiveServerRegion; + + @BeforeClass + public static void setupBeforeClass() throws IOException { + String serverName = "GemFireDataSourceSpringBasedServer"; + + File serverWorkingDirectory = new File(FileSystemUtils.WORKING_DIRECTORY, serverName.toLowerCase()); + + Assert.isTrue(serverWorkingDirectory.isDirectory() || serverWorkingDirectory.mkdirs()); + + List arguments = new ArrayList(); + + arguments.add(String.format("-Dgemfire.name=%1$s", serverName)); + arguments.add("/org/springframework/data/gemfire/client/GemFireDataSourceIntegrationTest-server-context.xml"); + + serverProcess = ProcessExecutor.launch(serverWorkingDirectory, ServerProcess.class, + arguments.toArray(new String[arguments.size()])); + + waitForProcessStart(TimeUnit.SECONDS.toMillis(20), serverProcess, ServerProcess.getServerProcessControlFilename()); + + System.out.println("Spring configured/bootstrapped GemFire Cache Server Process for ClientCache DataSource Test should be running..."); + } + + private static void waitForProcessStart(final long milliseconds, final ProcessWrapper process, final String processControlFilename) { + ThreadUtils.timedWait(milliseconds, TimeUnit.MILLISECONDS.toMillis(500), new ThreadUtils.WaitCondition() { + private File processControlFile = new File(process.getWorkingDirectory(), processControlFilename); + + @Override public boolean waiting() { + return !processControlFile.isFile(); + } + }); + } + + @AfterClass + public static void tearDown() { + serverProcess.shutdown(); + + if (Boolean.valueOf(System.getProperty("spring.gemfire.fork.clean", Boolean.TRUE.toString()))) { + org.springframework.util.FileSystemUtils.deleteRecursively(serverProcess.getWorkingDirectory()); + } + } + + protected void assertRegion(Region actualRegion, String expectedRegionName) { + assertThat(actualRegion, is(not(nullValue()))); + assertThat(actualRegion.getName(), is(equalTo(expectedRegionName))); + assertThat(actualRegion.getFullPath(), is(equalTo(String.format("%1$s%2$s", + Region.SEPARATOR, expectedRegionName)))); + assertThat(gemfireClientCache.getRegion(actualRegion.getFullPath()), is(sameInstance(actualRegion))); + assertThat(applicationContext.containsBean(expectedRegionName), is(true)); + assertThat(applicationContext.getBean(expectedRegionName, Region.class), is(sameInstance(actualRegion))); + } + + @Test + @SuppressWarnings("unchecked") + public void clientProxyRegionBeansExist() { + assertRegion(localRegion, "LocalRegion"); + assertRegion(serverRegion, "ServerRegion"); + assertRegion(exclusiveServerRegion, "ExclusiveServerRegion"); + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/client/GemFireDataSourceUsingNonSpringConfiguredGemFireServerIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/client/GemFireDataSourceUsingNonSpringConfiguredGemFireServerIntegrationTest.java new file mode 100644 index 00000000..2714aed1 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/client/GemFireDataSourceUsingNonSpringConfiguredGemFireServerIntegrationTest.java @@ -0,0 +1,144 @@ +/* + * 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.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.data.gemfire.fork.GemFireBasedServerProcess; +import org.springframework.data.gemfire.process.ProcessExecutor; +import org.springframework.data.gemfire.process.ProcessWrapper; +import org.springframework.data.gemfire.test.support.FileSystemUtils; +import org.springframework.data.gemfire.test.support.ThreadUtils; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.Assert; + +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.client.ClientCache; + +/** + * The GemFireDataSourceUsingNonSpringConfiguredGemFireServerIntegrationTest class is a test suite of test cases + * testing the contract and functionality of the GemfireDataSourcePostProcessor using the <gfe-data:datasource> + * element in Spring config to setup a GemFire ClientCache connecting to a native, non-Spring configured GemFire Server + * as the DataSource to assert that client Region Proxies are registered as Spring beans + * in the Spring ApplicationContext correctly. + * + * @author John Blum + * @see org.springframework.data.gemfire.client.GemfireDataSourcePostProcessor + * @since 1.7.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@SuppressWarnings({ "rawtypes", "unused"}) +public class GemFireDataSourceUsingNonSpringConfiguredGemFireServerIntegrationTest { + + private static ProcessWrapper serverProcess; + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private ClientCache gemfireClientCache; + + @Resource(name = "LocalRegion") + private Region localRegion; + + @Resource(name = "ServerRegion") + private Region serverRegion; + + @Resource(name = "AnotherServerRegion") + private Region anotherServerRegion; + + @BeforeClass + public static void setupBeforeClass() throws IOException { + String serverName = "GemFireDataSourceGemFireBasedServer"; + + File serverWorkingDirectory = new File(FileSystemUtils.WORKING_DIRECTORY, serverName.toLowerCase()); + + Assert.isTrue(serverWorkingDirectory.isDirectory() || serverWorkingDirectory.mkdirs()); + + List arguments = new ArrayList(5); + + arguments.add(String.format("-Dgemfire.name=%1$s", serverName)); + arguments.add(String.format("-Dgemfire.mcast-port=%1$s", "0")); + arguments.add(String.format("-Dgemfire.log-level=%1$s", "warning")); + arguments.add(String.format("-Dgemfire.cache-xml-file=%1$s", "gemfire-datasource-integration-test-cache.xml")); + + serverProcess = ProcessExecutor.launch(serverWorkingDirectory, GemFireBasedServerProcess.class, + arguments.toArray(new String[arguments.size()])); + + waitForProcessStart(TimeUnit.SECONDS.toMillis(20), serverProcess, GemFireBasedServerProcess.getServerProcessControlFilename()); + + System.out.println("GemFire-based Cache Server Process for ClientCache DataSource Test should be running and connected..."); + } + + private static void waitForProcessStart(final long milliseconds, final ProcessWrapper process, final String processControlFilename) { + ThreadUtils.timedWait(milliseconds, TimeUnit.MILLISECONDS.toMillis(500), new ThreadUtils.WaitCondition() { + private File processControlFile = new File(process.getWorkingDirectory(), processControlFilename); + + @Override public boolean waiting() { + return !processControlFile.isFile(); + } + }); + } + + @AfterClass + public static void tearDown() { + serverProcess.shutdown(); + + if (Boolean.valueOf(System.getProperty("spring.gemfire.fork.clean", Boolean.TRUE.toString()))) { + org.springframework.util.FileSystemUtils.deleteRecursively(serverProcess.getWorkingDirectory()); + } + } + + protected void assertRegion(Region actualRegion, String expectedRegionName) { + assertThat(actualRegion, is(not(nullValue()))); + assertThat(actualRegion.getName(), is(equalTo(expectedRegionName))); + assertThat(actualRegion.getFullPath(), is(equalTo(String.format("%1$s%2$s", + Region.SEPARATOR, expectedRegionName)))); + assertThat(gemfireClientCache.getRegion(actualRegion.getFullPath()), is(sameInstance(actualRegion))); + assertThat(applicationContext.containsBean(expectedRegionName), is(true)); + assertThat(applicationContext.getBean(expectedRegionName, Region.class), is(sameInstance(actualRegion))); + } + + @Test + @SuppressWarnings("unchecked") + public void clientProxyRegionBeansExist() { + assertRegion(localRegion, "LocalRegion"); + assertRegion(serverRegion, "ServerRegion"); + assertRegion(anotherServerRegion, "AnotherServerRegion"); + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessorTest.java b/src/test/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessorTest.java index 32bed19a..f4a9d99c 100644 --- a/src/test/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessorTest.java +++ b/src/test/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessorTest.java @@ -17,9 +17,12 @@ package org.springframework.data.gemfire.client; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; @@ -31,9 +34,11 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; @@ -43,12 +48,18 @@ import org.junit.rules.ExpectedException; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.data.gemfire.support.ListRegionsOnServerFunction; +import com.gemstone.gemfire.cache.DataPolicy; import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionAttributes; +import com.gemstone.gemfire.cache.Scope; 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.domain.RegionInformation; +import com.gemstone.gemfire.management.internal.cli.functions.GetRegionsFunction; /** * The GemfireDataSourcePostProcessor class is a test suite of test cases testing the contract and functionality @@ -63,7 +74,6 @@ import com.gemstone.gemfire.cache.execute.Function; * @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 { @@ -71,75 +81,180 @@ 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"); + protected RegionInformation newRegionInformation(Region region) { + return new RegionInformation(region, false); + } + + @SuppressWarnings("unchecked") + protected Region mockRegion(String name) { + Region mockRegion = mock(Region.class, name); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class, + String.format("%1$s-RegionAttributes", name)); + + when(mockRegion.getFullPath()).thenReturn(String.format("%1$s%2$s", Region.SEPARATOR, name)); + when(mockRegion.getName()).thenReturn(name); + when(mockRegion.getParentRegion()).thenReturn(null); + when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes); + when(mockRegionAttributes.getDataPolicy()).thenReturn(DataPolicy.PARTITION); + when(mockRegionAttributes.getScope()).thenReturn(Scope.DISTRIBUTED_ACK); + + return mockRegion; + } + + protected List asList(Iterable iterable) { + List list = new ArrayList(); + + if (iterable != null) { + for (T element : iterable) { + list.add(element); + } } - }; + + return list; + } @Test - public void postProcessBeanFactoryWhenListRegionsOnServerFunctionIsAvailable() { - final AtomicBoolean createClientProxyRegionsCalled = new AtomicBoolean(false); - - final ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, - "MockStringBeanFactory"); + public void postProcessBeanFactoryCallsCreateClientRegionProxiesWithRegionNames() { + final AtomicBoolean createClientRegionProxiesCalled = new AtomicBoolean(false); + final ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, "MockSpringBeanFactory"); + final List testRegionNames = Collections.singletonList("Test"); GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(null) { - @Override boolean isFunctionAvailable(String targetFunctionId) { - return true; + @Override Iterable regionNames() { + return testRegionNames; } - @Override void createClientProxyRegions(final ConfigurableListableBeanFactory beanFactory) { + @Override void createClientRegionProxies(ConfigurableListableBeanFactory beanFactory, Iterable regionNames) { assertThat(beanFactory, is(sameInstance(mockBeanFactory))); - createClientProxyRegionsCalled.compareAndSet(false, true); + assertSame(testRegionNames, regionNames); + createClientRegionProxiesCalled.compareAndSet(false, true); } }; postProcessor.postProcessBeanFactory(mockBeanFactory); - assertThat(createClientProxyRegionsCalled.get(), is(true)); + assertThat(createClientRegionProxiesCalled.get(), is(true)); } @Test - public void postProcessBeanFactoryWhenListRegionsOnServerFunctionIsNotAvailable() { - final AtomicBoolean createClientProxyRegionsCalled = new AtomicBoolean(false); - - final ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, - "MockStringBeanFactory"); + public void regionNamesWithListRegionsOnServerFunction() { + final List expectedRegionNames = Arrays.asList("ExampleOne", "ExampleTwo"); 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); + @Override @SuppressWarnings("unchecked") T execute(Function gemfireFunction, Object... arguments) { + assertThat(gemfireFunction, is(instanceOf(ListRegionsOnServerFunction.class))); + return (T) expectedRegionNames; } }; - postProcessor.postProcessBeanFactory(mockBeanFactory); + Iterable actualRegionNames = postProcessor.regionNames(); - assertThat(createClientProxyRegionsCalled.get(), is(false)); + assertSame(expectedRegionNames, actualRegionNames); } @Test - public void isFunctionAvailableWhenFunctionIsAvailable() { - assertThat(postProcessor.isFunctionAvailable("FunctionOne"), is(true)); + public void regionNamesWithGetRegionsFunction() { + final List expectedRegionNames = Arrays.asList("ExampleOne", "ExampleTwo"); + + GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(null) { + @Override @SuppressWarnings("unchecked") T execute(Function gemfireFunction, Object... arguments) { + if (gemfireFunction instanceof ListRegionsOnServerFunction) { + throw new RuntimeException("fail"); + } + else if (gemfireFunction instanceof GetRegionsFunction) { + return (T) Arrays.asList(newRegionInformation(mockRegion(expectedRegionNames.get(0))), + newRegionInformation(mockRegion(expectedRegionNames.get(1)))).toArray(); + } + + throw new IllegalArgumentException(String.format("GemFire Function (%1$s) with ID (%2$s) not registered", + gemfireFunction.getClass().getName(), gemfireFunction.getId())); + } + }; + + Iterable actualRegionNames = postProcessor.regionNames(); + + assertThat(asList(actualRegionNames).containsAll(expectedRegionNames), 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)); + public void regionNamesWithGetRegionsFunctionReturningNoResults() { + GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(null) { + @Override @SuppressWarnings("unchecked") T execute(Function gemfireFunction, Object... arguments) { + if (gemfireFunction instanceof ListRegionsOnServerFunction) { + throw new RuntimeException("fail"); + } + else if (gemfireFunction instanceof GetRegionsFunction) { + return null; + } + + throw new IllegalArgumentException(String.format("GemFire Function (%1$s) with ID (%2$s) not registered", + gemfireFunction.getClass().getName(), gemfireFunction.getId())); + } + }; + + Iterable actualRegionNames = postProcessor.regionNames(); + + assertThat(actualRegionNames, is(not(nullValue()))); + assertThat(actualRegionNames.iterator(), (is(not(nullValue())))); + assertThat(actualRegionNames.iterator().hasNext(), is(false)); + } + + @Test + public void regionNamesWithGetRegionsFunctionThrowingException() { + final AtomicBoolean logMethodCalled = new AtomicBoolean(false); + + GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(null) { + @Override @SuppressWarnings("unchecked") T execute(Function gemfireFunction, Object... arguments) { + throw new IllegalArgumentException(String.format("GemFire Function (%1$s) with ID (%2$s) not registered", + gemfireFunction.getClass().getName(), gemfireFunction.getId())); + } + + @Override void log(final String message, final Object... arguments) { + assertThat(message.startsWith("Failed to determine the Regions available on the Server:"), is(true)); + logMethodCalled.compareAndSet(false, true); + } + }; + + Iterable actualRegionNames = postProcessor.regionNames(); + + assertThat(actualRegionNames, is(not(nullValue()))); + assertThat(actualRegionNames.iterator(), (is(not(nullValue())))); + assertThat(actualRegionNames.iterator().hasNext(), is(false)); + assertThat(logMethodCalled.get(), is(true)); + } + + @Test + public void containsRegionInformationIsTrue() { + assertThat(new GemfireDataSourcePostProcessor(null).containsRegionInformation( + new Object[] { newRegionInformation(mockRegion("Example")) }), is(true)); + } + + @Test + public void containsRegionInformationWithListOfRegionInformationIsFalse() { + assertThat(new GemfireDataSourcePostProcessor(null).containsRegionInformation( + Arrays.asList(newRegionInformation(mockRegion("Example")))), is(false)); + } + + @Test + public void containsRegionInformationWithNonEmptyArrayContainingNonRegionInformationIsFalse() { + assertThat(new GemfireDataSourcePostProcessor(null).containsRegionInformation(new Object[] { "test" }), + is(false)); + } + + @Test + public void containsRegionInformationWithEmptyArrayIsFalse() { + assertThat(new GemfireDataSourcePostProcessor(null).containsRegionInformation(new Object[0]), is(false)); + } + + @Test + public void containsRegionInformationWithNullIsFalse() { + assertThat(new GemfireDataSourcePostProcessor(null).containsRegionInformation(null), is(false)); } @Test @SuppressWarnings("unchecked") - public void createClientProxyRegions() { + public void createClientRegionProxies() { ClientCache mockClientCache = mock(ClientCache.class, "MockGemFireClientCache"); ClientRegionFactory mockClientRegionFactory = mock(ClientRegionFactory.class, "MockGemFireClientRegionFactory"); @@ -163,17 +278,13 @@ public class GemfireDataSourcePostProcessorTest { } }).when(mockClientRegionFactory).create(any(String.class)); - ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, "SpringBeanFactory"); + ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, "MockSpringBeanFactory"); 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"); - } - }; + GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(mockClientCache); - postProcessor.createClientProxyRegions(mockBeanFactory); + postProcessor.createClientRegionProxies(mockBeanFactory, regionMap.keySet()); verify(mockClientCache, times(1)).createClientRegionFactory(eq(ClientRegionShortcut.PROXY)); verify(mockClientRegionFactory, times(1)).create(eq("RegionOne")); @@ -184,7 +295,7 @@ public class GemfireDataSourcePostProcessorTest { @Test @SuppressWarnings("unchecked") - public void createClientProxyRegionWhenRegionBeanExists() { + public void createClientRegionProxiesWhenRegionBeanExists() { ClientCache mockClientCache = mock(ClientCache.class, "MockGemFireClientCache"); ClientRegionFactory mockClientRegionFactory = mock(ClientRegionFactory.class, "MockGemFireClientRegionFactory"); @@ -194,18 +305,14 @@ public class GemfireDataSourcePostProcessorTest { Region mockRegion = mock(Region.class, "MockGemFireRegion"); - ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, "SpringBeanFactory"); + ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, "MockSpringBeanFactory"); 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"); - } - }; + GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(mockClientCache); - postProcessor.createClientProxyRegions(mockBeanFactory); + postProcessor.createClientRegionProxies(mockBeanFactory, Arrays.asList("Example")); verify(mockClientCache, times(1)).createClientRegionFactory(eq(ClientRegionShortcut.PROXY)); verify(mockClientRegionFactory, never()).create(any(String.class)); @@ -214,7 +321,7 @@ public class GemfireDataSourcePostProcessorTest { @Test @SuppressWarnings("unchecked") - public void createClientProxyRegionWhenBeanOfDifferentTypeWithSameNameRegistered() { + public void createClientRegionProxiesWhenBeanOfDifferentTypeWithSameNameAsRegionIsRegistered() { ClientCache mockClientCache = mock(ClientCache.class, "MockGemFireClientCache"); ClientRegionFactory mockClientRegionFactory = mock(ClientRegionFactory.class, "MockGemFireClientRegionFactory"); @@ -222,16 +329,12 @@ public class GemfireDataSourcePostProcessorTest { when(mockClientCache.createClientRegionFactory(eq(ClientRegionShortcut.PROXY))).thenReturn( mockClientRegionFactory); - ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, "SpringBeanFactory"); + ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, "MockSpringBeanFactory"); when(mockBeanFactory.containsBean(any(String.class))).thenReturn(true); - when(mockBeanFactory.getBean(eq("Example"))).thenReturn(new Object()); + when(mockBeanFactory.getBean(any(String.class))).thenReturn(new Object()); - GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(mockClientCache) { - @Override Iterable execute(final Function gemfireFunction, final Object... arguments) { - return (Iterable) Collections.singletonList("Example"); - } - }; + GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(mockClientCache); try { expectedException.expect(IllegalArgumentException.class); @@ -239,7 +342,7 @@ public class GemfireDataSourcePostProcessorTest { 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); + postProcessor.createClientRegionProxies(mockBeanFactory, Arrays.asList("Example")); } finally { verify(mockClientCache, times(1)).createClientRegionFactory(eq(ClientRegionShortcut.PROXY)); diff --git a/src/test/java/org/springframework/data/gemfire/fork/GemFireBasedServerProcess.java b/src/test/java/org/springframework/data/gemfire/fork/GemFireBasedServerProcess.java new file mode 100644 index 00000000..ed2a8bd3 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/fork/GemFireBasedServerProcess.java @@ -0,0 +1,90 @@ +/* + * 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.fork; + +import java.io.File; + +import org.springframework.data.gemfire.process.support.ProcessUtils; +import org.springframework.data.gemfire.test.support.FileSystemUtils; + +import com.gemstone.gemfire.distributed.ServerLauncher; +import com.gemstone.gemfire.distributed.internal.DistributionConfig; + +/** + * The GemFireBasedServerProcess class is a main Java class used to launch a GemFire Server + * using GemFire's ServerLauncher API. + * + * @author John Blum + * @see com.gemstone.gemfire.distributed.ServerLauncher + * @since 1.7.0 + */ +public class GemFireBasedServerProcess { + + protected static final String DEFAULT_GEMFIRE_MEMBER_NAME = "SpringDataGemFire-Server"; + protected static final String DEFAULT_HTTP_SERVICE_PORT = "0"; + protected static final String DEFAULT_LOG_LEVEL = "warning"; + protected static final String DEFAULT_USE_CLUSTER_CONFIGURATION = "false"; + + public static void main(final String[] args) throws Throwable { + runServer(args); + + registerShutdownHook(); + + ProcessUtils.writePid(new File(FileSystemUtils.WORKING_DIRECTORY, getServerProcessControlFilename()), + ProcessUtils.currentPid()); + + ProcessUtils.waitForStopSignal(); + } + + public static String getServerProcessControlFilename() { + return GemFireBasedServerProcess.class.getSimpleName().toLowerCase().concat(".pid"); + } + + private static ServerLauncher runServer(final String[] args) { + ServerLauncher serverLauncher = buildServerLauncher(args); + + // start the GemFire Server process... + serverLauncher.start(); + + return serverLauncher; + } + + private static ServerLauncher buildServerLauncher(final String[] args) { + return new ServerLauncher.Builder(args) + .setMemberName(System.getProperty("gemfire.name", DEFAULT_GEMFIRE_MEMBER_NAME)) + .setCommand(ServerLauncher.Command.START) + .setDisableDefaultServer(true) + .setRedirectOutput(false) + .set(DistributionConfig.HTTP_SERVICE_PORT_NAME, System.getProperty("spring.gemfire.http-service-port", + DEFAULT_HTTP_SERVICE_PORT)) + .set(DistributionConfig.JMX_MANAGER_NAME, String.valueOf(Boolean.TRUE)) + .set(DistributionConfig.JMX_MANAGER_START_NAME, String.valueOf(Boolean.FALSE)) + .set(DistributionConfig.LOG_LEVEL_NAME, System.getProperty("spring.gemfire.log-level", DEFAULT_LOG_LEVEL)) + .set(DistributionConfig.USE_CLUSTER_CONFIGURATION_NAME, System.getProperty("spring.gemfire.use-cluster-configuration", + DEFAULT_USE_CLUSTER_CONFIGURATION)) + .build(); + } + + private static void registerShutdownHook() { + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override public void run() { + ServerLauncher.getInstance().stop(); + } + })); + } + +} diff --git a/src/test/resources/gemfire-datasource-integration-test-cache.xml b/src/test/resources/gemfire-datasource-integration-test-cache.xml new file mode 100644 index 00000000..d7bc4c38 --- /dev/null +++ b/src/test/resources/gemfire-datasource-integration-test-cache.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/client/GemFireDataSourceIntegrationTest-context.xml b/src/test/resources/org/springframework/data/gemfire/client/GemFireDataSourceIntegrationTest-context.xml new file mode 100644 index 00000000..5525f8fa --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/client/GemFireDataSourceIntegrationTest-context.xml @@ -0,0 +1,31 @@ + + + + + localhost + 42082 + + + + + + + + + + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/client/GemFireDataSourceIntegrationTest-server-context.xml b/src/test/resources/org/springframework/data/gemfire/client/GemFireDataSourceIntegrationTest-server-context.xml new file mode 100644 index 00000000..e5aca411 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/client/GemFireDataSourceIntegrationTest-server-context.xml @@ -0,0 +1,36 @@ + + + + + localhost + 42082 + + + + + + GemFireDataSourceIntegrationTestServer + 0 + warning + + + + + + + + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/client/GemFireDataSourceUsingNonSpringConfiguredGemFireServerIntegrationTest-context.xml b/src/test/resources/org/springframework/data/gemfire/client/GemFireDataSourceUsingNonSpringConfiguredGemFireServerIntegrationTest-context.xml new file mode 100644 index 00000000..bff3c1f7 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/client/GemFireDataSourceUsingNonSpringConfiguredGemFireServerIntegrationTest-context.xml @@ -0,0 +1,29 @@ + + + + + localhost + 42082 + + + + + + + + + + +