SGF-441 - Fix possible CacheClosedException in ClientCacheFactoryBean onApplicationEvent(:ContextRefreshedEvent) when the ClientCache initialization is lazy.

This commit is contained in:
John Blum
2015-10-17 23:17:31 -07:00
parent af343a45fa
commit f645e7d1a4
12 changed files with 207 additions and 39 deletions

View File

@@ -21,6 +21,8 @@ import java.util.concurrent.ConcurrentMap;
import org.springframework.data.gemfire.util.DistributedSystemUtils;
import org.springframework.util.ClassUtils;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.CacheClosedException;
import com.gemstone.gemfire.cache.CacheFactory;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.client.ClientCache;
@@ -34,6 +36,12 @@ import com.gemstone.gemfire.management.internal.cli.util.spring.StringUtils;
*
* @author John Blum
* @see org.springframework.data.gemfire.util.DistributedSystemUtils
* @see com.gemstone.gemfire.cache.Cache
* @see com.gemstone.gemfire.cache.CacheFactory
* @see com.gemstone.gemfire.cache.Region
* @see com.gemstone.gemfire.cache.client.ClientCache
* @see com.gemstone.gemfire.cache.client.ClientCacheFactory
* @see com.gemstone.gemfire.distributed.DistributedSystem
* @since 1.3.3
*/
@SuppressWarnings("unused")
@@ -42,7 +50,7 @@ public abstract class GemfireUtils extends DistributedSystemUtils {
public final static String GEMFIRE_VERSION = CacheFactory.getVersion();
public static boolean isDurable(ClientCache clientCache) {
DistributedSystem distributedSystem = clientCache.getDistributedSystem();
DistributedSystem distributedSystem = getDistributedSystem(clientCache);
// NOTE technically the following code snippet would be more useful/valuable but is not "testable"!
//((InternalDistributedSystem) distributedSystem).getConfig().getDurableClientId();
@@ -71,7 +79,25 @@ public abstract class GemfireUtils extends DistributedSystemUtils {
}
}
public static boolean isGemfireVersionGreaterThanEqual(double expectedVersion) {
public static Cache getCache() {
try {
return CacheFactory.getAnyInstance();
}
catch (CacheClosedException ignore) {
return null;
}
}
public static ClientCache getClientCache() {
try {
return ClientCacheFactory.getAnyInstance();
}
catch (CacheClosedException ignore) {
return null;
}
}
public static boolean isGemfireVersionGreaterThanEqualTo(double expectedVersion) {
double actualVersion = Double.parseDouble(GEMFIRE_VERSION.substring(0, 3));
return actualVersion >= expectedVersion;
}
@@ -90,7 +116,7 @@ public abstract class GemfireUtils extends DistributedSystemUtils {
public static boolean isGemfireVersion7OrAbove() {
try {
return isGemfireVersionGreaterThanEqual(7.0);
return isGemfireVersionGreaterThanEqualTo(7.0);
}
catch (NumberFormatException e) {
// NOTE the com.gemstone.gemfire.distributed.ServerLauncher class only exists in GemFire v 7.0.x or above...
@@ -101,7 +127,7 @@ public abstract class GemfireUtils extends DistributedSystemUtils {
public static boolean isGemfireVersion8OrAbove() {
try {
return isGemfireVersionGreaterThanEqual(8.0);
return isGemfireVersionGreaterThanEqualTo(8.0);
}
catch (NumberFormatException e) {
// NOTE the com.gemstone.gemfire.management.internal.web.domain.LinkIndex class only exists

View File

@@ -394,7 +394,12 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat
return Boolean.TRUE.equals(readyForEvents);
}
else {
return GemfireUtils.isDurable((ClientCache) fetchCache());
try {
return GemfireUtils.isDurable((ClientCache) fetchCache());
}
catch (Throwable ignore) {
return false;
}
}
}

View File

@@ -20,6 +20,7 @@ import java.util.Properties;
import org.springframework.util.Assert;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.server.CacheServer;
import com.gemstone.gemfire.distributed.DistributedSystem;
import com.gemstone.gemfire.distributed.internal.DistributionConfig;
@@ -31,6 +32,7 @@ import com.gemstone.gemfire.management.internal.cli.util.spring.StringUtils;
* DistributedSystemUtils is an abstract utility class for working with the GemFire DistributedSystem.
*
* @author John Blum
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.distributed.DistributedSystem
* @since 1.7.0
*/
@@ -43,6 +45,7 @@ public abstract class DistributedSystemUtils {
public static final String DURABLE_CLIENT_ID_PROPERTY_NAME = DistributionConfig.DURABLE_CLIENT_ID_NAME;
public static final String DURABLE_CLIENT_TIMEOUT_PROPERTY_NAME = DistributionConfig.DURABLE_CLIENT_TIMEOUT_NAME;
/* (non-Javadoc) */
public static Properties configureDurableClient(Properties gemfireProperties, String durableClientId, Integer durableClientTimeout) {
if (StringUtils.hasText(durableClientId)) {
Assert.notNull(gemfireProperties, "gemfireProperties must not be null");
@@ -57,17 +60,26 @@ public abstract class DistributedSystemUtils {
return gemfireProperties;
}
/* (non-Javadoc) */
public static boolean isConnected(DistributedSystem distributedSystem) {
return (distributedSystem != null && distributedSystem.isConnected());
}
/* (non-Javadoc) */
public static boolean isNotConnected(DistributedSystem distributedSystem) {
return !isConnected(distributedSystem);
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
public static <T extends DistributedSystem> T getDistributedSystem() {
return (T) InternalDistributedSystem.getAnyInstance();
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
public static <T extends DistributedSystem> T getDistributedSystem(GemFireCache gemfireCache) {
return (gemfireCache != null ? (T) gemfireCache.getDistributedSystem() : null);
}
}

View File

@@ -38,6 +38,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.gemfire.test.AbstractMockerySupport;
import org.springframework.data.gemfire.test.GemfireTestApplicationContextInitializer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -66,13 +67,14 @@ import com.gemstone.gemfire.cache.query.TypeMismatchException;
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.springframework.data.gemfire.GemfireTemplate
* @see org.springframework.data.gemfire.test.AbstractMockerySupport
* @see org.springframework.data.gemfire.test.GemfireTestApplicationContextInitializer
* @see org.springframework.test.context.ContextConfiguration
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="basic-template.xml", initializers=GemfireTestApplicationContextInitializer.class)
@ContextConfiguration(locations = "basic-template.xml", initializers = GemfireTestApplicationContextInitializer.class)
@SuppressWarnings("unused")
public class GemfireTemplateTest {
public class GemfireTemplateTest extends AbstractMockerySupport {
private static final String MULTI_QUERY = "SELECT * FROM /simple";
private static final String SINGLE_QUERY = "(SELECT * FROM /simple).size";
@@ -86,17 +88,19 @@ public class GemfireTemplateTest {
@Before
@SuppressWarnings("rawtypes")
public void setUp() throws FunctionDomainException, TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
QueryService queryService = simple.getRegionService().getQueryService();
Query singleQuery = mock(Query.class);
if (isMocking()) {
QueryService queryService = simple.getRegionService().getQueryService();
Query singleQuery = mock(Query.class);
when(singleQuery.execute(any(Object[].class))).thenReturn(0);
when(queryService.newQuery(SINGLE_QUERY)).thenReturn(singleQuery);
when(singleQuery.execute(any(Object[].class))).thenReturn(0);
when(queryService.newQuery(SINGLE_QUERY)).thenReturn(singleQuery);
Query multipleQuery = mock(Query.class);
SelectResults selectResults = mock(SelectResults.class);
Query multipleQuery = mock(Query.class);
SelectResults selectResults = mock(SelectResults.class);
when(multipleQuery.execute(any(Object[].class))).thenReturn(selectResults);
when(queryService.newQuery(MULTI_QUERY)).thenReturn(multipleQuery);
when(multipleQuery.execute(any(Object[].class))).thenReturn(selectResults);
when(queryService.newQuery(MULTI_QUERY)).thenReturn(multipleQuery);
}
}
@After

View File

@@ -681,9 +681,21 @@ public class ClientCacheFactoryBeanTest {
verify(mockClientCache, never()).getDistributedSystem();
}
@Test
public void isReadyForEventsIsFalseWhenClientCacheNotInitialized() {
ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean() {
@Override protected <T extends GemFireCache> T fetchCache() {
throw new CacheClosedException("test");
}
};
assertThat(clientCacheFactoryBean.getReadyForEvents(), is(nullValue()));
assertThat(clientCacheFactoryBean.isReadyForEvents(), is(false));
}
@Test
@SuppressWarnings("unchecked")
public void isReadyForEventsIsTrueWhenDurableClientIdSet() {
public void isReadyForEventsIsTrueWhenDurableClientIdIsSet() {
final ClientCache mockClientCache = mockClientCache("TestDurableClientId");
ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean() {

View File

@@ -0,0 +1,92 @@
/*
* 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.notNullValue;
import static org.junit.Assert.assertThat;
import java.util.Properties;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* The LazyInitializedClientCacheIntegrationTest class is a test suite of test cases testing the proper behavior a
* lazy initialized ClientCache by the SDG ClientCacheFactoryBean when the ClientCache instance is "looked up"
* in fetchCache() to ascertain whether the client is durable and readyForEvents needs to be signaled or not.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.data.gemfire.client.ClientCacheFactoryBean
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
* @see com.gemstone.gemfire.cache.client.ClientCache
* @link https://jira.spring.io/browse/SGF-441
* @since 1.0.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LazyInitializedClientCacheIntegrationTest.GemFireConfiguration.class)
@SuppressWarnings("unused")
public class LazyInitializedClientCacheIntegrationTest {
@Resource(name = "&clientCache")
private ClientCacheFactoryBean clientCacheFactoryBean;
@Autowired
private Properties gemfireProperties;
@Test
public void clientCacheFactoryBeanConfiguration() {
assertThat(clientCacheFactoryBean, is(notNullValue()));
assertThat(clientCacheFactoryBean.getBeanName(), is(equalTo("clientCache")));
assertThat(clientCacheFactoryBean.isLazyInitialize(), is(equalTo(true)));
assertThat(clientCacheFactoryBean.getProperties(), is(equalTo(gemfireProperties)));
}
@Configuration
public static class GemFireConfiguration {
@Bean
public Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", LazyInitializedClientCacheIntegrationTest.class.getSimpleName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", "warning");
return gemfireProperties;
}
@Bean
public ClientCacheFactoryBean clientCache() {
ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean();
clientCacheFactoryBean.setUseBeanFactoryLocator(false);
clientCacheFactoryBean.setProperties(gemfireProperties());
clientCacheFactoryBean.setLazyInitialize(true);
return clientCacheFactoryBean;
}
}
}

View File

@@ -173,9 +173,9 @@ public class CacheNamespaceTest{
@Test(expected = IllegalArgumentException.class)
public void testNoBeanFactoryLocator() throws Exception {
assertTrue(context.containsBean("no-bean-factory-locator"));
assertTrue(context.containsBean("no-bean-factory-locator-cache"));
CacheFactoryBean cacheFactoryBean = context.getBean("&no-bean-factory-locator", CacheFactoryBean.class);
CacheFactoryBean cacheFactoryBean = context.getBean("&no-bean-factory-locator-cache", CacheFactoryBean.class);
assertThat(ReflectionTestUtils.getField(cacheFactoryBean, "beanFactoryLocator"), is(nullValue()));
@@ -183,7 +183,7 @@ public class CacheNamespaceTest{
try {
assertNotNull(beanFactoryLocator.useBeanFactory("cache-with-name"));
beanFactoryLocator.useBeanFactory("no-bean-factory-locator");
beanFactoryLocator.useBeanFactory("no-bean-factory-locator-cache");
}
finally {
beanFactoryLocator.destroy();
@@ -191,7 +191,7 @@ public class CacheNamespaceTest{
}
@Test
public void testNamedClientCache() throws Exception {
public void namedClientCacheWithNoProperties() throws Exception {
assertTrue(context.containsBean("client-cache-with-name"));
ClientCacheFactoryBean clientCacheFactoryBean = context.getBean("&client-cache-with-name", ClientCacheFactoryBean.class);
@@ -205,7 +205,7 @@ public class CacheNamespaceTest{
}
@Test
public void testClientCacheWithXml() throws Exception {
public void clientCacheWithXmlNoProperties() throws Exception {
assertTrue(context.containsBean("client-cache-with-xml"));
ClientCacheFactoryBean clientCacheFactoryBean = context.getBean("&client-cache-with-xml", ClientCacheFactoryBean.class);

View File

@@ -46,7 +46,7 @@ import com.gemstone.gemfire.cache.client.PoolManager;
* @author John Blum
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="pool-ns.xml", initializers=GemfireTestApplicationContextInitializer.class)
@ContextConfiguration(locations = "pool-ns.xml", initializers = GemfireTestApplicationContextInitializer.class)
@SuppressWarnings("unused")
public class PoolNamespaceTest {
@@ -117,10 +117,10 @@ public class PoolNamespaceTest {
}
@Test
public void testComplexPool() throws Exception {
assertTrue(context.containsBean("complex"));
public void testServerPool() throws Exception {
assertTrue(context.containsBean("server"));
PoolFactoryBean poolFactoryBean = context.getBean("&complex", PoolFactoryBean.class);
PoolFactoryBean poolFactoryBean = context.getBean("&server", PoolFactoryBean.class);
assertEquals(2000, TestUtils.readField("freeConnectionTimeout", poolFactoryBean));
assertEquals(20000l, TestUtils.readField("idleTimeout", poolFactoryBean));

View File

@@ -19,6 +19,8 @@ package org.springframework.data.gemfire.test;
import org.springframework.data.gemfire.test.support.IdentifierSequence;
import org.springframework.data.gemfire.test.support.StackTraceUtils;
import com.gemstone.gemfire.management.internal.cli.util.spring.StringUtils;
/**
* The AbstractMockery class is an abstract base class supporting the creation and use of mock objects in unit tests.
*
@@ -30,6 +32,19 @@ public abstract class AbstractMockerySupport {
protected static final String NOT_IMPLEMENTED = "Not Implemented";
protected boolean isMocking() {
String gemfireTestRunnerNoMock = StringUtils.trimWhitespace(System.getProperty(
GemfireTestApplicationContextInitializer.GEMFIRE_TEST_RUNNER_DISABLED, "false"));
return !(Boolean.parseBoolean(gemfireTestRunnerNoMock)
|| "yes".equalsIgnoreCase(gemfireTestRunnerNoMock)
|| "y".equalsIgnoreCase(gemfireTestRunnerNoMock));
}
protected boolean isNotMocking() {
return !isMocking();
}
protected Object getMockId() {
StackTraceElement element = StackTraceUtils.getTestCaller();
return (element != null ? StackTraceUtils.getCallerSimpleName(element): IdentifierSequence.nextId());

View File

@@ -48,7 +48,8 @@ public class GemfireTestApplicationContextInitializer implements ApplicationCont
private boolean isGemFireTestRunnerDisable(final String systemPropertyValue) {
return (Boolean.valueOf(StringUtils.trimAllWhitespace(systemPropertyValue))
|| "yes".equalsIgnoreCase(systemPropertyValue));
|| "yes".equalsIgnoreCase(systemPropertyValue)
|| "y".equalsIgnoreCase(systemPropertyValue));
}
}

View File

@@ -12,30 +12,31 @@
<!-- all beans are lazy to allow the same config to be used between multiple tests -->
<!-- as there can be only one GemFire cache per VM -->
<util:properties id="gemfireProperties">
<prop key="disable-tcp">false</prop>
<prop key="log-level">warning</prop>
</util:properties>
<gfe:cache/>
<gfe:cache id="cache-with-name"/>
<util:properties id="gemfireProperties">
<prop key="disable-tcp">false</prop>
</util:properties>
<gfe:cache id="cache-with-xml-and-props" cache-xml-location="classpath:gemfire-cache.xml" properties-ref="gemfireProperties"
pdx-read-serialized="true" pdx-ignore-unread-fields="false" pdx-persistent="true"/>
<gfe:cache id="cache-with-gateway-conflict-resolver">
<gfe:cache id="cache-with-gateway-conflict-resolver" properties-ref="gemfireProperties">
<gfe:gateway-conflict-resolver>
<bean class="org.springframework.data.gemfire.config.CacheNamespaceTest.TestGatewayConflictResolver"/>
</gfe:gateway-conflict-resolver>
</gfe:cache>
<gfe:cache id="cache-with-auto-reconnect-disabled" enable-auto-reconnect="false"/>
<gfe:cache id="cache-with-auto-reconnect-disabled" properties-ref="gemfireProperties" enable-auto-reconnect="false"/>
<gfe:cache id="cache-with-auto-reconnect-enabled" enable-auto-reconnect="true"/>
<gfe:cache id="cache-with-auto-reconnect-enabled" properties-ref="gemfireProperties" enable-auto-reconnect="true"/>
<gfe:cache id="heap-tuned-cache" critical-heap-percentage="70.0" eviction-heap-percentage="60.0"/>
<gfe:cache id="heap-tuned-cache" properties-ref="gemfireProperties" critical-heap-percentage="70.0" eviction-heap-percentage="60.0"/>
<gfe:cache id="no-bean-factory-locator" use-bean-factory-locator="false"/>
<gfe:cache id="no-bean-factory-locator-cache" properties-ref="gemfireProperties" use-bean-factory-locator="false"/>
<gfe:client-cache id="client-cache-with-name"/>

View File

@@ -11,16 +11,16 @@
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
">
<context:property-placeholder location="classpath:port.properties"/>
<util:properties id="gemfireProperties">
<prop key="name">PoolNamespaceConfig</prop>
<prop key="mcast-port">0</prop>
<prop key="log-level">error</prop>
<prop key="log-level">warning</prop>
</util:properties>
<gfe:client-cache properties-ref="gemfireProperties"/>
<context:property-placeholder location="classpath:port.properties"/>
<gfe:pool>
<gfe:locator host="localhost" port="${gfe.port}"/>
</gfe:pool>
@@ -29,7 +29,7 @@
<gfe:pool id="locator" locators="skullbox, yorktown[12480]"/>
<gfe:pool id="complex" free-connection-timeout="2000" idle-timeout="20000" load-conditioning-interval="10000"
<gfe:pool id="server" free-connection-timeout="2000" idle-timeout="20000" load-conditioning-interval="10000"
keep-alive="true" max-connections="100" min-connections="5" multi-user-authentication="true"
ping-interval="5000" pr-single-hop-enabled="false" read-timeout="500" retry-attempts="5"
server-group="TestGroup" socket-buffer-size="65536" statistic-interval="5000"