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 aa401d6d..50c987e5 100644 --- a/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java +++ b/src/main/java/org/springframework/data/gemfire/client/GemfireDataSourcePostProcessor.java @@ -17,15 +17,17 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.data.gemfire.function.execution.GemfireFunctionOperations; 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; 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.management.internal.cli.functions.ListFunctionFunction; /** * A {@link BeanFactoryPostProcessor} to register a Client Region bean, if necessary, for each Region accessible @@ -44,47 +46,74 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor this.cache = cache; } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory) */ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - createClientRegions(beanFactory); + if (isFunctionAvailable(ListRegionsOnServerFunction.ID)) { + createClientRegions(beanFactory); + } } - private void createClientRegions(ConfigurableListableBeanFactory beanFactory) { - GemfireFunctionOperations functionTemplate = new GemfireOnServersFunctionTemplate(cache); + // 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 regionNames = functionTemplate.executeAndExtract(new ListRegionsOnServerFunction()); + Iterable functionIds = CollectionUtils.nullSafeIterable( + functionTemplate.>executeAndExtract(new ListFunctionFunction())); - ClientRegionFactory clientRegionFactory = null; - - if (regionNames != null && regionNames.iterator().hasNext()) { - clientRegionFactory = cache.createClientRegionFactory(ClientRegionShortcut.PROXY); + for (String functionId : functionIds) { + if (functionId.equals(targetFunctionId)) { + return true; + } } - for (String regionName : regionNames) { - boolean createRegion = true; + return false; + } - if (beanFactory.containsBean(regionName)) { - Object existingBean = beanFactory.getBean(regionName); - Assert.isTrue(existingBean instanceof Region, String.format( - "Cannot create a ClientRegion bean named '%1$s'. A bean with this name of type '%2$s' already exists.", - regionName, existingBean.getClass().getName())); - createRegion = false; - } + /* (non-Javadoc) */ + void createClientRegions(ConfigurableListableBeanFactory beanFactory) { + GemfireOnServersFunctionTemplate functionTemplate = new GemfireOnServersFunctionTemplate(cache); - if (createRegion) { - if (logger.isDebugEnabled()) { - logger.debug(String.format("Creating Client Region bean with name '%s'...", regionName)); + Iterable regionNames = CollectionUtils.nullSafeIterable( + functionTemplate.>executeAndExtract(new ListRegionsOnServerFunction())); + + if (regionNames.iterator().hasNext()) { + ClientRegionFactory clientRegionFactory = cache.createClientRegionFactory(ClientRegionShortcut.PROXY); + + for (String regionName : regionNames) { + boolean createRegion = true; + + if (beanFactory.containsBean(regionName)) { + 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))); + + createRegion = false; } - beanFactory.registerSingleton(regionName, clientRegionFactory.create(regionName)); - } else { - if (logger.isDebugEnabled()) { - logger.debug(String.format("A Region named '%s' is already defined.", regionName)); + + if (createRegion) { + log("Creating Region bean with name '%s'...", regionName); + beanFactory.registerSingleton(regionName, clientRegionFactory.create(regionName)); + } + else { + log("A Region with name '%s' is already defined.", regionName); } } } } + /* (non-Javadoc) */ + private void log(String message, Object... arguments) { + if (logger.isDebugEnabled()) { + logger.debug(String.format(message, arguments)); + } + } + } + diff --git a/src/main/java/org/springframework/data/gemfire/support/ListRegionsOnServerFunction.java b/src/main/java/org/springframework/data/gemfire/support/ListRegionsOnServerFunction.java index b447f6c8..e3d622b1 100644 --- a/src/main/java/org/springframework/data/gemfire/support/ListRegionsOnServerFunction.java +++ b/src/main/java/org/springframework/data/gemfire/support/ListRegionsOnServerFunction.java @@ -22,27 +22,40 @@ import com.gemstone.gemfire.cache.execute.Function; import com.gemstone.gemfire.cache.execute.FunctionContext; /** - * @author David Turanski + * ListRegionsOnServerFunction is a GemFire Function class that returns a List of names for all Regions + * defined in the GemFire cluster. * + * @author David Turanski + * @author John Blum + * @see com.gemstone.gemfire.cache.execute.Function */ @SuppressWarnings("serial") public class ListRegionsOnServerFunction implements Function { - /* (non-Javadoc) + public static final String ID = ListRegionsOnServerFunction.class.getName(); + + /* + * (non-Javadoc) * @see com.gemstone.gemfire.cache.execute.Function#execute(com.gemstone.gemfire.cache.execute.FunctionContext) */ @Override public void execute(FunctionContext functionContext) { - Cache cache = CacheFactory.getAnyInstance(); List regionNames = new ArrayList(); - for (Region region: cache.rootRegions()) { + + for (Region region : getCache().rootRegions()) { regionNames.add(region.getName()); } - + functionContext.getResultSender().lastResult(regionNames); } - /* (non-Javadoc) + /* (non-Javadoc) */ + Cache getCache() { + return CacheFactory.getAnyInstance(); + } + + /* + * (non-Javadoc) * @see com.gemstone.gemfire.cache.execute.Function#getId() */ @Override @@ -50,7 +63,8 @@ public class ListRegionsOnServerFunction implements Function { return this.getClass().getName(); } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see com.gemstone.gemfire.cache.execute.Function#hasResult() */ @Override @@ -58,7 +72,8 @@ public class ListRegionsOnServerFunction implements Function { return true; } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see com.gemstone.gemfire.cache.execute.Function#isHA() */ @Override @@ -66,7 +81,8 @@ public class ListRegionsOnServerFunction implements Function { return false; } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see com.gemstone.gemfire.cache.execute.Function#optimizeForWrite() */ @Override diff --git a/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java b/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java index 8e38e5a2..b0aaa55a 100644 --- a/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java @@ -18,6 +18,8 @@ package org.springframework.data.gemfire.util; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; /** * The CollectionUtils class is a utility class for working with Java Collections Framework and classes. @@ -32,7 +34,37 @@ import java.util.Collections; public abstract class CollectionUtils extends org.springframework.util.CollectionUtils { /** - * A null-safe operation returning the original Collection is non-null or an empty Collection + * A null-safe operation returning the original Iterable object if non-null or a default, empty Iterable + * implementation if null. + * + * @param the class type of the iterable elements. + * @param iterable the Iterable object evaluated for a null reference. + * @return the Iterable object if not null or a default, empty Iterable implementation otherwise. + * @see java.lang.Iterable + * @see java.util.Iterator + */ + public static Iterable nullSafeIterable(Iterable iterable) { + return (iterable != null ? iterable : new Iterable() { + @Override public Iterator iterator() { + return new Iterator() { + @Override public boolean hasNext() { + return false; + } + + @Override public T next() { + throw new NoSuchElementException("no elements in this Iterator"); + } + + @Override public void remove() { + throw new UnsupportedOperationException("operation not supported"); + } + }; + } + }); + } + + /** + * A null-safe operation returning the original Collection if non-null or an empty Collection * (implemented with List) if null. * * @param the element class type of the Collection. diff --git a/src/test/java/org/springframework/data/gemfire/support/ListRegionsOnServerFunctionTest.java b/src/test/java/org/springframework/data/gemfire/support/ListRegionsOnServerFunctionTest.java new file mode 100644 index 00000000..47b156e8 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/ListRegionsOnServerFunctionTest.java @@ -0,0 +1,169 @@ +/* + * 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.support; + +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.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +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.HashSet; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.execute.FunctionContext; +import com.gemstone.gemfire.cache.execute.ResultSender; + +/** + * The ListRegionsOnServerFunctionTest class is a test suite of test cases testing the contract and functionality + * of the ListRegionsOnServerFunction GemFire Function class. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.support.ListRegionsOnServerFunction + * @since 1.7.0 + */ +public class ListRegionsOnServerFunctionTest { + + private ListRegionsOnServerFunction function = new ListRegionsOnServerFunction(); + + @Test + @SuppressWarnings("unchecked") + public void executeReturnsRootRegionNames() { + final Cache mockCache = mock(Cache.class, "MockGemFireCache"); + + Region mockRegionOne = mock(Region.class, "MockGemFireRegionOne"); + Region mockRegionTwo = mock(Region.class, "MockGemFireRegionTwo"); + Region mockRegionThree = mock(Region.class, "MockGemFireRegionThree"); + + FunctionContext mockFunctionContext = mock(FunctionContext.class, "MockGemFireFunctionContext"); + + ResultSender mockResultSender = mock(ResultSender.class, "MockGemFireResultSender"); + + when(mockCache.rootRegions()).thenReturn(new HashSet>( + Arrays.>asList(mockRegionOne, mockRegionTwo, mockRegionThree))); + when(mockRegionOne.getName()).thenReturn("One"); + when(mockRegionTwo.getName()).thenReturn("Two"); + when(mockRegionThree.getName()).thenReturn("Three"); + when(mockFunctionContext.getResultSender()).thenReturn(mockResultSender); + + final AtomicReference> regionNames = new AtomicReference>(null); + + doAnswer(new Answer() { + @Override + public Void answer(final InvocationOnMock invocation) throws Throwable { + regionNames.compareAndSet(null, invocation.getArgumentAt(0, List.class)); + return null; + } + }).when(mockResultSender).lastResult(any(List.class)); + + ListRegionsOnServerFunction function = new ListRegionsOnServerFunction() { + @Override Cache getCache() { + return mockCache; + } + }; + + function.execute(mockFunctionContext); + + List actualRegionNames = regionNames.get(); + + assertThat(actualRegionNames, is(not(nullValue()))); + assertThat(actualRegionNames.isEmpty(), is(false)); + assertThat(actualRegionNames.size(), is(equalTo(3))); + assertThat(actualRegionNames.containsAll(Arrays.asList("One", "Two", "Three")), is(true)); + + verify(mockCache, times(1)).rootRegions(); + verify(mockRegionOne, times(1)).getName(); + verify(mockRegionTwo, times(1)).getName(); + verify(mockRegionThree, times(1)).getName(); + verify(mockFunctionContext, times(1)).getResultSender(); + verify(mockResultSender, times(1)).lastResult(any(List.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void executeWithNoRegions() { + final Cache mockCache = mock(Cache.class, "MockGemFireCache"); + + FunctionContext mockFunctionContext = mock(FunctionContext.class, "MockGemFireFunctionContext"); + + ResultSender mockResultSender = mock(ResultSender.class, "MockGemFireResultSender"); + + when(mockCache.rootRegions()).thenReturn(Collections.>emptySet()); + when(mockFunctionContext.getResultSender()).thenReturn(mockResultSender); + + final AtomicReference> regionNames = new AtomicReference>(null); + + doAnswer(new Answer() { + @Override + public Void answer(final InvocationOnMock invocation) throws Throwable { + regionNames.compareAndSet(null, invocation.getArgumentAt(0, List.class)); + return null; + } + }).when(mockResultSender).lastResult(any(List.class)); + + ListRegionsOnServerFunction function = new ListRegionsOnServerFunction() { + @Override Cache getCache() { + return mockCache; + } + }; + + function.execute(mockFunctionContext); + + List actualRegionNames = regionNames.get(); + + assertThat(actualRegionNames, is(not(nullValue()))); + assertThat(actualRegionNames.isEmpty(), is(true)); + + verify(mockCache, times(1)).rootRegions(); + verify(mockFunctionContext, times(1)).getResultSender(); + verify(mockResultSender, times(1)).lastResult(any(List.class)); + } + + @Test + public void getIdIsFullyQualifiedClassName() { + assertThat(function.getId(), is(equalTo(ListRegionsOnServerFunction.class.getName()))); + } + + @Test + public void hasResultIsTrue() { + assertThat(function.hasResult(), is(true)); + } + + @Test + public void isHighAvailabilityAndOptimizeForWriteAreFalse() { + assertThat(function.isHA(), is(false)); + assertThat(function.optimizeForWrite(), is(false)); + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsTest.java b/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsTest.java index c10dc3dd..4aee2368 100644 --- a/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsTest.java +++ b/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsTest.java @@ -16,13 +16,21 @@ package org.springframework.data.gemfire.util; +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.assertNotNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import java.util.Collection; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import org.junit.Test; @@ -32,6 +40,7 @@ import org.junit.Test; * * @author John Blum * @see java.util.Collection + * @see java.util.Iterator * @see org.junit.Test * @see org.mockito.Mockito * @see org.springframework.data.gemfire.util.CollectionUtils @@ -40,13 +49,52 @@ import org.junit.Test; public class CollectionUtilsTest { @Test - public void testNullSafeCollectionWithNonNullCollection() { + @SuppressWarnings("unchecked") + public void nullSafeIterableWithNonNullIterable() { + Iterable mockIterable = mock(Iterable.class); + assertThat(CollectionUtils.nullSafeIterable(mockIterable), is(sameInstance(mockIterable))); + } + + @Test + public void nullSafeIterableWithNullIterable() { + Iterable iterable = CollectionUtils.nullSafeIterable(null); + assertThat(iterable, is(not(nullValue()))); + assertThat(iterable.iterator(), is(not(nullValue()))); + } + + @Test(expected = UnsupportedOperationException.class) + public void nullSafeIterableIterator() { + Iterator iterator = CollectionUtils.nullSafeIterable(null).iterator(); + + assertThat(iterator, is(not(nullValue()))); + assertThat(iterator.hasNext(), is(equalTo(false))); + + try { + iterator.next(); + } + catch (NoSuchElementException ignore) { + assertThat(ignore.getMessage(), is(equalTo("no elements in this Iterator"))); + assertThat(ignore.getCause(), is(nullValue())); + + try { + iterator.remove(); + } + catch (UnsupportedOperationException expected) { + assertThat(expected.getMessage(), is(equalTo("operation not supported"))); + assertThat(expected.getCause(), is(nullValue())); + throw expected; + } + } + } + + @Test + public void nullSafeCollectionWithNonNullCollection() { List mockList = mock(List.class); assertSame(mockList, CollectionUtils.nullSafeCollection(mockList)); } @Test - public void testNullSafeCollectionWithNullCollection() { + public void nullSafeCollectionWithNullCollection() { Collection collection = CollectionUtils.nullSafeCollection(null); assertNotNull(collection);