From 6b55f3f7d271b30882044df30ecb574f4d7b9601 Mon Sep 17 00:00:00 2001 From: John Blum Date: Tue, 25 Apr 2017 19:27:38 -0700 Subject: [PATCH] Restore proper behavior of HttpSession created events in GemFire support when client Region is a PROXY in the client/server topology Fixes gh-757 (cherry picked from commit c0c404ab966639924e081bf3bf3142369b5b9b3d) Signed-off-by: John Blum --- .../AbstractGemFireIntegrationTests.java | 14 + ...ionsSessionRepositoryIntegrationTests.java | 4 +- ...essionAttributesDeltaIntegrationTests.java | 6 +- ...gionSessionOperationsIntegrationTests.java | 310 +++++++++++++++++ ...actGemFireOperationsSessionRepository.java | 66 +++- .../data/gemfire/support/GemFireUtils.java | 15 +- ...emFireOperationsSessionRepositoryTest.java | 321 +++++++++++++++--- .../gemfire/support/GemFireUtilsTest.java | 80 +++-- 8 files changed, 729 insertions(+), 87 deletions(-) create mode 100644 spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerProxyRegionSessionOperationsIntegrationTests.java diff --git a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/AbstractGemFireIntegrationTests.java b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/AbstractGemFireIntegrationTests.java index 2daa5117..8644561d 100644 --- a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/AbstractGemFireIntegrationTests.java +++ b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/AbstractGemFireIntegrationTests.java @@ -36,6 +36,7 @@ import com.gemstone.gemfire.cache.client.ClientCache; import com.gemstone.gemfire.cache.client.ClientCacheFactory; import com.gemstone.gemfire.cache.query.Index; import com.gemstone.gemfire.cache.server.CacheServer; + import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; @@ -310,6 +311,13 @@ public abstract class AbstractGemFireIntegrationTests { return processControl; } + /* (non-Javadoc) */ + protected void assertValidSession(ExpiringSession session) { + assertThat(session).isNotNull(); + assertThat(session.getId()).isNotEmpty(); + assertThat(session.isExpired()).isFalse(); + } + /* (non-Javadoc) */ protected void assertRegion(Region actualRegion, String expectedName, DataPolicy expectedDataPolicy) { assertThat(actualRegion).isNotNull(); @@ -379,6 +387,12 @@ public abstract class AbstractGemFireIntegrationTests { return (T) session; } + /* (non-Javadoc) */ + protected T delete(T session) { + this.gemfireSessionRepository.delete(session); + return session; + } + /* (non-Javadoc) */ protected T expire(T session) { session.setLastAccessedTime(0L); diff --git a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerGemFireOperationsSessionRepositoryIntegrationTests.java b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerGemFireOperationsSessionRepositoryIntegrationTests.java index 9762158d..8972aea7 100644 --- a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerGemFireOperationsSessionRepositoryIntegrationTests.java +++ b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerGemFireOperationsSessionRepositoryIntegrationTests.java @@ -135,7 +135,7 @@ public class ClientServerGemFireOperationsSessionRepositoryIntegrationTests } @AfterClass - public static void stopGemFireServerAndDeleteArtifacts() { + public static void stopGemFireServer() { if (gemfireServer != null) { gemfireServer.destroy(); System.err.printf("GemFire Server [exit code = %1$d]%n", @@ -332,7 +332,7 @@ public class ClientServerGemFireOperationsSessionRepositoryIntegrationTests } String name() { - return SpringSessionDataGemFireServerConfiguration.class.getName(); + return ClientServerGemFireOperationsSessionRepositoryIntegrationTests.class.getName(); } @Bean diff --git a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerHttpSessionAttributesDeltaIntegrationTests.java b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerHttpSessionAttributesDeltaIntegrationTests.java index 101cf747..34a3ec89 100644 --- a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerHttpSessionAttributesDeltaIntegrationTests.java +++ b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerHttpSessionAttributesDeltaIntegrationTests.java @@ -112,7 +112,7 @@ public class ClientServerHttpSessionAttributesDeltaIntegrationTests extends Abst } @AfterClass - public static void stopGemFireServerAndDeleteArtifacts() { + public static void stopGemFireServer() { if (gemfireServer != null) { gemfireServer.destroy(); System.err.printf("GemFire Server [exit code = %1$d]%n", @@ -165,7 +165,7 @@ public class ClientServerHttpSessionAttributesDeltaIntegrationTests extends Abst assertThat(reloadedSession.getAttribute("attrOne")).isEqualTo(1); } - @EnableGemFireHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS) + @EnableGemFireHttpSession static class SpringSessionDataGemFireClientConfiguration { @Bean @@ -245,7 +245,7 @@ public class ClientServerHttpSessionAttributesDeltaIntegrationTests extends Abst } String name() { - return SpringSessionDataGemFireServerConfiguration.class.getName(); + return ClientServerHttpSessionAttributesDeltaIntegrationTests.class.getName(); } @Bean diff --git a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerProxyRegionSessionOperationsIntegrationTests.java b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerProxyRegionSessionOperationsIntegrationTests.java new file mode 100644 index 00000000..0754ff9b --- /dev/null +++ b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerProxyRegionSessionOperationsIntegrationTests.java @@ -0,0 +1,310 @@ +/* + * Copyright 2014-2016 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.session.data.gemfire; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.client.ClientCache; + +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.beans.factory.annotation.Value; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.data.gemfire.client.ClientCacheFactoryBean; +import org.springframework.data.gemfire.client.PoolFactoryBean; +import org.springframework.data.gemfire.server.CacheServerFactoryBean; +import org.springframework.data.gemfire.support.ConnectionEndpoint; +import org.springframework.session.ExpiringSession; +import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession; +import org.springframework.session.events.AbstractSessionEvent; +import org.springframework.session.events.SessionCreatedEvent; +import org.springframework.session.events.SessionDeletedEvent; +import org.springframework.session.events.SessionExpiredEvent; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.FileSystemUtils; +import org.springframework.util.SocketUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The ClientServerProxyRegionSessionOperationsIntegrationTests class... + * + * @author John Blum + * @since 1.0.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = + ClientServerProxyRegionSessionOperationsIntegrationTests.SpringSessionDataGemFireClientConfiguration.class) +public class ClientServerProxyRegionSessionOperationsIntegrationTests extends AbstractGemFireIntegrationTests { + + private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1; + + private static final DateFormat TIMESTAMP = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); + + private static File processWorkingDirectory; + + private static Process gemfireServer; + + @Autowired + private SessionEventListener sessionEventListener; + + @BeforeClass + public static void startGemFireServer() throws IOException { + long t0 = System.currentTimeMillis(); + + int port = SocketUtils.findAvailableTcpPort(); + + System.err.printf("Starting a GemFire Server running on host [%1$s] listening on port [%2$d]%n", + SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port); + + System.setProperty("spring.session.data.gemfire.port", String.valueOf(port)); + + String processWorkingDirectoryPathname = + String.format("gemfire-client-server-tests-%1$s", TIMESTAMP.format(new Date())); + + processWorkingDirectory = createDirectory(processWorkingDirectoryPathname); + + gemfireServer = run(SpringSessionDataGemFireServerConfiguration.class, processWorkingDirectory, + String.format("-Dspring.session.data.gemfire.port=%1$d", port)); + + assertThat(waitForCacheServerToStart(SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port)) + .isTrue(); + + System.err.printf("GemFire Server [startup time = %1$d ms]%n", System.currentTimeMillis() - t0); + } + + @AfterClass + public static void stopGemFireServer() { + if (gemfireServer != null) { + gemfireServer.destroy(); + System.err.printf("GemFire Server [exit code = %1$d]%n", + waitForProcessToStop(gemfireServer, processWorkingDirectory)); + } + + if (Boolean.valueOf(System.getProperty("spring.session.data.gemfire.fork.clean", Boolean.TRUE.toString()))) { + FileSystemUtils.deleteRecursively(processWorkingDirectory); + } + + assertThat(waitForClientCacheToClose(DEFAULT_WAIT_DURATION)).isTrue(); + } + + @Test + public void createReadUpdateExpireRecreateDeleteRecreateSessionResultsCorrectSessionCreatedEvents() { + ExpiringSession session = save(touch(createSession())); + + assertValidSession(session); + + AbstractSessionEvent sessionEvent = this.sessionEventListener.waitForSessionEvent(500); + + assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class); + assertThat(sessionEvent.getSessionId()).isEqualTo(session.getId()); + + // GET + ExpiringSession loadedSession = get(session.getId()); + + assertThat(loadedSession).isNotNull(); + assertThat(loadedSession.getId()).isEqualTo(session.getId()); + assertThat(loadedSession.getCreationTime()).isEqualTo(session.getCreationTime()); + assertThat(loadedSession.getLastAccessedTime()).isGreaterThanOrEqualTo((session.getLastAccessedTime())); + + sessionEvent = this.sessionEventListener.waitForSessionEvent(500); + + assertThat(sessionEvent).isNull(); + + loadedSession.setAttribute("attrOne", 1); + loadedSession.setAttribute("attrTwo", 2); + + // UPDATE + save(touch(loadedSession)); + + sessionEvent = this.sessionEventListener.waitForSessionEvent(500); + + assertThat(sessionEvent).isNull(); + + // EXPIRE + sessionEvent = this.sessionEventListener.waitForSessionEvent( + TimeUnit.SECONDS.toMillis(MAX_INACTIVE_INTERVAL_IN_SECONDS + 1)); + + assertThat(sessionEvent).isInstanceOf(SessionExpiredEvent.class); + assertThat(sessionEvent.getSessionId()).isEqualTo(session.getId()); + + // RECREATE + save(touch(session)); + + sessionEvent = this.sessionEventListener.waitForSessionEvent(500); + + assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class); + assertThat(sessionEvent.getSessionId()).isEqualTo(session.getId()); + + // DELETE + delete(session); + + sessionEvent = this.sessionEventListener.waitForSessionEvent(500); + + assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class); + assertThat(sessionEvent.getSessionId()).isEqualTo(session.getId()); + + // RECREATE + save(touch(session)); + + sessionEvent = this.sessionEventListener.waitForSessionEvent(500); + + assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class); + assertThat(sessionEvent.getSessionId()).isEqualTo(session.getId()); + } + + @EnableGemFireHttpSession + static class SpringSessionDataGemFireClientConfiguration { + + @Bean + static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + Properties gemfireProperties() { + Properties gemfireProperties = new Properties(); + gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL); + return gemfireProperties; + } + + @Bean + ClientCacheFactoryBean gemfireCache() { + ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean(); + + clientCacheFactory.setClose(true); + clientCacheFactory.setProperties(gemfireProperties()); + + return clientCacheFactory; + } + + @Bean + PoolFactoryBean gemfirePool(@Value("${spring.session.data.gemfire.port:" + + DEFAULT_GEMFIRE_SERVER_PORT + "}") int port) { + + PoolFactoryBean poolFactory = new PoolFactoryBean(); + + poolFactory.setKeepAlive(false); + poolFactory.setPingInterval(TimeUnit.SECONDS.toMillis(5)); + poolFactory.setReadTimeout(2000); // 2 seconds + poolFactory.setRetryAttempts(1); + poolFactory.setSubscriptionEnabled(true); + + poolFactory.setServers(Collections.singletonList(new ConnectionEndpoint( + SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port))); + + return poolFactory; + } + + @Bean + public SessionEventListener sessionEventListener() { + return new SessionEventListener(); + } + + // used for debugging purposes + @SuppressWarnings("resource") + public static void main(String[] args) { + ConfigurableApplicationContext applicationContext = + new AnnotationConfigApplicationContext(SpringSessionDataGemFireClientConfiguration.class); + + applicationContext.registerShutdownHook(); + + ClientCache clientCache = applicationContext.getBean(ClientCache.class); + + for (InetSocketAddress server : clientCache.getCurrentServers()) { + System.err.printf("GemFire Server [host: %1$s, port: %2$d]%n", + server.getHostName(), server.getPort()); + } + } + } + + @EnableGemFireHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS) + static class SpringSessionDataGemFireServerConfiguration { + + static final String SERVER_HOSTNAME = "localhost"; + + @Bean + static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + Properties gemfireProperties() { + Properties gemfireProperties = new Properties(); + + gemfireProperties.setProperty("name", name()); + gemfireProperties.setProperty("mcast-port", "0"); + gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL); + + return gemfireProperties; + } + + String name() { + return ClientServerProxyRegionSessionOperationsIntegrationTests.class.getName(); + } + + @Bean + CacheFactoryBean gemfireCache() { + CacheFactoryBean gemfireCache = new CacheFactoryBean(); + + gemfireCache.setClose(true); + gemfireCache.setProperties(gemfireProperties()); + + return gemfireCache; + } + + @Bean + CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache, + @Value("${spring.session.data.gemfire.port:" + DEFAULT_GEMFIRE_SERVER_PORT + "}") int port) { + + CacheServerFactoryBean cacheServerFactory = new CacheServerFactoryBean(); + + cacheServerFactory.setCache(gemfireCache); + cacheServerFactory.setAutoStartup(true); + cacheServerFactory.setBindAddress(SERVER_HOSTNAME); + cacheServerFactory.setPort(port); + + return cacheServerFactory; + } + + @SuppressWarnings("resource") + public static void main(String[] args) throws IOException { + AnnotationConfigApplicationContext context = + new AnnotationConfigApplicationContext(SpringSessionDataGemFireServerConfiguration.class); + + context.registerShutdownHook(); + + writeProcessControlFile(WORKING_DIRECTORY); + } + } +} diff --git a/spring-session/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java b/spring-session/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java index 566c75e6..dfaf65d9 100644 --- a/spring-session/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java +++ b/spring-session/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java @@ -39,8 +39,10 @@ import com.gemstone.gemfire.Delta; import com.gemstone.gemfire.Instantiator; import com.gemstone.gemfire.InvalidDeltaException; import com.gemstone.gemfire.cache.EntryEvent; +import com.gemstone.gemfire.cache.Operation; import com.gemstone.gemfire.cache.Region; import com.gemstone.gemfire.cache.util.CacheListenerAdapter; +import com.gemstone.gemfire.internal.concurrent.ConcurrentHashSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -58,11 +60,13 @@ import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.Session; import org.springframework.session.SessionRepository; import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration; +import org.springframework.session.data.gemfire.support.GemFireUtils; import org.springframework.session.events.SessionCreatedEvent; import org.springframework.session.events.SessionDeletedEvent; import org.springframework.session.events.SessionDestroyedEvent; import org.springframework.session.events.SessionExpiredEvent; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -91,6 +95,8 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi } }; + private final Set cachedSessionIds = new ConcurrentHashSet(); + private final GemfireOperations template; protected final Log logger = newLogger(); @@ -217,8 +223,49 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi } /* (non-Javadoc) */ - boolean isExpiringSessionOrNull(Object obj) { - return (obj == null || obj instanceof ExpiringSession); + boolean isCreate(EntryEvent event) { + return (isCreate(event.getOperation()) && isNotUpdate(event) && isExpiringSessionOrNull(event.getNewValue())); + } + + /* (non-Javadoc) */ + private boolean isCreate(Operation operation) { + return (operation.isCreate() && !Operation.LOCAL_LOAD_CREATE.equals(operation)); + } + + /** + * Used to determine whether the developer is storing (HTTP) Sessions with other, arbitrary application + * domain objects in the same GemFire cache {@link Region}; crazier things have happened! + * + * @param obj {@link Object} to evaluate. + * @return a boolean value indicating whether the {@link Object} from the entry event is indeed + * a {@link ExpiringSession}. + * @see org.springframework.session.ExpiringSession + */ + private boolean isExpiringSessionOrNull(Object obj) { + return (obj instanceof ExpiringSession || obj == null); + } + + /* (non-Javadoc) */ + private boolean isNotUpdate(EntryEvent event) { + return (isNotProxyRegion() || !this.cachedSessionIds.contains(ObjectUtils.nullSafeHashCode(event.getKey()))); + } + + /* (non-Javadoc) */ + private boolean isNotProxyRegion() { + return !isProxyRegion(); + } + + /* (non-Javadoc) */ + private boolean isProxyRegion() { + return GemFireUtils.isProxy(((GemfireAccessor) getTemplate()).getRegion()); + } + + boolean forget(Object sessionId) { + return this.cachedSessionIds.remove(ObjectUtils.nullSafeHashCode(sessionId)); + } + + boolean remember(Object sessionId) { + return (isProxyRegion() && this.cachedSessionIds.add(ObjectUtils.nullSafeHashCode(sessionId))); } /* (non-Javadoc) */ @@ -227,16 +274,15 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi } /** - * Callback method triggered when an entry is created in the GemFire cache - * {@link Region}. + * Callback method triggered when an entry is created in the GemFire cache {@link Region}. * - * @param event an EntryEvent containing the details of the cache operation. + * @param event {@link EntryEvent} containing the details of the cache {@link Region} operation. * @see com.gemstone.gemfire.cache.EntryEvent * @see #handleCreated(String, ExpiringSession) */ @Override public void afterCreate(EntryEvent event) { - if (isExpiringSessionOrNull(event.getNewValue())) { + if (isCreate(event)) { handleCreated(event.getKey().toString(), toExpiringSession(event.getNewValue())); } } @@ -290,6 +336,8 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi * @see #publishEvent(ApplicationEvent) */ protected void handleCreated(String sessionId, ExpiringSession session) { + remember(sessionId); + publishEvent(session != null ? new SessionCreatedEvent(this, session) : new SessionCreatedEvent(this, sessionId)); } @@ -304,6 +352,8 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi * @see #publishEvent(ApplicationEvent) */ protected void handleDeleted(String sessionId, ExpiringSession session) { + forget(sessionId); + publishEvent(session != null ? new SessionDeletedEvent(this, session) : new SessionDeletedEvent(this, sessionId)); } @@ -318,6 +368,8 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi * @see #publishEvent(ApplicationEvent) */ protected void handleDestroyed(String sessionId, ExpiringSession session) { + forget(sessionId); + publishEvent(session != null ? new SessionDestroyedEvent(this, session) : new SessionDestroyedEvent(this, sessionId)); } @@ -332,6 +384,8 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi * @see #publishEvent(ApplicationEvent) */ protected void handleExpired(String sessionId, ExpiringSession session) { + forget(sessionId); + publishEvent(session != null ? new SessionExpiredEvent(this, session) : new SessionExpiredEvent(this, sessionId)); } diff --git a/spring-session/src/main/java/org/springframework/session/data/gemfire/support/GemFireUtils.java b/spring-session/src/main/java/org/springframework/session/data/gemfire/support/GemFireUtils.java index f1a0e8ea..8b5fcf57 100644 --- a/spring-session/src/main/java/org/springframework/session/data/gemfire/support/GemFireUtils.java +++ b/spring-session/src/main/java/org/springframework/session/data/gemfire/support/GemFireUtils.java @@ -20,6 +20,7 @@ import java.io.Closeable; import java.io.IOException; import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.DataPolicy; import com.gemstone.gemfire.cache.GemFireCache; import com.gemstone.gemfire.cache.Region; import com.gemstone.gemfire.cache.RegionShortcut; @@ -122,6 +123,19 @@ public abstract class GemFireUtils { } } + /** + * Determines whether the given {@link Region} is a PROXY, which would be indicated by the {@link Region} + * having a {@link DataPolicy} of {@link DataPolicy#EMPTY}. + * + * @param region {@link Region} to evaluate. + * @return a boolean value indicating whether the {@link Region} is a PROXY. + * @see com.gemstone.gemfire.cache.DataPolicy + * @see com.gemstone.gemfire.cache.Region + */ + public static boolean isProxy(Region region) { + return DataPolicy.EMPTY.equals(region.getAttributes().getDataPolicy()); + } + /** * Determines whether the peer {@link RegionShortcut} is a proxy-based shortcut. NOTE: * "proxy"-based Regions keep no local state. @@ -153,5 +167,4 @@ public abstract class GemFireUtils { public static String toRegionPath(String regionName) { return String.format("%1$s%2$s", Region.SEPARATOR, regionName); } - } diff --git a/spring-session/src/test/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepositoryTest.java b/spring-session/src/test/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepositoryTest.java index b9d440cb..fd819778 100644 --- a/spring-session/src/test/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepositoryTest.java +++ b/spring-session/src/test/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepositoryTest.java @@ -31,8 +31,11 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import com.gemstone.gemfire.cache.AttributesMutator; +import com.gemstone.gemfire.cache.DataPolicy; import com.gemstone.gemfire.cache.EntryEvent; +import com.gemstone.gemfire.cache.Operation; import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionAttributes; import edu.umd.cs.mtc.MultithreadedTestCase; import edu.umd.cs.mtc.TestFramework; @@ -84,7 +87,6 @@ import static org.mockito.Mockito.verify; * * @author John Blum * @since 1.1.0 - * @see org.junit.Rule * @see org.junit.Test * @see org.junit.runner.RunWith * @see org.mockito.Mock @@ -107,14 +109,14 @@ public class AbstractGemFireOperationsSessionRepositoryTest { private AbstractGemFireOperationsSessionRepository sessionRepository; @Mock - private GemfireOperations mockGemfireOperations; + private ExpiringSession mockExpiringSession; @Mock private Log mockLog; @Before public void setup() { - this.sessionRepository = spy(new TestGemFireOperationsSessionRepository(this.mockGemfireOperations) { + this.sessionRepository = spy(new TestGemFireOperationsSessionRepository(new GemfireTemplate()) { @Override Log newLogger() { return AbstractGemFireOperationsSessionRepositoryTest.this.mockLog; @@ -128,8 +130,31 @@ public class AbstractGemFireOperationsSessionRepositoryTest { return set; } - protected ExpiringSession mockSession(String sessionId, - long creationAndLastAccessedTime, int maxInactiveIntervalInSeconds) { + @SuppressWarnings("unchecked") + protected EntryEvent mockEntryEvent(Operation operation, K key, V oldValue, V newValue) { + EntryEvent mockEntryEvent = mock(EntryEvent.class); + + given(mockEntryEvent.getOperation()).willReturn(operation); + given(mockEntryEvent.getKey()).willReturn(key); + given(mockEntryEvent.getOldValue()).willReturn(oldValue); + given(mockEntryEvent.getNewValue()).willReturn(newValue); + + return mockEntryEvent; + } + + @SuppressWarnings("unchecked") + protected Region mockRegion(String name, DataPolicy dataPolicy) { + Region mockRegion = mock(Region.class, name); + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + given(mockRegion.getAttributes()).willReturn(mockRegionAttributes); + given(mockRegionAttributes.getDataPolicy()).willReturn(dataPolicy); + + return mockRegion; + } + + protected ExpiringSession mockSession(String sessionId, long creationAndLastAccessedTime, + int maxInactiveIntervalInSeconds) { return mockSession(sessionId, creationAndLastAccessedTime, creationAndLastAccessedTime, maxInactiveIntervalInSeconds); @@ -148,6 +173,14 @@ public class AbstractGemFireOperationsSessionRepositoryTest { return mockSession; } + protected AbstractGemFireOperationsSessionRepository withRegion( + AbstractGemFireOperationsSessionRepository sessionRepository, Region region) { + + ((GemfireTemplate) sessionRepository.getTemplate()).setRegion(region); + + return sessionRepository; + } + @Test(expected = IllegalArgumentException.class) public void constructGemFireOperationsSessionRepositoryWithNullTemplate() { try { @@ -220,6 +253,105 @@ public class AbstractGemFireOperationsSessionRepositoryTest { assertThat(this.sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MAX_VALUE); } + @Test + public void isCreateWithCreateOperationReturnsTrue() { + EntryEvent mockEvent = + this.mockEntryEvent(Operation.CREATE, "123", null, + this.mockExpiringSession); + + withRegion(this.sessionRepository, mockRegion("Example", DataPolicy.EMPTY)); + + assertThat(this.sessionRepository.isCreate(mockEvent)).isTrue(); + + verify(mockEvent, times(1)).getOperation(); + verify(mockEvent, times(1)).getKey(); + verify(mockEvent, times(1)).getNewValue(); + verify(mockEvent, never()).getOldValue(); + } + + @Test + public void isCreateWithCreateOperationAndNonProxyRegionReturnsTrue() { + EntryEvent mockEvent = + this.mockEntryEvent(Operation.CREATE, "123", null, null); + + withRegion(this.sessionRepository, mockRegion("Example", DataPolicy.NORMAL)); + + this.sessionRepository.remember("123"); + + assertThat(this.sessionRepository.isCreate(mockEvent)).isTrue(); + + verify(mockEvent, times(1)).getOperation(); + verify(mockEvent, never()).getKey(); + verify(mockEvent, times(1)).getNewValue(); + verify(mockEvent, never()).getOldValue(); + } + + @Test + public void isCreateWithLocalLoadCreateOperationReturnsFalse() { + EntryEvent mockEvent = + this.mockEntryEvent(Operation.LOCAL_LOAD_CREATE, "123", null, + this.mockExpiringSession); + + withRegion(this.sessionRepository, mockRegion("Example", DataPolicy.EMPTY)); + + assertThat(this.sessionRepository.isCreate(mockEvent)).isFalse(); + + verify(mockEvent, times(1)).getOperation(); + verify(mockEvent, never()).getKey(); + verify(mockEvent, never()).getNewValue(); + verify(mockEvent, never()).getOldValue(); + } + + @Test + public void isCreateWithUpdateOperationReturnsFalse() { + EntryEvent mockEvent = + this.mockEntryEvent(Operation.UPDATE, "123", null, + this.mockExpiringSession); + + withRegion(this.sessionRepository, mockRegion("Example", DataPolicy.EMPTY)); + + assertThat(this.sessionRepository.isCreate(mockEvent)).isFalse(); + + verify(mockEvent, times(1)).getOperation(); + verify(mockEvent, never()).getKey(); + verify(mockEvent, never()).getNewValue(); + verify(mockEvent, never()).getOldValue(); + } + + @Test + public void isCreateWithRememberedSessionIdReturnsFalse() { + EntryEvent mockEvent = + this.mockEntryEvent(Operation.CREATE, "123", null, + this.mockExpiringSession); + + withRegion(this.sessionRepository, mockRegion("Example", DataPolicy.EMPTY)); + + this.sessionRepository.remember("123"); + + assertThat(this.sessionRepository.isCreate(mockEvent)).isFalse(); + + verify(mockEvent, times(1)).getOperation(); + verify(mockEvent, times(1)).getKey(); + verify(mockEvent, never()).getNewValue(); + verify(mockEvent, never()).getOldValue(); + } + + @Test + public void isCreateWithTombstoneReturnsFalse() { + EntryEvent mockEvent = + this.mockEntryEvent(Operation.CREATE, "123", null, + new Tombstone()); + + withRegion(this.sessionRepository, mockRegion("Example", DataPolicy.EMPTY)); + + assertThat(this.sessionRepository.isCreate(mockEvent)).isFalse(); + + verify(mockEvent, times(1)).getOperation(); + verify(mockEvent, times(1)).getKey(); + verify(mockEvent, times(1)).getNewValue(); + verify(mockEvent, never()).getOldValue(); + } + @Test @SuppressWarnings("unchecked") public void afterCreateWithSessionPublishesSessionCreatedEvent() { @@ -247,10 +379,10 @@ public class AbstractGemFireOperationsSessionRepositoryTest { } }).given(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); - EntryEvent mockEntryEvent = mock(EntryEvent.class); + EntryEvent mockEntryEvent = + this.mockEntryEvent(Operation.CREATE, sessionId, null, mockSession); - given(mockEntryEvent.getKey()).willReturn(sessionId); - given(mockEntryEvent.getNewValue()).willReturn(mockSession); + withRegion(this.sessionRepository, mockRegion("Example", DataPolicy.EMPTY)); this.sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); this.sessionRepository.afterCreate(mockEntryEvent); @@ -258,7 +390,8 @@ public class AbstractGemFireOperationsSessionRepositoryTest { assertThat(this.sessionRepository.getApplicationEventPublisher()) .isSameAs(mockApplicationEventPublisher); - verify(mockEntryEvent, times(1)).getKey(); + verify(mockEntryEvent, times(1)).getOperation(); + verify(mockEntryEvent, times(2)).getKey(); verify(mockEntryEvent, times(2)).getNewValue(); verify(mockEntryEvent, never()).getOldValue(); verify(mockSession, times(1)).getId(); @@ -290,17 +423,18 @@ public class AbstractGemFireOperationsSessionRepositoryTest { } }).given(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); - EntryEvent mockEntryEvent = mock(EntryEvent.class); + EntryEvent mockEntryEvent = + this.mockEntryEvent(Operation.CREATE, sessionId, null, null); - given(mockEntryEvent.getKey()).willReturn(sessionId); - given(mockEntryEvent.getNewValue()).willReturn(null); + withRegion(this.sessionRepository, mockRegion("Example", DataPolicy.EMPTY)); this.sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); this.sessionRepository.afterCreate(mockEntryEvent); assertThat(this.sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); - verify(mockEntryEvent, times(1)).getKey(); + verify(mockEntryEvent, times(1)).getOperation(); + verify(mockEntryEvent, times(2)).getKey(); verify(mockEntryEvent, times(2)).getNewValue(); verify(mockEntryEvent, never()).getOldValue(); verify(mockApplicationEventPublisher, times(1)) @@ -308,24 +442,73 @@ public class AbstractGemFireOperationsSessionRepositoryTest { } @Test - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void afterCreatedWithNonSessionTypeDoesNotPublishSessionCreatedEvent() { + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void afterCreateForDestroyOperationDoesNotPublishSessionCreatedEvent() { + Region mockRegion = mockRegion("Example", DataPolicy.EMPTY); + TestGemFireOperationsSessionRepository sessionRepository = - new TestGemFireOperationsSessionRepository(this.mockGemfireOperations) { + new TestGemFireOperationsSessionRepository(new GemfireTemplate(mockRegion)) { + @Override + protected void handleCreated(String sessionId, ExpiringSession session) { + fail("handleCreated(..) should not have been called"); + } + }; + + EntryEvent mockEntryEvent = + mockEntryEvent(Operation.DESTROY, null, null, null); + + sessionRepository.afterCreate(mockEntryEvent); + + verify(mockEntryEvent, times(1)).getOperation(); + verify(mockEntryEvent, never()).getKey(); + verify(mockEntryEvent, never()).getNewValue(); + verify(mockEntryEvent, never()).getOldValue(); + } + + @Test + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void afterCreateForModificationDoesNotPublishSessionCreatedEvent() { + Region mockRegion = mockRegion("Example", DataPolicy.EMPTY); + + TestGemFireOperationsSessionRepository sessionRepository = + new TestGemFireOperationsSessionRepository(new GemfireTemplate(mockRegion)) { + @Override + protected void handleCreated(String sessionId, ExpiringSession session) { + fail("handleCreated(..) should not have been called"); + } + }; + + EntryEvent mockEntryEvent = + this.mockEntryEvent(Operation.CREATE, "123", null, null); + + sessionRepository.remember("123"); + sessionRepository.afterCreate(mockEntryEvent); + + verify(mockEntryEvent, times(1)).getOperation(); + verify(mockEntryEvent, times(1)).getKey(); + verify(mockEntryEvent, never()).getNewValue(); + verify(mockEntryEvent, never()).getOldValue(); + } + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void afterCreateForNonSessionTypeDoesNotPublishSessionCreatedEvent() { + Region mockRegion = mockRegion("Example", DataPolicy.EMPTY); + + TestGemFireOperationsSessionRepository sessionRepository = + new TestGemFireOperationsSessionRepository(new GemfireTemplate(mockRegion)) { @Override protected void handleCreated(String sessionId, ExpiringSession session) { fail("handleCreated(..) should not have been called"); } }; - EntryEvent mockEntryEvent = mock(EntryEvent.class); - - given(mockEntryEvent.getKey()).willReturn("abc123"); - given(mockEntryEvent.getNewValue()).willReturn(new Tombstone()); + EntryEvent mockEntryEvent = mockEntryEvent(Operation.CREATE, null, null, new Tombstone()); sessionRepository.afterCreate((EntryEvent) mockEntryEvent); - verify(mockEntryEvent, never()).getKey(); + verify(mockEntryEvent, times(1)).getOperation(); + verify(mockEntryEvent, times(1)).getKey(); verify(mockEntryEvent, times(1)).getNewValue(); verify(mockEntryEvent, never()).getOldValue(); } @@ -357,10 +540,8 @@ public class AbstractGemFireOperationsSessionRepositoryTest { } }).given(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); - EntryEvent mockEntryEvent = mock(EntryEvent.class); - - given(mockEntryEvent.getKey()).willReturn(sessionId); - given(mockEntryEvent.getOldValue()).willReturn(mockSession); + EntryEvent mockEntryEvent = + this.mockEntryEvent(Operation.DESTROY, sessionId, mockSession, null); this.sessionRepository .setApplicationEventPublisher(mockApplicationEventPublisher); @@ -401,10 +582,8 @@ public class AbstractGemFireOperationsSessionRepositoryTest { } }).given(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); - EntryEvent mockEntryEvent = mock(EntryEvent.class); - - given(mockEntryEvent.getKey()).willReturn(sessionId); - given(mockEntryEvent.getOldValue()).willReturn(null); + EntryEvent mockEntryEvent = + this.mockEntryEvent(Operation.DESTROY, sessionId, null, null); this.sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); this.sessionRepository.afterDestroy(mockEntryEvent); @@ -442,10 +621,7 @@ public class AbstractGemFireOperationsSessionRepositoryTest { } }).given(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); - EntryEvent mockEntryEvent = mock(EntryEvent.class); - - given(mockEntryEvent.getKey()).willReturn(sessionId); - given(mockEntryEvent.getOldValue()).willReturn(new Tombstone()); + EntryEvent mockEntryEvent = mockEntryEvent(Operation.DESTROY, sessionId, new Tombstone(), null); this.sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); this.sessionRepository.afterDestroy((EntryEvent) mockEntryEvent); @@ -486,10 +662,8 @@ public class AbstractGemFireOperationsSessionRepositoryTest { } }).given(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); - EntryEvent mockEntryEvent = mock(EntryEvent.class); - - given(mockEntryEvent.getKey()).willReturn(sessionId); - given(mockEntryEvent.getOldValue()).willReturn(mockSession); + EntryEvent mockEntryEvent = + this.mockEntryEvent(Operation.INVALIDATE, sessionId, mockSession, null); this.sessionRepository .setApplicationEventPublisher(mockApplicationEventPublisher); @@ -530,10 +704,8 @@ public class AbstractGemFireOperationsSessionRepositoryTest { } }).given(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); - EntryEvent mockEntryEvent = mock(EntryEvent.class); - - given(mockEntryEvent.getKey()).willReturn(sessionId); - given(mockEntryEvent.getOldValue()).willReturn(null); + EntryEvent mockEntryEvent = + this.mockEntryEvent(Operation.INVALIDATE, sessionId, null, null); this.sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); this.sessionRepository.afterInvalidate(mockEntryEvent); @@ -589,6 +761,69 @@ public class AbstractGemFireOperationsSessionRepositoryTest { .publishEvent(isA(SessionExpiredEvent.class)); } + @Test + public void sessionCreateCreateExpireRecreatePublishesSessionEventsCreateExpireCreate() { + final String sessionId = "123456789"; + final ExpiringSession mockSession = mock(ExpiringSession.class); + + given(mockSession.getId()).willReturn(sessionId); + + ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); + + willAnswer(new Answer() { + int index = 0; + + Class[] expectedSessionTypes = { + SessionCreatedEvent.class, SessionExpiredEvent.class, SessionCreatedEvent.class + }; + + public Void answer(InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(this.expectedSessionTypes[this.index++]); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()) + .isEqualTo(AbstractGemFireOperationsSessionRepositoryTest.this.sessionRepository); + assertThat(sessionEvent.getSession()).isEqualTo(mockSession); + assertThat(sessionEvent.getSessionId()).isEqualTo(sessionId); + + return null; + } + }).given(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); + + EntryEvent mockCreateEvent = + this.mockEntryEvent(Operation.CREATE, sessionId, null, mockSession); + + EntryEvent mockExpireEvent = + this.mockEntryEvent(Operation.INVALIDATE, sessionId, mockSession, null); + + withRegion(this.sessionRepository, mockRegion("Example", DataPolicy.EMPTY)); + + this.sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + this.sessionRepository.afterCreate(mockCreateEvent); + this.sessionRepository.afterCreate(mockCreateEvent); + this.sessionRepository.afterInvalidate(mockExpireEvent); + this.sessionRepository.afterCreate(mockCreateEvent); + + assertThat(this.sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + + verify(mockCreateEvent, times(3)).getOperation(); + verify(mockCreateEvent, times(5)).getKey(); + verify(mockCreateEvent, times(4)).getNewValue(); + verify(mockCreateEvent, never()).getOldValue(); + verify(mockExpireEvent, never()).getOperation(); + verify(mockExpireEvent, times(1)).getKey(); + verify(mockExpireEvent, never()).getNewValue(); + verify(mockExpireEvent, times(1)).getOldValue(); + verify(mockSession, times(3)).getId(); + verify(mockApplicationEventPublisher, times(2)) + .publishEvent(isA(SessionCreatedEvent.class)); + verify(mockApplicationEventPublisher, times(1)) + .publishEvent(isA(SessionExpiredEvent.class)); + } + @Test public void deleteSessionCallsDeleteSessionId() { Session mockSession = mock(Session.class); @@ -1650,9 +1885,9 @@ public class AbstractGemFireOperationsSessionRepositoryTest { static class Tombstone { } - protected static class TestGemFireOperationsSessionRepository extends AbstractGemFireOperationsSessionRepository { + static class TestGemFireOperationsSessionRepository extends AbstractGemFireOperationsSessionRepository { - protected TestGemFireOperationsSessionRepository(GemfireOperations gemfireOperations) { + TestGemFireOperationsSessionRepository(GemfireOperations gemfireOperations) { super(gemfireOperations); } diff --git a/spring-session/src/test/java/org/springframework/session/data/gemfire/support/GemFireUtilsTest.java b/spring-session/src/test/java/org/springframework/session/data/gemfire/support/GemFireUtilsTest.java index fc1e983f..7f3de4a0 100644 --- a/spring-session/src/test/java/org/springframework/session/data/gemfire/support/GemFireUtilsTest.java +++ b/spring-session/src/test/java/org/springframework/session/data/gemfire/support/GemFireUtilsTest.java @@ -20,13 +20,18 @@ import java.io.Closeable; import java.io.IOException; import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.DataPolicy; import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionAttributes; import com.gemstone.gemfire.cache.RegionShortcut; import com.gemstone.gemfire.cache.client.ClientCache; import com.gemstone.gemfire.cache.client.ClientRegionShortcut; + import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -52,8 +57,7 @@ public class GemFireUtilsTest { } @Test - public void closeNonNullCloseableObjectThrowingIOExceptionReturnsFalse() - throws IOException { + public void closeNonNullCloseableObjectThrowingIOExceptionReturnsFalse() throws IOException { Closeable mockCloseable = mock(Closeable.class); willThrow(new IOException("test")).given(mockCloseable).close(); assertThat(GemFireUtils.close(mockCloseable)).isFalse(); @@ -101,17 +105,14 @@ public class GemFireUtilsTest { assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_HEAP_LRU)).isTrue(); assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_OVERFLOW)).isTrue(); assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_PERSISTENT)).isTrue(); - assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW)) - .isTrue(); + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW)).isTrue(); } @Test public void clientRegionShortcutIsNotLocal() { assertThat(GemFireUtils.isLocal(ClientRegionShortcut.CACHING_PROXY)).isFalse(); - assertThat(GemFireUtils.isLocal(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU)) - .isFalse(); - assertThat(GemFireUtils.isLocal(ClientRegionShortcut.CACHING_PROXY_OVERFLOW)) - .isFalse(); + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU)).isFalse(); + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.CACHING_PROXY_OVERFLOW)).isFalse(); assertThat(GemFireUtils.isLocal(ClientRegionShortcut.PROXY)).isFalse(); } @@ -123,56 +124,72 @@ public class GemFireUtilsTest { @Test public void clientRegionShortcutIsNotProxy() { assertThat(GemFireUtils.isProxy(ClientRegionShortcut.CACHING_PROXY)).isFalse(); - assertThat(GemFireUtils.isProxy(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU)) - .isFalse(); - assertThat(GemFireUtils.isProxy(ClientRegionShortcut.CACHING_PROXY_OVERFLOW)) - .isFalse(); + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU)).isFalse(); + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.CACHING_PROXY_OVERFLOW)).isFalse(); assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL)).isFalse(); assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_HEAP_LRU)).isFalse(); assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_OVERFLOW)).isFalse(); assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_PERSISTENT)).isFalse(); - assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW)) - .isFalse(); + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW)).isFalse(); } @Test public void regionShortcutIsProxy() { assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PROXY)).isTrue(); - assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PROXY_REDUNDANT)) - .isTrue(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PROXY_REDUNDANT)).isTrue(); assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_PROXY)).isTrue(); } + @Test + public void regionIsProxy() { + Region mockRegion = mock(Region.class); + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + given(mockRegion.getAttributes()).willReturn(mockRegionAttributes); + given(mockRegionAttributes.getDataPolicy()).willReturn(DataPolicy.EMPTY); + + assertThat(GemFireUtils.isProxy(mockRegion)).isTrue(); + + verify(mockRegion, times(1)).getAttributes(); + verify(mockRegionAttributes, times(1)).getDataPolicy(); + } + + @Test + public void regionIsNotProxy() { + Region mockRegion = mock(Region.class); + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + given(mockRegion.getAttributes()).willReturn(mockRegionAttributes); + given(mockRegionAttributes.getDataPolicy()).willReturn(DataPolicy.NORMAL); + + assertThat(GemFireUtils.isProxy(mockRegion)).isFalse(); + + verify(mockRegion, times(1)).getAttributes(); + verify(mockRegionAttributes, times(1)).getDataPolicy(); + } + @Test public void regionShortcutIsNotProxy() { assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_HEAP_LRU)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_OVERFLOW)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_PERSISTENT)).isFalse(); - assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_PERSISTENT_OVERFLOW)) - .isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_PERSISTENT_OVERFLOW)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_HEAP_LRU)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_OVERFLOW)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_PERSISTENT)).isFalse(); - assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_PERSISTENT_OVERFLOW)) - .isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_PERSISTENT_OVERFLOW)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_HEAP_LRU)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_OVERFLOW)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PERSISTENT)).isFalse(); - assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PERSISTENT_OVERFLOW)) - .isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PERSISTENT_OVERFLOW)).isFalse(); assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT)).isFalse(); - assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_HEAP_LRU)) - .isFalse(); - assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_OVERFLOW)) - .isFalse(); - assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT)) - .isFalse(); - assertThat(GemFireUtils - .isProxy(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT_OVERFLOW)) - .isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_HEAP_LRU)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_OVERFLOW)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT_OVERFLOW)).isFalse(); } @Test @@ -183,5 +200,4 @@ public class GemFireUtilsTest { assertThat(GemFireUtils.toRegionPath("/")).isEqualTo("//"); assertThat(GemFireUtils.toRegionPath("")).isEqualTo("/"); } - }