Add GemFire Support

Fixes GH PR #148 and PR #308 implementing a GemFire Adapter to support
clustered HttpSessions in Spring Session.

* Resolve SGF-373 - Implement a Spring Session Adapter for GemFire backing
a HttpSession similar to the Redis support.
* Add Spring Session annotation to enable GemFire support with
@EnableGemFireHttpSession.
* Add extesion of SpringHttpSessionConfiguration to configure GemFire using
GemFireHttpSessionConfiguration.
* Add implementation of SessionRepository to access clustered, replicated
HttpSession state in GemFire with GemFireOperationsSessionRepository.
* Utilize GemFire Data Serialization framework to both replicate
HttpSession state information as well as handle deltas.
* Utilize GemFire OQL query to lookup arbitrary Session attributes by name,
and in particular the user authenticated principal name.
* Implment unit and integration tests, and in particular, tests for both
peer-to-peer (p2p) and client/server topologies.
* Set initial Spring Data GemFire version to 1.7.2.RELEASE, which depends
on Pivotal GemFire 8.1.0.
* Add documentation, Javadoc and samples along with additional Integration
Tests.

Fixes gh-148
This commit is contained in:
John Blum
2016-01-28 13:19:18 -06:00
committed by Rob Winch
parent 1931da83a5
commit 019e0083b0
68 changed files with 8634 additions and 2 deletions

View File

@@ -0,0 +1,441 @@
/*
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.session.ExpiringSession;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.session.events.AbstractSessionEvent;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.CacheClosedException;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.ExpirationAction;
import com.gemstone.gemfire.cache.ExpirationAttributes;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.client.ClientCache;
import com.gemstone.gemfire.cache.client.ClientCacheFactory;
import com.gemstone.gemfire.cache.server.CacheServer;
/**
* AbstractGemFireIntegrationTests is an abstract base class encapsulating common operations for writing
* Spring Session GemFire integration tests.
*
* @author John Blum
* @see org.springframework.session.ExpiringSession
* @see org.springframework.session.events.AbstractSessionEvent
* @see com.gemstone.gemfire.cache.Cache
* @see com.gemstone.gemfire.cache.DataPolicy
* @see com.gemstone.gemfire.cache.ExpirationAttributes
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.Region
* @see com.gemstone.gemfire.cache.client.ClientCache
* @see com.gemstone.gemfire.cache.server.CacheServer
* @since 1.1.0
*/
@SuppressWarnings("unused")
public class AbstractGemFireIntegrationTests {
protected static final boolean DEFAULT_ENABLE_QUERY_DEBUGGING = false;
protected static final boolean GEMFIRE_QUERY_DEBUG = Boolean.getBoolean("spring.session.data.gemfire.query.debug");
protected static final int DEFAULT_GEMFIRE_SERVER_PORT = CacheServer.DEFAULT_PORT;
protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20);
protected static final long DEFAULT_WAIT_INTERVAL = 500l;
protected static final File WORKING_DIRECTORY = new File(System.getProperty("user.dir"));
protected static final String DEFAULT_PROCESS_CONTROL_FILENAME = "process.ctl";
protected static final String GEMFIRE_LOG_FILE_NAME = System.getProperty(
"spring.session.data.gemfire.log-file", "server.log");
protected static final String GEMFIRE_LOG_LEVEL = System.getProperty(
"spring.session.data.gemfire.log-level", "warning");
@Autowired
protected Cache gemfireCache;
@Autowired
protected GemFireOperationsSessionRepository gemfireSessionRepository;
@Before
public void setup() {
System.setProperty("gemfire.Query.VERBOSE", String.valueOf(isQueryDebuggingEnabled()));
}
/* (non-Javadoc) */
protected static File createDirectory(String pathname) {
File directory = new File(WORKING_DIRECTORY, pathname);
assertThat(directory.isDirectory() || directory.mkdirs()).as(
String.format("Failed to create directory (%1$s)", directory)).isTrue();
directory.deleteOnExit();
return directory;
}
/* (non-Javadoc) */
protected static List<String> createJavaProcessCommandLine(Class type, String... args) {
List<String> commandLine = new ArrayList<String>();
String javaHome = System.getProperty("java.home");
String javaExe = new File(new File(javaHome, "bin"), "java").getAbsolutePath();
commandLine.add(javaExe);
commandLine.add("-server");
commandLine.add("-ea");
commandLine.add(String.format("-Dgemfire.log-file=%1$s", GEMFIRE_LOG_FILE_NAME));
commandLine.add(String.format("-Dgemfire.log-level=%1$s", GEMFIRE_LOG_LEVEL));
commandLine.add(String.format("-Dgemfire.Query.VERBOSE=%1$s", GEMFIRE_QUERY_DEBUG));
commandLine.addAll(extractJvmArguments(args));
commandLine.add("-classpath");
commandLine.add(System.getProperty("java.class.path"));
commandLine.add(type.getName());
commandLine.addAll(extractProgramArguments(args));
// System.err.printf("Java process command-line is (%1$s)%n", commandLine);
return commandLine;
}
/* (non-Javadoc) */
protected static List<String> extractJvmArguments(final String... args) {
List<String> jvmArgs = new ArrayList<String>(args.length);
for (String arg : args) {
if (arg.startsWith("-")) {
jvmArgs.add(arg);
}
}
return jvmArgs;
}
/* (non-Javadoc) */
protected static List<String> extractProgramArguments(final String... args) {
List<String> jvmArgs = new ArrayList<String>(args.length);
for (String arg : args) {
if (!arg.startsWith("-")) {
jvmArgs.add(arg);
}
}
return jvmArgs;
}
/* (non-Javadoc) */
protected static Process run(Class type, File directory, String... args) throws IOException {
return new ProcessBuilder()
.command(createJavaProcessCommandLine(type, args))
.directory(directory)
.start();
}
/* (non-Javadoc) */
protected static boolean waitForCacheServerToStart(CacheServer cacheServer) {
return waitForCacheServerToStart(cacheServer, DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
protected static boolean waitForCacheServerToStart(CacheServer cacheServer, long duration) {
return waitForCacheServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), duration);
}
/* (non-Javadoc) */
protected static boolean waitForCacheServerToStart(String host, int port) {
return waitForCacheServerToStart(host, port, DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
protected static boolean waitForCacheServerToStart(final String host, final int port, long duration) {
return waitOnCondition(new Condition() {
AtomicBoolean connected = new AtomicBoolean(false);
public boolean evaluate() {
Socket socket = null;
try {
if (!connected.get()) {
socket = new Socket(host, port);
connected.set(true);
}
}
catch (IOException ignore) {
}
finally {
GemFireUtils.close(socket);
}
return connected.get();
}
}, duration);
}
// NOTE this method would not be necessary except Spring Sessions' build does not fork the test JVM
// for every test class.
/* (non-Javadoc) */
protected static boolean waitForClientCacheToClose() {
return waitForClientCacheToClose(DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
protected static boolean waitForClientCacheToClose(long duration) {
try {
final ClientCache clientCache = ClientCacheFactory.getAnyInstance();
clientCache.close();
waitOnCondition(new Condition() {
public boolean evaluate() {
return clientCache.isClosed();
}
}, duration);
return clientCache.isClosed();
}
catch (CacheClosedException ignore) {
return true;
}
}
/* (non-Javadoc) */
protected static boolean waitForProcessToStart(Process process, File directory) {
return waitForProcessToStart(process, directory, DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
@SuppressWarnings("all")
protected static boolean waitForProcessToStart(Process process, File directory, long duration) {
final File processControl = new File(directory, DEFAULT_PROCESS_CONTROL_FILENAME);
waitOnCondition(new Condition() {
public boolean evaluate() {
return processControl.isFile();
}
}, duration);
return process.isAlive();
}
/* (non-Javadoc) */
protected static int waitForProcessToStop(Process process, File directory) {
return waitForProcessToStop(process, directory, DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
protected static int waitForProcessToStop(Process process, File directory, long duration) {
final long timeout = (System.currentTimeMillis() + duration);
try {
while (process.isAlive() && System.currentTimeMillis() < timeout) {
if (process.waitFor(DEFAULT_WAIT_INTERVAL, TimeUnit.MILLISECONDS)) {
return process.exitValue();
}
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return (process.isAlive() ? -1 : process.exitValue());
}
/* (non-Javadoc) */
protected static boolean waitOnCondition(Condition condition) {
return waitOnCondition(condition, DEFAULT_WAIT_DURATION);
}
/* (non-Javadoc) */
@SuppressWarnings("all")
protected static boolean waitOnCondition(Condition condition, long duration) {
final long timeout = (System.currentTimeMillis() + duration);
try {
while (!condition.evaluate() && System.currentTimeMillis() < timeout) {
synchronized (condition) {
TimeUnit.MILLISECONDS.timedWait(condition, DEFAULT_WAIT_INTERVAL);
}
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return condition.evaluate();
}
/* (non-Javadoc) */
protected static File writeProcessControlFile(File path) throws IOException {
assertThat(path != null && path.isDirectory()).isTrue();
File processControl = new File(path, DEFAULT_PROCESS_CONTROL_FILENAME);
assertThat(processControl.createNewFile()).isTrue();
processControl.deleteOnExit();
return processControl;
}
/* (non-Javadoc) */
protected void assertRegion(Region<?, ?> actualRegion, String expectedName, DataPolicy expectedDataPolicy) {
assertThat(actualRegion).isNotNull();
assertThat(actualRegion.getName()).isEqualTo(expectedName);
assertThat(actualRegion.getFullPath()).isEqualTo(GemFireUtils.toRegionPath(expectedName));
assertThat(actualRegion.getAttributes()).isNotNull();
assertThat(actualRegion.getAttributes().getDataPolicy()).isEqualTo(expectedDataPolicy);
}
/* (non-Javadoc) */
protected void assertEntryIdleTimeout(Region<?, ?> region, ExpirationAction expectedAction, int expectedTimeout) {
assertEntryIdleTimeout(region.getAttributes().getEntryIdleTimeout(), expectedAction, expectedTimeout);
}
/* (non-Javadoc) */
protected void assertEntryIdleTimeout(ExpirationAttributes actualExpirationAttributes,
ExpirationAction expectedAction, int expectedTimeout) {
assertThat(actualExpirationAttributes).isNotNull();
assertThat(actualExpirationAttributes.getAction()).isEqualTo(expectedAction);
assertThat(actualExpirationAttributes.getTimeout()).isEqualTo(expectedTimeout);
}
/* (non-Javadoc) */
protected boolean enableQueryDebugging() {
return DEFAULT_ENABLE_QUERY_DEBUGGING;
}
/* (non-Javadoc) */
protected boolean isQueryDebuggingEnabled() {
return (GEMFIRE_QUERY_DEBUG || enableQueryDebugging());
}
/* (non-Javadoc) */
protected List<String> listRegions(GemFireCache gemfireCache) {
Set<Region<?, ?>> regions = gemfireCache.rootRegions();
List<String> regionList = new ArrayList<String>(regions.size());
for (Region region : regions) {
regionList.add(region.getFullPath());
}
return regionList;
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
protected <T extends ExpiringSession> T createSession() {
T expiringSession = (T) gemfireSessionRepository.createSession();
assertThat(expiringSession).isNotNull();
return expiringSession;
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
protected <T extends ExpiringSession> T createSession(String principalName) {
GemFireOperationsSessionRepository.GemFireSession session = createSession();
session.setPrincipalName(principalName);
return (T) session;
}
/* (non-Javadoc) */
protected <T extends ExpiringSession> T expire(T session) {
session.setLastAccessedTime(0l);
return session;
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
protected <T extends ExpiringSession> T get(String sessionId) {
return (T) gemfireSessionRepository.getSession(sessionId);
}
/* (non-Javadoc) */
protected <T extends ExpiringSession> T save(T session) {
gemfireSessionRepository.save(session);
return session;
}
/* (non-Javadoc) */
protected <T extends ExpiringSession> T touch(T session) {
session.setLastAccessedTime(System.currentTimeMillis());
return session;
}
/**
* The SessionEventListener class is a Spring {@link ApplicationListener} listening for Spring HTTP Session
* application events.
*
* @see org.springframework.context.ApplicationListener
* @see org.springframework.session.events.AbstractSessionEvent
*/
public static class SessionEventListener implements ApplicationListener<AbstractSessionEvent> {
private volatile AbstractSessionEvent sessionEvent;
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
public <T extends AbstractSessionEvent> T getSessionEvent() {
T sessionEvent = (T) this.sessionEvent;
this.sessionEvent = null;
return sessionEvent;
}
/* (non-Javadoc) */
public void onApplicationEvent(AbstractSessionEvent event) {
sessionEvent = event;
}
/* (non-Javadoc) */
public <T extends AbstractSessionEvent> T waitForSessionEvent(long duration) {
waitOnCondition(new Condition() {
public boolean evaluate() {
return (sessionEvent != null);
}
}, duration);
return getSessionEvent();
}
}
/**
* The Condition interface defines a logical condition that must be satisfied before it is safe to proceed.
*/
protected interface Condition {
boolean evaluate();
}
}

View File

@@ -0,0 +1,371 @@
/*
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
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 org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
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.config.GemfireConstants;
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.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
import org.springframework.session.data.gemfire.support.GemFireUtils;
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.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.SocketUtils;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.RegionAttributes;
import com.gemstone.gemfire.cache.client.ClientCache;
import com.gemstone.gemfire.cache.client.Pool;
/**
* The ClientServerGemFireOperationsSessionRepositoryIntegrationTests class is a test suite of test cases testing
* the functionality of GemFire-backed Spring Sessions using a GemFire client-server topology.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
* @see org.springframework.test.context.web.WebAppConfiguration
* @see com.gemstone.gemfire.cache.Cache
* @see com.gemstone.gemfire.cache.client.ClientCache
* @see com.gemstone.gemfire.cache.client.Pool
* @see com.gemstone.gemfire.cache.server.CacheServer
* @since 1.0.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes =
ClientServerGemFireOperationsSessionRepositoryIntegrationTests.SpringSessionGemFireClientConfiguration.class)
@WebAppConfiguration
@SuppressWarnings("unused")
public class ClientServerGemFireOperationsSessionRepositoryIntegrationTests 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 {
final long t0 = System.currentTimeMillis();
final int port = SocketUtils.findAvailableTcpPort();
System.err.printf("Starting GemFire Server running on [%1$s] listening on port [%2$d]%n",
InetAddress.getLocalHost().getHostName(), 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(SpringSessionGemFireServerConfiguration.class, processWorkingDirectory,
String.format("-Dspring.session.data.gemfire.port=%1$d", port));
assertThat(waitForCacheServerToStart(SpringSessionGemFireServerConfiguration.SERVER_HOSTNAME, port)).isTrue();
//assertThat(waitForProcessToStart(gemfireServer, processWorkingDirectory)).isTrue();
System.err.printf("GemFire Server [startup time = %1$d ms]%n", System.currentTimeMillis() - t0);
}
@AfterClass
public static void stopGemFireServerAndDeleteArtifacts() {
if (gemfireServer != null) {
gemfireServer.destroyForcibly();
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();
}
@Before
public void setup() {
assertThat(GemFireUtils.isClient(gemfireCache)).isTrue();
Region<Object, ExpiringSession> springSessionGemFireRegion = gemfireCache.getRegion(
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
assertThat(springSessionGemFireRegion).isNotNull();
RegionAttributes<Object, ExpiringSession> springSessionGemFireRegionAttributes =
springSessionGemFireRegion.getAttributes();
assertThat(springSessionGemFireRegionAttributes).isNotNull();
assertThat(springSessionGemFireRegionAttributes.getDataPolicy()).isEqualTo(DataPolicy.EMPTY);
}
@After
public void tearDown() {
sessionEventListener.getSessionEvent();
}
@Test
public void createSessionFiresSessionCreatedEvent() {
final long beforeOrAtCreationTime = System.currentTimeMillis();
ExpiringSession expectedSession = save(createSession());
AbstractSessionEvent sessionEvent = sessionEventListener.waitForSessionEvent(500);
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
ExpiringSession createdSession = sessionEvent.getSession();
assertThat(createdSession).isEqualTo(expectedSession);
assertThat(createdSession.getId()).isNotNull();
assertThat(createdSession.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime);
assertThat(createdSession.getLastAccessedTime()).isEqualTo(createdSession.getCreationTime());
assertThat(createdSession.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
gemfireSessionRepository.delete(expectedSession.getId());
}
@Test
public void getExistingNonExpiredSessionBeforeAndAfterExpiration() {
ExpiringSession expectedSession = save(touch(createSession()));
AbstractSessionEvent sessionEvent = sessionEventListener.waitForSessionEvent(500);
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
assertThat(sessionEvent.<ExpiringSession>getSession()).isEqualTo(expectedSession);
assertThat(sessionEventListener.getSessionEvent()).isNull();
ExpiringSession savedSession = gemfireSessionRepository.getSession(expectedSession.getId());
assertThat(savedSession).isEqualTo(expectedSession);
// NOTE for some reason or another, performing a GemFire (Client)Cache Region.get(key)
// causes a Region CREATE event... o.O
// calling sessionEventListener.getSessionEvent() here to clear the event
sessionEventListener.getSessionEvent();
sessionEvent = sessionEventListener.waitForSessionEvent(TimeUnit.SECONDS.toMillis(
MAX_INACTIVE_INTERVAL_IN_SECONDS + 1));
assertThat(sessionEvent).isInstanceOf(SessionExpiredEvent.class);
assertThat(sessionEvent.<ExpiringSession>getSessionId()).isEqualTo(expectedSession.getId());
ExpiringSession expiredSession = gemfireSessionRepository.getSession(expectedSession.getId());
assertThat(expiredSession).isNull();
}
@Test
public void deleteExistingNonExpiredSessionFiresSessionDeletedEventAndReturnsNullOnGet() {
ExpiringSession expectedSession = save(touch(createSession()));
AbstractSessionEvent sessionEvent = sessionEventListener.waitForSessionEvent(500);
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
assertThat(sessionEvent.<ExpiringSession>getSession()).isEqualTo(expectedSession);
gemfireSessionRepository.delete(expectedSession.getId());
sessionEvent = sessionEventListener.waitForSessionEvent(500);
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
assertThat(sessionEvent.<ExpiringSession>getSessionId()).isEqualTo(expectedSession.getId());
ExpiringSession deletedSession = gemfireSessionRepository.getSession(expectedSession.getId());
assertThat(deletedSession).isNull();
}
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
static class SpringSessionGemFireClientConfiguration {
//TODO remove when SGF-458 is released
static {
System.setProperty("gemfire.log-level", GEMFIRE_LOG_LEVEL);
}
@Bean
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
//TODO uncomment when SGF-458 is released.
//gemfireProperties.setProperty("name", ClientServerGemFireOperationsSessionRepositoryIntegrationTests.class.getName());
//gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
return gemfireProperties;
}
@Bean(name = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME)
PoolFactoryBean gemfirePool(@Value("${spring.session.data.gemfire.port:"+DEFAULT_GEMFIRE_SERVER_PORT+"}") int port) {
PoolFactoryBean poolFactory = new PoolFactoryBean();
// TODO uncomment when SGF-458 is released
//poolFactory.setProperties(gemfireProperties());
poolFactory.setName(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME);
poolFactory.setFreeConnectionTimeout(5000); // 5 seconds
poolFactory.setKeepAlive(false);
poolFactory.setMaxConnections(SpringSessionGemFireServerConfiguration.MAX_CONNECTIONS);
poolFactory.setPingInterval(TimeUnit.SECONDS.toMillis(5));
poolFactory.setReadTimeout(2000); // 2 seconds
poolFactory.setRetryAttempts(2);
poolFactory.setSubscriptionEnabled(true);
poolFactory.setThreadLocalConnections(false);
poolFactory.setServerEndpoints(Collections.singletonList(new ConnectionEndpoint(
SpringSessionGemFireServerConfiguration.SERVER_HOSTNAME, port)));
return poolFactory;
}
@Bean
ClientCacheFactoryBean gemfireCache(Pool gemfirePool) {
ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean();
clientCacheFactory.setClose(true);
clientCacheFactory.setPool(gemfirePool);
clientCacheFactory.setProperties(gemfireProperties());
clientCacheFactory.setUseBeanFactoryLocator(false);
return clientCacheFactory;
}
@Bean
public SessionEventListener sessionEventListener() {
return new SessionEventListener();
}
// used for debugging purposes
public static void main(final String[] args) {
ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(
SpringSessionGemFireClientConfiguration.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 SpringSessionGemFireServerConfiguration {
static final int MAX_CONNECTIONS = 50;
static final String SERVER_HOSTNAME = "localhost";
@Bean
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", SpringSessionGemFireServerConfiguration.class.getName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-file", "server.log");
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
return gemfireProperties;
}
@Bean
CacheFactoryBean gemfireCache() {
CacheFactoryBean cacheFactory = new CacheFactoryBean();
cacheFactory.setProperties(gemfireProperties());
cacheFactory.setUseBeanFactoryLocator(false);
return cacheFactory;
}
@Bean
CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache,
@Value("${spring.session.data.gemfire.port:"+DEFAULT_GEMFIRE_SERVER_PORT+"}") int port) {
CacheServerFactoryBean cacheServerFactory = new CacheServerFactoryBean();
cacheServerFactory.setAutoStartup(true);
cacheServerFactory.setBindAddress(SERVER_HOSTNAME);
cacheServerFactory.setCache(gemfireCache);
cacheServerFactory.setMaxConnections(MAX_CONNECTIONS);
cacheServerFactory.setPort(port);
return cacheServerFactory;
}
public static void main(final String[] args) throws IOException {
new AnnotationConfigApplicationContext(SpringSessionGemFireServerConfiguration.class).registerShutdownHook();
writeProcessControlFile(WORKING_DIRECTORY);
}
}
}

View File

@@ -0,0 +1,304 @@
/*
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.session.ExpiringSession;
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.ExpirationAction;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.query.Query;
import com.gemstone.gemfire.cache.query.QueryService;
import com.gemstone.gemfire.cache.query.SelectResults;
import com.gemstone.gemfire.pdx.PdxReader;
import com.gemstone.gemfire.pdx.PdxSerializable;
import com.gemstone.gemfire.pdx.PdxWriter;
/**
* The GemFireOperationsSessionRepositoryIntegrationTests class is a test suite of test cases testing
* the findByPrincipalName query method on the GemFireOpeationsSessionRepository class.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
* @see org.springframework.test.context.web.WebAppConfiguration
* @since 1.1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
@SuppressWarnings("unused")
public class GemFireOperationsSessionRepositoryIntegrationTests extends AbstractGemFireIntegrationTests {
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 300;
private static final String GEMFIRE_LOG_LEVEL = "warning";
private static final String SPRING_SESSION_GEMFIRE_REGION_NAME = "TestPartitionedSessions";
@Before
public void setup() {
assertThat(gemfireCache).isNotNull();
assertThat(gemfireSessionRepository).isNotNull();
assertThat(gemfireSessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(
MAX_INACTIVE_INTERVAL_IN_SECONDS);
Region<Object, ExpiringSession> sessionRegion = gemfireCache.getRegion(SPRING_SESSION_GEMFIRE_REGION_NAME);
assertRegion(sessionRegion, SPRING_SESSION_GEMFIRE_REGION_NAME, DataPolicy.PARTITION);
assertEntryIdleTimeout(sessionRegion, ExpirationAction.INVALIDATE, MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
protected Map<String, ExpiringSession> doFindByPrincipalName(String principalName) {
return gemfireSessionRepository.findByPrincipalName(principalName);
}
@SuppressWarnings("unchecked")
protected Map<String, ExpiringSession> doFindByPrincipalName(String regionName, String principalName) {
try {
Region<String, ExpiringSession> region = gemfireCache.getRegion(regionName);
assertThat(region).isNotNull();
QueryService queryService = region.getRegionService().getQueryService();
String queryString = String.format("SELECT s FROM %1$s s WHERE s.principalName = $1", region.getFullPath());
Query query = queryService.newQuery(queryString);
SelectResults<ExpiringSession> results = (SelectResults<ExpiringSession>) query.execute(
new Object[] { principalName });
Map<String, ExpiringSession> sessions = new HashMap<String, ExpiringSession>(results.size());
for (ExpiringSession session : results.asList()) {
sessions.put(session.getId(), session);
}
return sessions;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
protected boolean enableQueryDebugging() {
return true;
}
@Test
public void findSessionsByPrincipalName() {
ExpiringSession sessionOne = save(touch(createSession("robWinch")));
ExpiringSession sessionTwo = save(touch(createSession("johnBlum")));
ExpiringSession sessionThree = save(touch(createSession("robWinch")));
ExpiringSession sessionFour = save(touch(createSession("johnBlum")));
ExpiringSession sessionFive = save(touch(createSession("robWinch")));
assertThat(get(sessionOne.getId())).isEqualTo(sessionOne);
assertThat(get(sessionTwo.getId())).isEqualTo(sessionTwo);
assertThat(get(sessionThree.getId())).isEqualTo(sessionThree);
assertThat(get(sessionFour.getId())).isEqualTo(sessionFour);
assertThat(get(sessionFive.getId())).isEqualTo(sessionFive);
Map<String, ExpiringSession> johnBlumSessions = doFindByPrincipalName("johnBlum");
assertThat(johnBlumSessions).isNotNull();
assertThat(johnBlumSessions.size()).isEqualTo(2);
assertThat(johnBlumSessions.containsKey(sessionOne.getId())).isFalse();
assertThat(johnBlumSessions.containsKey(sessionThree.getId())).isFalse();
assertThat(johnBlumSessions.containsKey(sessionFive.getId())).isFalse();
assertThat(johnBlumSessions.get(sessionTwo.getId())).isEqualTo(sessionTwo);
assertThat(johnBlumSessions.get(sessionFour.getId())).isEqualTo(sessionFour);
Map<String, ExpiringSession> robWinchSessions = doFindByPrincipalName("robWinch");
assertThat(robWinchSessions).isNotNull();
assertThat(robWinchSessions.size()).isEqualTo(3);
assertThat(robWinchSessions.containsKey(sessionTwo.getId())).isFalse();
assertThat(robWinchSessions.containsKey(sessionFour.getId())).isFalse();
assertThat(robWinchSessions.get(sessionOne.getId())).isEqualTo(sessionOne);
assertThat(robWinchSessions.get(sessionThree.getId())).isEqualTo(sessionThree);
assertThat(robWinchSessions.get(sessionFive.getId())).isEqualTo(sessionFive);
}
@Test
public void findsNoSessionsByNonExistingPrincipal() {
Map<String, ExpiringSession> nonExistingPrincipalSessions = doFindByPrincipalName("nonExistingPrincipalName");
assertThat(nonExistingPrincipalSessions).isNotNull();
assertThat(nonExistingPrincipalSessions.isEmpty()).isTrue();
}
@Test
public void saveAndReadSessionWithAttributes() {
ExpiringSession expectedSession = gemfireSessionRepository.createSession();
assertThat(expectedSession).isInstanceOf(GemFireSession.class);
((GemFireSession) expectedSession).setPrincipalName("jblum");
List<String> expectedAttributeNames = Arrays.asList(
"booleanAttribute", "numericAttribute", "stringAttribute", "personAttribute");
Person jonDoe = new Person("Jon", "Doe");
expectedSession.setAttribute(expectedAttributeNames.get(0), true);
expectedSession.setAttribute(expectedAttributeNames.get(1), Math.PI);
expectedSession.setAttribute(expectedAttributeNames.get(2), "test");
expectedSession.setAttribute(expectedAttributeNames.get(3), jonDoe);
gemfireSessionRepository.save(touch(expectedSession));
ExpiringSession savedSession = gemfireSessionRepository.getSession(expectedSession.getId());
assertThat(savedSession).isEqualTo(expectedSession);
assertThat(savedSession).isInstanceOf(GemFireSession.class);
assertThat(((GemFireSession) savedSession).getPrincipalName()).isEqualTo("jblum");
assertThat(savedSession.getAttributeNames().containsAll(expectedAttributeNames)).as(
String.format("Expected (%1$s); but was (%2$s)", expectedAttributeNames,savedSession.getAttributeNames()))
.isTrue();
assertThat(Boolean.valueOf(String.valueOf(savedSession.getAttribute(expectedAttributeNames.get(0))))).isTrue();
assertThat(Double.valueOf(String.valueOf(savedSession.getAttribute(expectedAttributeNames.get(1)))))
.isEqualTo(Math.PI);
assertThat(String.valueOf(savedSession.getAttribute(expectedAttributeNames.get(2)))).isEqualTo("test");
assertThat(savedSession.getAttribute(expectedAttributeNames.get(3))).isEqualTo(jonDoe);
}
@EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME,
maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
static class SpringSessionGemFireConfiguration {
@Bean
Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", GemFireOperationsSessionRepositoryIntegrationTests.class.getName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
return gemfireProperties;
}
@Bean
CacheFactoryBean gemfireCache() {
CacheFactoryBean gemfireCache = new CacheFactoryBean();
gemfireCache.setLazyInitialize(false);
gemfireCache.setProperties(gemfireProperties());
gemfireCache.setUseBeanFactoryLocator(false);
return gemfireCache;
}
}
public static class Person implements PdxSerializable {
private String firstName;
private String lastName;
public Person() {
}
public Person(String firstName, String lastName) {
this.firstName = validate(firstName);
this.lastName = validate(lastName);
}
private String validate(String value) {
Assert.hasText(value, String.format("The String value (%1$s) must be specified!", value));
return value;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getName() {
return String.format("%1$s %2$s", getFirstName(), getLastName());
}
public void toData(PdxWriter pdxWriter) {
pdxWriter.writeString("firstName", getFirstName());
pdxWriter.writeString("lastName", getLastName());
}
public void fromData(final PdxReader pdxReader) {
this.firstName = pdxReader.readString("firstName");
this.lastName = pdxReader.readString("lastName");
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Person)) {
return false;
}
Person that = (Person) obj;
return ObjectUtils.nullSafeEquals(this.getFirstName(), that.getFirstName())
&& ObjectUtils.nullSafeEquals(this.getLastName(), that.getLastName());
}
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getFirstName());
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getLastName());
return hashValue;
}
@Override
public String toString() {
return getName();
}
}
}

View File

@@ -0,0 +1,253 @@
/*
* Copyright 2002-2015 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.config.annotation.web.http;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
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.data.gemfire.CacheFactoryBean;
import org.springframework.session.ExpiringSession;
import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests;
import org.springframework.session.data.gemfire.support.GemFireUtils;
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.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.ExpirationAction;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.RegionShortcut;
/**
* The EnableGemFireHttpSessionEventsIntegrationTests class is a test suite of test cases testing the Session Event
* functionality and behavior of the GemFireOperationsSessionRepository and GemFire's configuration.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.springframework.session.ExpiringSession
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
* @see org.springframework.session.events.SessionCreatedEvent
* @see org.springframework.session.events.SessionDeletedEvent
* @see org.springframework.session.events.SessionExpiredEvent
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
* @see org.springframework.test.context.web.WebAppConfiguration
* @see com.gemstone.gemfire.cache.Region
* @since 1.1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
@SuppressWarnings("unused")
public class EnableGemFireHttpSessionEventsIntegrationTests extends AbstractGemFireIntegrationTests {
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
private static final String GEMFIRE_LOG_LEVEL = "warning";
private static final String SPRING_SESSION_GEMFIRE_REGION_NAME = "TestReplicatedSessions";
@Autowired
private SessionEventListener sessionEventListener;
@Before
public void setup() {
assertThat(GemFireUtils.isPeer(gemfireCache)).isTrue();
assertThat(gemfireSessionRepository).isNotNull();
assertThat(gemfireSessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(
MAX_INACTIVE_INTERVAL_IN_SECONDS);
assertThat(sessionEventListener).isNotNull();
Region<Object, ExpiringSession> sessionRegion = gemfireCache.getRegion(SPRING_SESSION_GEMFIRE_REGION_NAME);
assertRegion(sessionRegion, SPRING_SESSION_GEMFIRE_REGION_NAME, DataPolicy.REPLICATE);
assertEntryIdleTimeout(sessionRegion, ExpirationAction.INVALIDATE, MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
@After
public void tearDown() {
sessionEventListener.getSessionEvent();
}
@Test
public void sessionCreatedEvent() {
final long beforeOrAtCreationTime = System.currentTimeMillis();
ExpiringSession expectedSession = save(createSession());
AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent();
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
ExpiringSession createdSession = sessionEvent.getSession();
assertThat(createdSession).isEqualTo(expectedSession);
assertThat(createdSession.getId()).isNotNull();
assertThat(createdSession.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime);
assertThat(createdSession.getLastAccessedTime()).isEqualTo(createdSession.getCreationTime());
assertThat(createdSession.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
assertThat(createdSession.isExpired()).isFalse();
}
@Test
public void getExistingNonExpiredSession() {
ExpiringSession expectedSession = save(touch(createSession()));
assertThat(expectedSession.isExpired()).isFalse();
// NOTE though unlikely, a possible race condition exists between save and get...
ExpiringSession savedSession = gemfireSessionRepository.getSession(expectedSession.getId());
assertThat(savedSession).isEqualTo(expectedSession);
}
@Test
public void getExistingExpiredSession() {
ExpiringSession expectedSession = save(expire(createSession()));
AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent();
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
ExpiringSession createdSession = sessionEvent.getSession();
assertThat(createdSession).isEqualTo(expectedSession);
assertThat(createdSession.isExpired()).isTrue();
assertThat(gemfireSessionRepository.getSession(createdSession.getId())).isNull();
}
@Test
public void getNonExistingSession() {
assertThat(gemfireSessionRepository.getSession(UUID.randomUUID().toString())).isNull();
}
@Test
public void deleteExistingNonExpiredSession() {
ExpiringSession expectedSession = save(touch(createSession()));
ExpiringSession savedSession = gemfireSessionRepository.getSession(expectedSession.getId());
assertThat(savedSession).isEqualTo(expectedSession);
assertThat(savedSession.isExpired()).isFalse();
gemfireSessionRepository.delete(savedSession.getId());
AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent();
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
assertThat(sessionEvent.getSessionId()).isEqualTo(savedSession.getId());
ExpiringSession deletedSession = sessionEvent.getSession();
assertThat(deletedSession).isEqualTo(savedSession);
assertThat(gemfireSessionRepository.getSession(deletedSession.getId())).isNull();
}
@Test
public void deleteExistingExpiredSession() {
ExpiringSession expectedSession = save(createSession());
AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent();
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
ExpiringSession createdSession = sessionEvent.getSession();
assertThat(createdSession).isEqualTo(expectedSession);
sessionEvent = sessionEventListener.waitForSessionEvent(TimeUnit.SECONDS.toMillis(
gemfireSessionRepository.getMaxInactiveIntervalInSeconds() + 1));
assertThat(sessionEvent).isInstanceOf(SessionExpiredEvent.class);
ExpiringSession expiredSession = sessionEvent.getSession();
assertThat(expiredSession).isEqualTo(createdSession);
assertThat(expiredSession.isExpired()).isTrue();
gemfireSessionRepository.delete(expectedSession.getId());
sessionEvent = sessionEventListener.getSessionEvent();
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
assertThat(sessionEvent.getSession()).isNull();
assertThat(sessionEvent.getSessionId()).isEqualTo(expiredSession.getId());
assertThat(gemfireSessionRepository.getSession(sessionEvent.getSessionId())).isNull();
}
@Test
public void deleteNonExistingSession() {
String expectedSessionId = UUID.randomUUID().toString();
assertThat(gemfireSessionRepository.getSession(expectedSessionId)).isNull();
gemfireSessionRepository.delete(expectedSessionId);
AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent();
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
assertThat(sessionEvent.getSession()).isNull();
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
}
@EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME,
maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS,
serverRegionShortcut = RegionShortcut.REPLICATE)
static class SpringSessionGemFireConfiguration {
@Bean
Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", EnableGemFireHttpSessionEventsIntegrationTests.class.getName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
return gemfireProperties;
}
@Bean
CacheFactoryBean gemfireCache() {
CacheFactoryBean gemfireCache = new CacheFactoryBean();
gemfireCache.setLazyInitialize(false);
gemfireCache.setProperties(gemfireProperties());
gemfireCache.setUseBeanFactoryLocator(false);
return gemfireCache;
}
@Bean
SessionEventListener sessionEventListener() {
return new SessionEventListener();
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2002-2015 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.config.annotation.web.http;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.ExpiringSession;
import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.ExpirationAction;
import com.gemstone.gemfire.cache.Region;
/**
* The GemFireHttpSessionConfigurationXmlTests class is a test suite of test cases testing the configuration of
* Spring Session backed by GemFire using XML configuration meta-data.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.session.ExpiringSession
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.web.WebAppConfiguration
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
* @since 1.1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
@SuppressWarnings("unused")
public class GemFireHttpSessionConfigurationXmlTests extends AbstractGemFireIntegrationTests {
@Autowired
private Cache gemfireCache;
@Test
public void gemfireCacheConfigurationIsValid() {
assertThat(gemfireCache).isNotNull();
Region<Object, ExpiringSession> example = gemfireCache.getRegion("Example");
assertRegion(example, "Example", DataPolicy.NORMAL);
assertEntryIdleTimeout(example, ExpirationAction.INVALIDATE, 3600);
}
}

View File

@@ -0,0 +1,766 @@
/*
* Copyright 2002-2015 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.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.data.gemfire.GemfireAccessor;
import org.springframework.data.gemfire.GemfireOperations;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByPrincipalNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
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.StringUtils;
import com.gemstone.gemfire.DataSerializable;
import com.gemstone.gemfire.DataSerializer;
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.Region;
import com.gemstone.gemfire.cache.util.CacheListenerAdapter;
/**
* AbstractGemFireOperationsSessionRepository is an abstract base class encapsulating functionality common
* to all implementations that support SessionRepository operations backed by GemFire.
*
* @author John Blum
* @see org.springframework.beans.factory.InitializingBean
* @see org.springframework.context.ApplicationEventPublisher
* @see org.springframework.context.ApplicationEventPublisherAware
* @see org.springframework.data.gemfire.GemfireAccessor
* @see org.springframework.data.gemfire.GemfireOperations
* @see org.springframework.session.ExpiringSession
* @see org.springframework.session.FindByPrincipalNameSessionRepository
* @see org.springframework.session.Session
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
* @see com.gemstone.gemfire.cache.Region
* @see com.gemstone.gemfire.cache.util.CacheListenerAdapter
* @since 1.1.0
*/
public abstract class AbstractGemFireOperationsSessionRepository extends CacheListenerAdapter<Object, ExpiringSession>
implements InitializingBean, FindByPrincipalNameSessionRepository<ExpiringSession>,
ApplicationEventPublisherAware {
private int maxInactiveIntervalInSeconds = GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS;
private ApplicationEventPublisher applicationEventPublisher = new ApplicationEventPublisher() {
public void publishEvent(ApplicationEvent event) {
}
};
private final GemfireOperations template;
protected final Log logger = newLogger();
private String fullyQualifiedRegionName;
/**
* Constructs an instance of AbstractGemFireOperationsSessionRepository with a required GemfireOperations instance
* used to perform GemFire data access operations and interactions supporting the SessionRepository operations.
*
* @param template the GemfireOperations instance used to interact with GemFire.
* @see org.springframework.data.gemfire.GemfireOperations
*/
public AbstractGemFireOperationsSessionRepository(GemfireOperations template) {
Assert.notNull(template, "GemfireOperations must not be null");
this.template = template;
}
/**
* Used for testing purposes only to override the Log implementation with a mock.
*
* @return an instance of Log constructed from Apache commons-logging LogFactory.
* @see org.apache.commons.logging.LogFactory#getLog(Class)
*/
Log newLogger() {
return LogFactory.getLog(getClass());
}
/**
* Sets the ApplicationEventPublisher used to publish Session events corresponding to GemFire cache events.
*
* @param applicationEventPublisher the Spring ApplicationEventPublisher used to publish Session-based events.
* @see org.springframework.context.ApplicationEventPublisher
*/
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
Assert.notNull(applicationEventPublisher, "ApplicationEventPublisher must not be null");
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* Gets the ApplicationEventPublisher used to publish Session events corresponding to GemFire cache events.
*
* @return the Spring ApplicationEventPublisher used to publish Session-based events.
* @see org.springframework.context.ApplicationEventPublisher
*/
protected ApplicationEventPublisher getApplicationEventPublisher() {
return applicationEventPublisher;
}
/**
* Gets the fully-qualified name of the GemFire cache {@link Region} used to store and manage Session data.
*
* @return a String indicating the fully qualified name of the GemFire cache {@link Region} used to store
* and manage Session data.
*/
protected String getFullyQualifiedRegionName() {
return fullyQualifiedRegionName;
}
/**
* Sets the maximum interval in seconds in which a Session can remain inactive before it is considered expired.
*
* @param maxInactiveIntervalInSeconds an integer value specifying the maximum interval in seconds that a Session
* can remain inactive before it is considered expired.
*/
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}
/**
* Gets the maximum interval in seconds in which a Session can remain inactive before it is considered expired.
*
* @return an integer value specifying the maximum interval in seconds that a Session can remain inactive
* before it is considered expired.
*/
public int getMaxInactiveIntervalInSeconds() {
return maxInactiveIntervalInSeconds;
}
/**
* Gets a reference to the GemfireOperations (template) used to perform data access operations
* and other interactions on the GemFire cache {@link Region} backing this SessionRepository.
*
* @return a reference to the GemfireOperations used to interact with GemFire.
* @see org.springframework.data.gemfire.GemfireOperations
*/
public GemfireOperations getTemplate() {
return template;
}
/**
* Callback method during Spring bean initialization that will capture the fully-qualified name
* of the GemFire cache {@link Region} used to manage Session state and register this SessionRepository
* as a GemFire {@link com.gemstone.gemfire.cache.CacheListener}.
*
* @throws Exception if an error occurs during the initialization process.
*/
public void afterPropertiesSet() throws Exception {
GemfireOperations template = getTemplate();
Assert.isInstanceOf(GemfireAccessor.class, template);
Region<Object, ExpiringSession> region = ((GemfireAccessor) template).getRegion();
fullyQualifiedRegionName = region.getFullPath();
region.getAttributesMutator().addCacheListener(this);
}
/**
* 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.
* @see com.gemstone.gemfire.cache.EntryEvent
* @see #handleCreated(String, ExpiringSession)
*/
@Override
public void afterCreate(EntryEvent<Object, ExpiringSession> event) {
handleCreated(event.getKey().toString(), event.getNewValue());
}
/**
* Callback method triggered when an entry is destroyed in the GemFire cache {@link Region}.
*
* @param event an EntryEvent containing the details of the cache operation.
* @see com.gemstone.gemfire.cache.EntryEvent
* @see #handleDestroyed(String, ExpiringSession)
*/
@Override
public void afterDestroy(EntryEvent<Object, ExpiringSession> event) {
handleDestroyed(event.getKey().toString(), event.getOldValue());
}
/**
* Callback method triggered when an entry is invalidated in the GemFire cache {@link Region}.
*
* @param event an EntryEvent containing the details of the cache operation.
* @see com.gemstone.gemfire.cache.EntryEvent
* @see #handleExpired(String, ExpiringSession)
*/
@Override
public void afterInvalidate(EntryEvent<Object, ExpiringSession> event) {
handleExpired(event.getKey().toString(), event.getOldValue());
}
/**
* Causes Session created events to be published to the Spring application context.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionCreatedEvent
* @see org.springframework.session.ExpiringSession
* @see #publishEvent(ApplicationEvent)
*/
protected void handleCreated(String sessionId, ExpiringSession session) {
publishEvent(session != null ? new SessionCreatedEvent(this, session)
: new SessionCreatedEvent(this, sessionId));
}
/**
* Causes Session deleted events to be published to the Spring application context.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionDeletedEvent
* @see org.springframework.session.ExpiringSession
* @see #publishEvent(ApplicationEvent)
*/
protected void handleDeleted(String sessionId, ExpiringSession session) {
publishEvent(session != null ? new SessionDeletedEvent(this, session)
: new SessionDeletedEvent(this, sessionId));
}
/**
* Causes Session destroyed events to be published to the Spring application context.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionDestroyedEvent
* @see org.springframework.session.ExpiringSession
* @see #publishEvent(ApplicationEvent)
*/
protected void handleDestroyed(String sessionId, ExpiringSession session) {
publishEvent(session != null ? new SessionDestroyedEvent(this, session)
: new SessionDestroyedEvent(this, sessionId));
}
/**
* Causes Session expired events to be published to the Spring application context.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionExpiredEvent
* @see org.springframework.session.ExpiringSession
* @see #publishEvent(ApplicationEvent)
*/
protected void handleExpired(String sessionId, ExpiringSession session) {
publishEvent(session != null ? new SessionExpiredEvent(this, session)
: new SessionExpiredEvent(this, sessionId));
}
/**
* Publishes the specified ApplicationEvent to the Spring application context.
*
* @param event the ApplicationEvent to publish.
* @see org.springframework.context.ApplicationEventPublisher#publishEvent(ApplicationEvent)
* @see org.springframework.context.ApplicationEvent
*/
protected void publishEvent(ApplicationEvent event) {
try {
getApplicationEventPublisher().publishEvent(event);
}
catch (Throwable t) {
logger.error(String.format("error occurred publishing event (%1$s)", event), t);
}
}
/**
* GemFireSession is a GemFire representation model of a Spring {@link ExpiringSession} for storing and accessing
* Session state information in GemFire. This class implements GemFire's {@link DataSerializable} interface
* to better handle replication of Session information across the GemFire cluster.
*
* @see java.lang.Comparable
* @see org.springframework.session.ExpiringSession
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes
* @see com.gemstone.gemfire.DataSerializable
* @see com.gemstone.gemfire.DataSerializer
* @see com.gemstone.gemfire.Delta
* @see com.gemstone.gemfire.Instantiator
*/
public static class GemFireSession implements Comparable<ExpiringSession>, DataSerializable, Delta, ExpiringSession {
protected static final boolean DEFAULT_ALLOW_JAVA_SERIALIZATION = true;
protected static final DateFormat TO_STRING_DATE_FORMAT = new SimpleDateFormat("YYYY-MM-dd-HH-mm-ss");
static {
Instantiator.register(new Instantiator(GemFireSession.class, 800813552) {
@Override public DataSerializable newInstance() {
return new GemFireSession();
}
});
}
private transient boolean delta = false;
private int maxInactiveIntervalInSeconds;
private long creationTime;
private long lastAccessedTime;
private transient final GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes(this);
private String id;
/* (non-Javadoc) */
protected GemFireSession() {
this(UUID.randomUUID().toString());
}
/* (non-Javadoc) */
protected GemFireSession(String id) {
this.id = validateId(id);
this.creationTime = System.currentTimeMillis();
this.lastAccessedTime = this.creationTime;
}
/* (non-Javadoc) */
protected GemFireSession(ExpiringSession session) {
Assert.notNull(session, "The ExpiringSession to copy cannot be null");
this.id = session.getId();
this.creationTime = session.getCreationTime();
this.lastAccessedTime = session.getLastAccessedTime();
this.maxInactiveIntervalInSeconds = session.getMaxInactiveIntervalInSeconds();
this.sessionAttributes.from(session);
}
/* (non-Javadoc) */
public static GemFireSession create(int maxInactiveIntervalInSeconds) {
GemFireSession session = new GemFireSession();
session.setMaxInactiveIntervalInSeconds(maxInactiveIntervalInSeconds);
return session;
}
/* (non-Javadoc) */
public static GemFireSession from(ExpiringSession expiringSession) {
GemFireSession session = new GemFireSession(expiringSession);
session.setLastAccessedTime(System.currentTimeMillis());
return session;
}
/* (non-Javadoc) */
private String validateId(String id) {
Assert.hasText(id, "ID must be specified");
return id;
}
/* (non-Javadoc) */
protected boolean allowJavaSerialization() {
return DEFAULT_ALLOW_JAVA_SERIALIZATION;
}
/* (non-Javadoc) */
public synchronized String getId() {
return id;
}
/* (non-Javadoc) */
public synchronized long getCreationTime() {
return creationTime;
}
/* (non-Javadoc) */
public void setAttribute(String attributeName, Object attributeValue) {
sessionAttributes.setAttribute(attributeName, attributeValue);
}
/* (non-Javadoc) */
public void removeAttribute(String attributeName) {
sessionAttributes.removeAttribute(attributeName);
}
/* (non-Javadoc) */
public <T> T getAttribute(String attributeName) {
return sessionAttributes.getAttribute(attributeName);
}
/* (non-Javadoc) */
public Set<String> getAttributeNames() {
return sessionAttributes.getAttributeNames();
}
/* (non-Javadoc) */
public synchronized boolean isExpired() {
long lastAccessedTime = getLastAccessedTime();
long maxInactiveIntervalInSeconds = getMaxInactiveIntervalInSeconds();
return (maxInactiveIntervalInSeconds >= 0
&& (idleTimeout(maxInactiveIntervalInSeconds) >= lastAccessedTime));
}
/* (non-Javadoc) */
private long idleTimeout(long maxInactiveIntervalInSeconds) {
return (System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(maxInactiveIntervalInSeconds));
}
/* (non-Javadoc) */
public synchronized void setLastAccessedTime(long lastAccessedTime) {
this.delta |= (this.lastAccessedTime != lastAccessedTime);
this.lastAccessedTime = lastAccessedTime;
}
/* (non-Javadoc) */
public synchronized long getLastAccessedTime() {
return lastAccessedTime;
}
/* (non-Javadoc) */
public synchronized void setMaxInactiveIntervalInSeconds(final int maxInactiveIntervalInSeconds) {
this.delta |= (this.maxInactiveIntervalInSeconds != maxInactiveIntervalInSeconds);
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}
/* (non-Javadoc) */
public synchronized int getMaxInactiveIntervalInSeconds() {
return maxInactiveIntervalInSeconds;
}
/* (non-Javadoc) */
public synchronized void setPrincipalName(String principalName) {
setAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME, principalName);
}
/* (non-Javadoc) */
public synchronized String getPrincipalName() {
return getAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME);
}
/* (non-Javadoc) */
public synchronized void toData(DataOutput out) throws IOException {
out.writeUTF(getId());
out.writeLong(getCreationTime());
out.writeLong(getLastAccessedTime());
out.writeInt(getMaxInactiveIntervalInSeconds());
String principalName = getPrincipalName();
int length = (StringUtils.hasText(principalName) ? principalName.length() : 0);
out.writeInt(length);
if (length > 0) {
out.writeUTF(principalName);
}
writeObject(sessionAttributes, out);
this.delta = false;
}
/* (non-Javadoc) */
void writeObject(Object obj, DataOutput out) throws IOException {
DataSerializer.writeObject(obj, out, allowJavaSerialization());
}
/* (non-Javadoc) */
public synchronized void fromData(DataInput in) throws ClassNotFoundException, IOException {
id = in.readUTF();
creationTime = in.readLong();
setLastAccessedTime(in.readLong());
setMaxInactiveIntervalInSeconds(in.readInt());
int principalNameLength = in.readInt();
if (principalNameLength > 0) {
setPrincipalName(in.readUTF());
}
sessionAttributes.from(this.<GemFireSessionAttributes>readObject(in));
this.delta = false;
}
/* (non-Javadoc) */
<T> T readObject(DataInput in) throws ClassNotFoundException, IOException {
return DataSerializer.readObject(in);
}
/* (non-Javadoc) */
public synchronized boolean hasDelta() {
return (delta || sessionAttributes.hasDelta());
}
/* (non-Javadoc) */
public synchronized void toDelta(DataOutput out) throws IOException {
out.writeLong(getLastAccessedTime());
out.writeInt(getMaxInactiveIntervalInSeconds());
sessionAttributes.toDelta(out);
this.delta = false;
}
/* (non-Javadoc) */
public synchronized void fromDelta(DataInput in) throws IOException {
setLastAccessedTime(in.readLong());
setMaxInactiveIntervalInSeconds(in.readInt());
sessionAttributes.fromDelta(in);
this.delta = false;
}
/* (non-Javadoc) */
@SuppressWarnings("all")
public int compareTo(ExpiringSession session) {
return (Long.valueOf(getCreationTime()).compareTo(session.getCreationTime()));
}
/* (non-Javadoc) */
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Session)) {
return false;
}
Session that = (Session) obj;
return this.getId().equals(that.getId());
}
/* (non-Javadoc) */
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + getId().hashCode();
return hashValue;
}
/* (non-Javadoc) */
@Override
public synchronized String toString() {
return String.format("{ @type = %1$s, id = %2$s, creationTime = %3$s, lastAccessedTime = %4$s"
+ ", maxInactiveIntervalInSeconds = %5$s, principalName = %6$s }", getClass().getName(), getId(),
toString(getCreationTime()), toString(getLastAccessedTime()), getMaxInactiveIntervalInSeconds(),
getPrincipalName());
}
/* (non-Javadoc) */
private String toString(long timestamp) {
return TO_STRING_DATE_FORMAT.format(new Date(timestamp));
}
}
/**
* The GemFireSessionAttributes class is a container for a Session attributes that implements both
* the {@link DataSerializable} and {@link Delta} GemFire interfaces for efficient storage and distribution
* (replication) in GemFire.
*
* @see com.gemstone.gemfire.DataSerializable
* @see com.gemstone.gemfire.DataSerializer
* @see com.gemstone.gemfire.Delta
* @see com.gemstone.gemfire.Instantiator
*/
public static class GemFireSessionAttributes implements DataSerializable, Delta {
protected static final boolean DEFAULT_ALLOW_JAVA_SERIALIZATION = true;
static {
Instantiator.register(new Instantiator(GemFireSessionAttributes.class, 800828008) {
@Override public DataSerializable newInstance() {
return new GemFireSessionAttributes();
}
});
}
private transient final Map<String, Object> sessionAttributes = new HashMap<String, Object>();
private transient final Map<String, Object> sessionAttributeDeltas = new HashMap<String, Object>();
private transient final Object lock;
/* (non-Javadoc) */
protected GemFireSessionAttributes() {
this.lock = this;
}
/* (non-Javadoc) */
protected GemFireSessionAttributes(Object lock) {
this.lock = (lock != null ? lock : this);
}
/* (non-Javadoc) */
public void setAttribute(String attributeName, Object attributeValue) {
synchronized (lock) {
if (attributeValue != null) {
if (!attributeValue.equals(sessionAttributes.put(attributeName, attributeValue))) {
sessionAttributeDeltas.put(attributeName, attributeValue);
}
}
else {
removeAttribute(attributeName);
}
}
}
/* (non-Javadoc) */
public void removeAttribute(String attributeName) {
synchronized (lock) {
if (sessionAttributes.remove(attributeName) != null) {
sessionAttributeDeltas.put(attributeName, null);
}
}
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
public <T> T getAttribute(String attributeName) {
synchronized (lock) {
return (T) sessionAttributes.get(attributeName);
}
}
/* (non-Javadoc) */
public Set<String> getAttributeNames() {
synchronized (lock) {
return Collections.unmodifiableSet(new HashSet<String>(sessionAttributes.keySet()));
}
}
/* (non-Javadoc) */
protected boolean allowJavaSerialization() {
return DEFAULT_ALLOW_JAVA_SERIALIZATION;
}
/* (non-Javadoc) */
public void from(Session session) {
synchronized (lock) {
for (String attributeName : session.getAttributeNames()) {
setAttribute(attributeName, session.getAttribute(attributeName));
}
}
}
/* (non-Javadoc) */
public void from(GemFireSessionAttributes sessionAttributes) {
synchronized (lock) {
for (String attributeName : sessionAttributes.getAttributeNames()) {
setAttribute(attributeName, sessionAttributes.getAttribute(attributeName));
}
}
}
/* (non-Javadoc) */
public void toData(DataOutput out) throws IOException {
synchronized (lock) {
Set<String> attributeNames = getAttributeNames();
out.writeInt(attributeNames.size());
for (String attributeName : attributeNames) {
out.writeUTF(attributeName);
writeObject(getAttribute(attributeName), out);
}
}
}
/* (non-Javadoc) */
void writeObject(Object obj, DataOutput out) throws IOException {
DataSerializer.writeObject(obj, out, allowJavaSerialization());
}
/* (non-Javadoc) */
public void fromData(DataInput in) throws IOException, ClassNotFoundException {
synchronized (lock) {
for (int count = in.readInt(); count > 0; count--) {
setAttribute(in.readUTF(), readObject(in));
}
sessionAttributeDeltas.clear();
}
}
/* (non-Javadoc) */
<T> T readObject(DataInput in) throws ClassNotFoundException , IOException {
return DataSerializer.readObject(in);
}
/* (non-Javadoc) */
public boolean hasDelta() {
synchronized (lock) {
return !sessionAttributeDeltas.isEmpty();
}
}
/* (non-Javadoc) */
public void toDelta(DataOutput out) throws IOException {
synchronized (lock) {
out.writeInt(sessionAttributeDeltas.size());
for (Map.Entry<String, Object> entry : sessionAttributeDeltas.entrySet()) {
out.writeUTF(entry.getKey());
writeObject(entry.getValue(), out);
}
sessionAttributeDeltas.clear();
}
}
/* (non-Javadoc) */
public void fromDelta(DataInput in) throws InvalidDeltaException, IOException {
synchronized (lock) {
try {
int count = in.readInt();
Map<String, Object> deltas = new HashMap<String, Object>(count);
while (count-- > 0) {
deltas.put(in.readUTF(), readObject(in));
}
for (Map.Entry<String, Object> entry : deltas.entrySet()) {
setAttribute(entry.getKey(), entry.getValue());
sessionAttributeDeltas.remove(entry.getKey());
}
}
catch (ClassNotFoundException e) {
throw new InvalidDeltaException("class type in data not found", e);
}
}
}
@Override
public String toString() {
return sessionAttributes.toString();
}
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2002-2015 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.util.HashMap;
import java.util.Map;
import org.springframework.data.gemfire.GemfireOperations;
import org.springframework.session.ExpiringSession;
import com.gemstone.gemfire.cache.query.SelectResults;
/**
* The GemFireOperationsSessionRepository class is a Spring SessionRepository implementation that interfaces with
* and uses GemFire to back and store Spring Sessions.
*
* @author John Blum
* @see org.springframework.data.gemfire.GemfireOperations
* @see org.springframework.session.ExpiringSession
* @see org.springframework.session.Session
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository
* @since 1.1.0
*/
public class GemFireOperationsSessionRepository extends AbstractGemFireOperationsSessionRepository {
// GemFire OQL query used to look up Sessions by principal name.
protected static final String FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY =
"SELECT s FROM %1$s s WHERE s.principalName = $1";
/**
* Constructs an instance of GemFireOperationsSessionRepository initialized with the required GemfireOperations
* object used to perform data access operations to manage Session state.
*
* @param template the GemfireOperations object used to access and manage Session state in GemFire.
* @see org.springframework.data.gemfire.GemfireOperations
*/
public GemFireOperationsSessionRepository(GemfireOperations template) {
super(template);
}
/**
* Looks up all the available Sessions tied to the specific user identified by principal name.
*
* @param principalName the principal name (i.e. username) to search for all existing Spring Sessions.
* @return a mapping of Session ID to Session instances.
* @see org.springframework.session.ExpiringSession
*/
public Map<String, ExpiringSession> findByPrincipalName(String principalName) {
SelectResults<ExpiringSession> results = getTemplate().find(String.format(
FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, getFullyQualifiedRegionName()), principalName);
Map<String, ExpiringSession> sessions = new HashMap<String, ExpiringSession>(results.size());
for (ExpiringSession session : results.asList()) {
sessions.put(session.getId(), session);
}
return sessions;
}
/**
* Constructs a new {@link ExpiringSession} instance backed by GemFire.
*
* @return an instance of {@link ExpiringSession} backed by GemFire.
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession#create(int)
* @see org.springframework.session.ExpiringSession
* @see #getMaxInactiveIntervalInSeconds()
*/
public ExpiringSession createSession() {
return GemFireSession.create(getMaxInactiveIntervalInSeconds());
}
/**
* Gets a copy of an existing, non-expired {@link ExpiringSession} by ID. If the Session is expired,
* then it is deleted.
*
* @param sessionId a String indicating the ID of the Session to get.
* @return an existing {@link ExpiringSession} by ID or null if not Session exists.
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession#from(ExpiringSession)
* @see org.springframework.session.ExpiringSession
* @see #delete(String)
*/
public ExpiringSession getSession(String sessionId) {
ExpiringSession storedSession = getTemplate().get(sessionId);
if (storedSession != null) {
if (storedSession.isExpired()) {
delete(storedSession.getId());
}
else {
return GemFireSession.from(storedSession);
}
}
return null;
}
/**
* Saves the specified {@link ExpiringSession} to GemFire.
*
* @param session the {@link ExpiringSession} to save.
* @see org.springframework.data.gemfire.GemfireOperations#put(Object, Object)
* @see org.springframework.session.ExpiringSession
*/
public void save(ExpiringSession session) {
getTemplate().put(session.getId(), new GemFireSession(session));
}
/**
* Deletes (removes) any existing {@link ExpiringSession} from GemFire. This operation also results in
* a SessionDeletedEvent.
*
* @param sessionId a String indicating the ID of the Session to remove from GemFire.
* @see org.springframework.data.gemfire.GemfireOperations#remove(Object)
* @see #handleDeleted(String, ExpiringSession)
*/
public void delete(String sessionId) {
handleDeleted(sessionId, getTemplate().<Object, ExpiringSession>remove(sessionId));
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2002-2015 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.config.annotation.web.http;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import com.gemstone.gemfire.cache.RegionShortcut;
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
/**
* Add this annotation to an {@code @Configuration} class to expose the SessionRepositoryFilter
* as a bean named "springSessionRepositoryFilter" and backed by Pivotal GemFire or Apache Geode.
*
* In order to leverage the annotation, a single Pivotal GemFire/Apache Geode {@link com.gemstone.gemfire.cache.Cache}
* or {@link com.gemstone.gemfire.cache.client.ClientCache} instance must be provided.
*
* For example:
*
* <pre>
* <code>
* {@literal @Configuration}
* {@literal @EnableGemFireHttpSession}
* public class GemFirePeerCacheHttpSessionConfiguration {
*
* {@literal @Bean}
* public Properties gemfireProperties() {
* Properties gemfireProperties = new Properties();
* gemfireProperties.setProperty("name", "ExamplePeer");
* gemfireProperties.setProperty("mcast-port", "0");
* gemfireProperties.setProperty("log-level", "warning");
* return gemfireProperties;
* }
*
* {@literal @Bean}
* public CacheFactoryBean gemfireCache() throws Exception {
* CacheFactoryBean clientCacheFactoryBean = new CacheFactoryBean();
* clientCacheFactoryBean.setLazyInitialize(false);
* clientCacheFactoryBean.setProperties(gemfireProperties());
* clientCacheFactoryBean.setUseBeanFactoryLocator(false);
* return clientCacheFactoryBean;
* }
* }
* </code>
* </pre>
*
* Alternatively, a Spring Session can be configured to use Pivotal GemFire (Apache Geode) as a client
* using a dedicated GemFire Server cluster and a {@link com.gemstone.gemfire.cache.client.ClientCache}.
* For example:
*
* <code>
* {@literal @Configuration}
* {@literal @EnableGemFireHttpSession}
* public class GemFireClientCacheHttpSessionConfiguration {
*
* {@literal @Bean}
* public Properties gemfireProperties() {
* Properties gemfireProperties = new Properties();
* gemfireProperties.setProperty("name", "ExampleClient");
* gemfireProperties.setProperty("log-level", "warning");
* return gemfireProperties;
* }
*
* {@literal @Bean}
* public ClientCacheFactoryBean gemfireCache() throws Exception {
* ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean();
* clientCacheFactoryBean.setLazyInitialize(false);
* clientCacheFactoryBean.setProperties(gemfireProperties());
* clientCacheFactoryBean.setUseBeanFactoryLocator(false);
* return clientCacheFactoryBean;
* }
* }
* </code>
*
* More advanced configurations can extend {@link GemFireHttpSessionConfiguration} instead.
*
* @author John Blum
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.Import
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
* @see org.springframework.session.config.annotation.web.http.EnableSpringHttpSession
* @since 1.1.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@SuppressWarnings("unused")
@Target(ElementType.TYPE)
@Configuration
@Import(GemFireHttpSessionConfiguration.class)
public @interface EnableGemFireHttpSession {
/**
* Defines the GemFire ClientCache Region DataPolicy.
*
* @return a ClientRegionShortcut used to specify and configure the ClientCache Region DataPolicy.
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
*/
ClientRegionShortcut clientRegionShortcut() default ClientRegionShortcut.PROXY;
/**
* Defines the maximum interval in seconds that a Session can remain inactive before it is considered expired.
* Defaults to 1800 seconds, or 30 minutes.
*
* @return an integer value defining the maximum inactive interval in seconds for declaring a Session expired.
*/
int maxInactiveIntervalInSeconds() default 1800;
/**
* Defines the name of the GemFire (Client)Cache Region used to store Sessions.
*
* @return a String specifying the name of the GemFire (Client)Cache Region used to store Sessions.
* @see com.gemstone.gemfire.cache.Region#getName()
*/
String regionName() default "ClusteredSpringSessions";
/**
* Defines the GemFire, Peer Cache Region DataPolicy.
*
* @return a RegionShortcut used to specify and configure the Peer Cache Region DataPolicy.
* @see com.gemstone.gemfire.cache.RegionShortcut
*/
RegionShortcut serverRegionShortcut() default RegionShortcut.PARTITION;
}

View File

@@ -0,0 +1,372 @@
/*
* Copyright 2002-2015 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.config.annotation.web.http;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.GemfireOperations;
import org.springframework.data.gemfire.GemfireTemplate;
import org.springframework.data.gemfire.IndexFactoryBean;
import org.springframework.data.gemfire.IndexType;
import org.springframework.data.gemfire.RegionAttributesFactoryBean;
import org.springframework.session.ExpiringSession;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession;
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
import org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.util.StringUtils;
import com.gemstone.gemfire.cache.ExpirationAction;
import com.gemstone.gemfire.cache.ExpirationAttributes;
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.ClientRegionShortcut;
/**
* The GemFireHttpSessionConfiguration class is a Spring @Configuration class used to configure and initialize
* Pivotal GemFire (or Apache Geode) as a clustered, replicated HttpSession provider implementation in Spring Session.
*
* @author John Blum
* @see org.springframework.beans.factory.BeanClassLoaderAware
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.ImportAware
* @see org.springframework.data.gemfire.GemfireOperations
* @see org.springframework.data.gemfire.GemfireTemplate
* @see org.springframework.data.gemfire.IndexFactoryBean
* @see org.springframework.data.gemfire.RegionAttributesFactoryBean
* @see org.springframework.session.ExpiringSession
* @see org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean
* @see com.gemstone.gemfire.cache.ExpirationAttributes
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.Region
* @see com.gemstone.gemfire.cache.RegionAttributes
* @since 1.1.0
*/
@Configuration
@SuppressWarnings("unused")
public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfiguration
implements BeanClassLoaderAware, ImportAware {
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS = (int) TimeUnit.MINUTES.toSeconds(30);
protected static final Class<Object> SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT = Object.class;
protected static final Class<GemFireSession> SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT = GemFireSession.class;
public static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT = ClientRegionShortcut.PROXY;
public static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT = RegionShortcut.PARTITION;
public static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME = "ClusteredSpringSessions";
private int maxInactiveIntervalInSeconds = DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS;
private ClassLoader beanClassLoader;
private ClientRegionShortcut clientRegionShortcut = DEFAULT_CLIENT_REGION_SHORTCUT;
private RegionShortcut serverRegionShortcut = DEFAULT_SERVER_REGION_SHORTCUT;
private String springSessionGemFireRegionName = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME;
/**
* Sets a reference to the {@link ClassLoader} used to load bean definition class types in a Spring context.
*
* @param beanClassLoader the ClassLoader used by the Spring container to load bean class types.
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(ClassLoader)
* @see java.lang.ClassLoader
*/
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
}
/**
* Gets a reference to the {@link ClassLoader} used to load bean definition class types in a Spring context.
*
* @return the ClassLoader used by the Spring container to load bean class types.
* @see java.lang.ClassLoader
*/
protected ClassLoader getBeanClassLoader() {
return beanClassLoader;
}
/**
* Sets the {@link ClientRegionShortcut} used to configure the GemFire ClientCache Region
* that will store Spring Sessions.
*
* @param shortcut the ClientRegionShortcut used to configure the GemFire ClientCache Region.
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
*/
public void setClientRegionShortcut(ClientRegionShortcut shortcut) {
this.clientRegionShortcut = shortcut;
}
/**
* Gets the {@link ClientRegionShortcut} used to configure the GemFire ClientCache Region
* that will store Spring Sessions. Defaults to {@link ClientRegionShortcut#PROXY}.
*
* @return the ClientRegionShortcut used to configure the GemFire ClientCache Region.
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
*/
protected ClientRegionShortcut getClientRegionShortcut() {
return (clientRegionShortcut != null ? clientRegionShortcut : DEFAULT_CLIENT_REGION_SHORTCUT);
}
/**
* Sets the maximum interval in seconds in which a Session can remain inactive before it is considered expired.
*
* @param maxInactiveIntervalInSeconds an integer value specifying the maximum interval in seconds that a Session
* can remain inactive before it is considered expired.
*/
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}
/**
* Gets the maximum interval in seconds in which a Session can remain inactive before it is considered expired.
*
* @return an integer value specifying the maximum interval in seconds that a Session can remain inactive
* before it is considered expired.
*/
protected int getMaxInactiveIntervalInSeconds() {
return maxInactiveIntervalInSeconds;
}
/**
* Sets the {@link RegionShortcut} used to configure the GemFire Cache Region that will store Spring Sessions.
*
* @param shortcut the RegionShortcut used to configure the GemFire Cache Region.
* @see com.gemstone.gemfire.cache.RegionShortcut
*/
public void setServerRegionShortcut(RegionShortcut shortcut) {
serverRegionShortcut = shortcut;
}
/**
* Gets the {@link RegionShortcut} used to configure the GemFire Cache Region that will store Spring Sessions.
* Defaults to {@link RegionShortcut#PARTITION}.
*
* @return the RegionShortcut used to configure the GemFire Cache Region.
* @see com.gemstone.gemfire.cache.RegionShortcut
*/
protected RegionShortcut getServerRegionShortcut() {
return (serverRegionShortcut != null ? serverRegionShortcut : DEFAULT_SERVER_REGION_SHORTCUT);
}
/**
* Sets the name of the Gemfire (Client)Cache Region used to store Sessions.
*
* @param springSessionGemFireRegionName a String specifying the name of the GemFire (Client)Cache Region
* used to store the Session.
*/
public void setSpringSessionGemFireRegionName(String springSessionGemFireRegionName) {
this.springSessionGemFireRegionName = springSessionGemFireRegionName;
}
/**
* Gets the name of the Gemfire (Client)Cache Region used to store Sessions. Defaults to 'ClusteredSpringSessions'.
*
* @return a String specifying the name of the GemFire (Client)Cache Region
* used to store the Session.
* @see com.gemstone.gemfire.cache.Region#getName()
*/
protected String getSpringSessionGemFireRegionName() {
return (StringUtils.hasText(springSessionGemFireRegionName) ? springSessionGemFireRegionName
: DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
}
/**
* Callback with the {@link AnnotationMetadata} of the class containing @Import annotation that imported
* this @Configuration class.
*
* @param importMetadata the AnnotationMetadata of the class importing this @Configuration class.
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
* @see org.springframework.core.type.AnnotationMetadata
*/
public void setImportMetadata(AnnotationMetadata importMetadata) {
AnnotationAttributes enableGemFireHttpSessionAnnotationAttributes = AnnotationAttributes.fromMap(
importMetadata.getAnnotationAttributes(EnableGemFireHttpSession.class.getName()));
setClientRegionShortcut(ClientRegionShortcut.class.cast(enableGemFireHttpSessionAnnotationAttributes.getEnum(
"clientRegionShortcut")));
setMaxInactiveIntervalInSeconds(enableGemFireHttpSessionAnnotationAttributes.getNumber(
"maxInactiveIntervalInSeconds").intValue());
setServerRegionShortcut(RegionShortcut.class.cast(enableGemFireHttpSessionAnnotationAttributes.getEnum(
"serverRegionShortcut")));
setSpringSessionGemFireRegionName(enableGemFireHttpSessionAnnotationAttributes.getString("regionName"));
}
/**
* Defines the Spring SessionRepository bean used to interact with GemFire as a Spring Session provider.
*
* @param gemfireOperations an instance of {@link GemfireOperations} used to manage Spring Sessions in GemFire.
* @return a GemFireOperationsSessionRepository for managing (clustering/replicating) Sessions using GemFire.
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
* @see org.springframework.data.gemfire.GemfireOperations
*/
@Bean
public GemFireOperationsSessionRepository sessionRepository(@Qualifier("sessionRegionTemplate")
GemfireOperations gemfireOperations) {
GemFireOperationsSessionRepository sessionRepository = new GemFireOperationsSessionRepository(gemfireOperations);
sessionRepository.setMaxInactiveIntervalInSeconds(getMaxInactiveIntervalInSeconds());
return sessionRepository;
}
/**
* Defines a Spring GemfireTemplate bean used to interact with GemFire's (Client)Cache {@link Region}
* storing Sessions.
*
* @param gemFireCache reference to the single GemFire cache instance used by the {@link GemfireTemplate}
* to perform GemFire cache data access operations.
* @return a {@link GemfireTemplate} used to interact with GemFire's (Client)Cache {@link Region} storing Sessions.
* @see org.springframework.data.gemfire.GemfireTemplate
* @see com.gemstone.gemfire.cache.Region
*/
@Bean
@DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
public GemfireTemplate sessionRegionTemplate(GemFireCache gemFireCache) {
return new GemfireTemplate(gemFireCache.getRegion(getSpringSessionGemFireRegionName()));
}
/**
* Defines a Spring GemFire {@link com.gemstone.gemfire.cache.Cache} {@link Region} bean used to store
* and manage Sessions using either a client-server or peer-to-peer (p2p) topology.
*
* @param gemfireCache a reference to the GemFire {@link com.gemstone.gemfire.cache.Cache}.
* @param sessionRegionAttributes the GemFire {@link RegionAttributes} used to configure the {@link Region}.
* @return a {@link GemFireCacheTypeAwareRegionFactoryBean} used to configure and initialize a GemFire Cache
* {@link Region} for storing and managing Sessions.
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.RegionAttributes
* @see #getClientRegionShortcut()
* @see #getSpringSessionGemFireRegionName()
* @see #getServerRegionShortcut()
*/
@Bean(name = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
public GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> sessionRegion(GemFireCache gemfireCache,
RegionAttributes<Object, ExpiringSession> sessionRegionAttributes) {
GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> serverRegion =
new GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession>();
serverRegion.setGemfireCache(gemfireCache);
serverRegion.setClientRegionShortcut(getClientRegionShortcut());
serverRegion.setRegionAttributes(sessionRegionAttributes);
serverRegion.setRegionName(getSpringSessionGemFireRegionName());
serverRegion.setServerRegionShortcut(getServerRegionShortcut());
return serverRegion;
}
/**
* Defines a Spring GemFire {@link RegionAttributes} bean used to configure and initialize the GemFire cache
* {@link Region} storing Sessions. Expiration is also configured for the {@link Region} on the basis that the
* GemFire cache {@link Region} is a not a proxy, on either the client or server.
*
* @param gemfireCache a reference to the GemFire cache.
* @return an instance of {@link RegionAttributes} used to configure and initialize the GemFire cache {@link Region}
* for storing and managing Sessions.
* @see org.springframework.data.gemfire.RegionAttributesFactoryBean
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.PartitionAttributes
* @see #isExpirationAllowed(GemFireCache)
*/
@Bean
@SuppressWarnings({ "unchecked", "deprecation" })
public RegionAttributesFactoryBean sessionRegionAttributes(GemFireCache gemfireCache) {
RegionAttributesFactoryBean regionAttributes = new RegionAttributesFactoryBean();
regionAttributes.setKeyConstraint(SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT);
regionAttributes.setValueConstraint(SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT);
if (isExpirationAllowed(gemfireCache)) {
regionAttributes.setStatisticsEnabled(true);
regionAttributes.setEntryIdleTimeout(new ExpirationAttributes(
Math.max(getMaxInactiveIntervalInSeconds(), 0), ExpirationAction.INVALIDATE));
}
return regionAttributes;
}
/**
* Determines whether expiration configuration is allowed to be set on the GemFire cache {@link Region}
* used to store and manage Sessions.
*
* @param gemfireCache a reference to the GemFire cache.
* @return a boolean indicating if a {@link Region} can be configured for Region entry idle-timeout expiration.
* @see GemFireUtils#isClient(GemFireCache)
* @see GemFireUtils#isProxy(ClientRegionShortcut)
* @see GemFireUtils#isProxy(RegionShortcut)
*/
boolean isExpirationAllowed(GemFireCache gemfireCache) {
return !(GemFireUtils.isClient(gemfireCache) ? GemFireUtils.isProxy(getClientRegionShortcut())
: GemFireUtils.isProxy(getServerRegionShortcut()));
}
/**
* Defines a Spring GemFire Index bean on the GemFire cache {@link Region} storing and managing Sessions,
* specifically on the 'principalName' property for quick lookup and queries. This index will only be created
* on a server @{link Region}.
*
* @param gemfireCache a reference to the GemFire cache.
* @return a IndexFactoryBean creating an GemFire Index on the 'principalName' property of Sessions stored
* in the GemFire cache {@link Region}.
* @see org.springframework.data.gemfire.IndexFactoryBean
* @see com.gemstone.gemfire.cache.GemFireCache
*/
@Bean
@DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
public IndexFactoryBean principalNameIndex(final GemFireCache gemfireCache) {
IndexFactoryBean index = new IndexFactoryBean() {
@Override public void afterPropertiesSet() throws Exception {
if (GemFireUtils.isPeer(gemfireCache)) {
super.afterPropertiesSet();
}
}
};
index.setCache(gemfireCache);
index.setName("principalNameIdx");
index.setExpression("principalName");
index.setFrom(GemFireUtils.toRegionPath(getSpringSessionGemFireRegionName()));
index.setOverride(true);
index.setType(IndexType.HASH);
return index;
}
}

View File

@@ -0,0 +1,314 @@
/*
* Copyright 2002-2015 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.config.annotation.web.http.support;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.gemfire.GenericRegionFactoryBean;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.client.Interest;
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.InterestResultPolicy;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.RegionAttributes;
import com.gemstone.gemfire.cache.RegionShortcut;
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
/**
* The GemFireCacheTypeAwareRegionFactoryBean class is a Spring {@link FactoryBean} used to construct, configure
* and initialize the GemFire cache {@link Region} used to store and manage Session state.
*
* @author John Blum
* @see org.springframework.beans.factory.FactoryBean
* @see org.springframework.beans.factory.InitializingBean
* @see org.springframework.data.gemfire.GenericRegionFactoryBean
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.InterestResultPolicy
* @see com.gemstone.gemfire.cache.Region
* @see com.gemstone.gemfire.cache.RegionAttributes
* @see com.gemstone.gemfire.cache.RegionShortcut
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
* @since 1.1.0
*/
public class GemFireCacheTypeAwareRegionFactoryBean<K, V> implements FactoryBean<Region<K, V>>, InitializingBean {
protected static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT =
GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT;
protected static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT =
GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT;
protected static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME =
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME;
private ClientRegionShortcut clientRegionShortcut;
private GemFireCache gemfireCache;
private Region<K, V> region;
private RegionAttributes<K, V> regionAttributes;
private RegionShortcut serverRegionShortcut;
private String regionName;
/**
* Post-construction initialization callback to create, configure and initialize the GemFire cache {@link Region}
* used to store, replicate (distribute) and manage Session state. This method intelligently handles
* both client-server and peer-to-peer (p2p) GemFire supported distributed system topologies.
*
* @throws Exception if the initialization of the GemFire cache {@link Region} fails.
* @see org.springframework.session.data.gemfire.support.GemFireUtils#isClient(GemFireCache)
* @see #getGemfireCache()
* @see #newClientRegion(GemFireCache)
* @see #newServerRegion(GemFireCache)
*/
public void afterPropertiesSet() throws Exception {
GemFireCache gemfireCache = getGemfireCache();
region = (GemFireUtils.isClient(gemfireCache) ? newClientRegion(gemfireCache)
: newServerRegion(gemfireCache));
}
/**
* Constructs a GemFire cache {@link Region} using a peer-to-peer (p2p) GemFire topology to store
* and manage Session state in a GemFire server cluster accessible from a GemFire cache client.
*
* @param gemfireCache a reference to the GemFire {@link com.gemstone.gemfire.cache.Cache}.
* @return a peer-to-peer-based GemFire cache {@link Region} to store and manage Session state.
* @throws Exception if the instantiation, configuration and initialization
* of the GemFire cache {@link Region} fails.
* @see org.springframework.data.gemfire.GenericRegionFactoryBean
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.Region
* @see #getRegionAttributes()
* @see #getRegionName()
* @see #getServerRegionShortcut()
*/
protected Region<K, V> newServerRegion(GemFireCache gemfireCache) throws Exception {
GenericRegionFactoryBean<K, V> serverRegion = new GenericRegionFactoryBean<K, V>();
serverRegion.setCache(gemfireCache);
serverRegion.setAttributes(getRegionAttributes());
serverRegion.setRegionName(getRegionName());
serverRegion.setShortcut(getServerRegionShortcut());
serverRegion.afterPropertiesSet();
return serverRegion.getObject();
}
/**
* Constructs a GemFire cache {@link Region} using the client-server GemFire topology to store
* and manage Session state in a GemFire server cluster accessible from a GemFire cache client.
*
* @param gemfireCache a reference to the GemFire {@link com.gemstone.gemfire.cache.Cache}.
* @return a client-server-based GemFire cache {@link Region} to store and manage Session state.
* @throws Exception if the instantiation, configuration and initialization
* of the GemFire cache {@link Region} fails.
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.Region
* @see #getClientRegionShortcut()
* @see #getRegionAttributes()
* @see #getRegionName()
* @see #registerInterests(boolean)
*/
protected Region<K, V> newClientRegion(GemFireCache gemfireCache) throws Exception {
ClientRegionFactoryBean<K, V> clientRegion = new ClientRegionFactoryBean<K, V>();
ClientRegionShortcut shortcut = getClientRegionShortcut();
clientRegion.setCache(gemfireCache);
clientRegion.setAttributes(getRegionAttributes());
clientRegion.setInterests(registerInterests(!GemFireUtils.isLocal(shortcut)));
clientRegion.setRegionName(getRegionName());
clientRegion.setShortcut(shortcut);
clientRegion.afterPropertiesSet();
return clientRegion.getObject();
}
/**
* Registers interests in all keys when the client {@link Region} is non-local.
*
* @return an array of Interests specifying the server notifications of interests to the client.
* @see org.springframework.data.gemfire.client.Interest
*/
/**
* Decides whether interests will be registered for all keys. Interests is only registered on a client
* and typically only when the client is a (CACHING) PROXY to the server (i.e. non-LOCAL only).
*
* @param register a boolean value indicating whether interests should be registered.
* @return an array of Interests KEY/VALUE registrations.
* @see org.springframework.data.gemfire.client.Interest
*/
@SuppressWarnings("unchecked")
protected Interest<K>[] registerInterests(boolean register) {
return (!register ? new Interest[0] : new Interest[] {
new Interest<String>("ALL_KEYS", InterestResultPolicy.KEYS)
});
}
/**
* Returns a reference to the constructed GemFire cache {@link Region} used to store and manage Session state.
*
* @return the {@link Region} used to store and manage Session state.
* @throws Exception if the {@link Region} reference cannot be obtained.
* @see com.gemstone.gemfire.cache.Region
*/
public Region<K, V> getObject() throws Exception {
return region;
}
/**
* Returns the specific type of GemFire cache {@link Region} this factory creates when initialized
* or Region.class when uninitialized.
*
* @return the GemFire cache {@link Region} class type constructed by this factory.
* @see com.gemstone.gemfire.cache.Region
* @see java.lang.Class
*/
public Class<?> getObjectType() {
return (region != null ? region.getClass() : Region.class);
}
/**
* Returns true indicating the GemFire cache {@link Region} created by this factory is the sole instance.
*
* @return true to indicate the GemFire cache {@link Region} storing and managing Sessions is a Singleton.
*/
public boolean isSingleton() {
return true;
}
/**
* Sets the {@link Region} data policy used by the GemFire cache client to manage Session state.
*
* @param clientRegionShortcut a {@link ClientRegionShortcut} to specify the client {@link Region}
* data management policy.
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
*/
public void setClientRegionShortcut(ClientRegionShortcut clientRegionShortcut) {
this.clientRegionShortcut = clientRegionShortcut;
}
/**
* Returns the {@link Region} data policy used by the GemFire cache client to manage Session state. Defaults to
* {@link ClientRegionShortcut#PROXY}.
*
* @return a {@link ClientRegionShortcut} specifying the client {@link Region} data management policy.
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_CLIENT_REGION_SHORTCUT
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
*/
protected ClientRegionShortcut getClientRegionShortcut() {
return (clientRegionShortcut != null ? clientRegionShortcut : DEFAULT_CLIENT_REGION_SHORTCUT);
}
/**
* Sets a reference to the GemFire cache used to construct the appropriate {@link Region}.
*
* @param gemfireCache a reference to the GemFire cache.
* @throws IllegalArgumentException if the {@link GemFireCache} reference is null.
*/
public void setGemfireCache(GemFireCache gemfireCache) {
Assert.notNull(gemfireCache, "The GemFireCache reference must not be null");
this.gemfireCache = gemfireCache;
}
/**
* Returns a reference to the GemFire cache used to construct the appropriate {@link Region}.
*
* @return a reference to the GemFire cache.
* @throws IllegalStateException if the {@link GemFireCache} reference is null.
*/
protected GemFireCache getGemfireCache() {
Assert.state(gemfireCache != null, "A reference to a GemFireCache was not properly configured");
return gemfireCache;
}
/**
* Sets the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region} used to
* store and manage Session state.
*
* @param regionAttributes the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region}.
* @see com.gemstone.gemfire.cache.RegionAttributes
*/
public void setRegionAttributes(RegionAttributes<K, V> regionAttributes) {
this.regionAttributes = regionAttributes;
}
/**
* Returns the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region} used to
* store and manage Session state.
*
* @return the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region}.
* @see com.gemstone.gemfire.cache.RegionAttributes
*/
protected RegionAttributes<K, V> getRegionAttributes() {
return regionAttributes;
}
/**
* Sets the name of the GemFire cache {@link Region} use to store and manage Session state.
*
* @param regionName a String specifying the name of the GemFire cache {@link Region}.
*/
public void setRegionName(final String regionName) {
this.regionName = regionName;
}
/**
* Returns the configured name of the GemFire cache {@link Region} use to store and manage Session state.
* Defaults to "ClusteredSpringSessions"
*
* @return a String specifying the name of the GemFire cache {@link Region}.
* @see com.gemstone.gemfire.cache.Region#getName()
*/
protected String getRegionName() {
return (StringUtils.hasText(regionName) ? regionName : DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
}
/**
* Sets the {@link Region} data policy used by the GemFire peer cache to manage Session state.
*
* @param serverRegionShortcut a {@link RegionShortcut} to specify the peer {@link Region} data management policy.
* @see com.gemstone.gemfire.cache.RegionShortcut
*/
public void setServerRegionShortcut(RegionShortcut serverRegionShortcut) {
this.serverRegionShortcut = serverRegionShortcut;
}
/**
* Returns the {@link Region} data policy used by the GemFire peer cache to manage Session state. Defaults to
* {@link RegionShortcut#PARTITION}.
*
* @return a {@link RegionShortcut} specifying the peer {@link Region} data management policy.
* @see com.gemstone.gemfire.cache.RegionShortcut
*/
protected RegionShortcut getServerRegionShortcut() {
return (serverRegionShortcut != null ? serverRegionShortcut : DEFAULT_SERVER_REGION_SHORTCUT);
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2002-2015 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.support;
import java.io.Closeable;
import java.io.IOException;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.RegionShortcut;
import com.gemstone.gemfire.cache.client.ClientCache;
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
import com.gemstone.gemfire.internal.cache.GemFireCacheImpl;
/**
* GemFireUtils is an abstract, extensible utility class for working with GemFire types and functionality
* and is used by Spring Session's GemFire adapter support classes.
*
* @author John Blum
* @since 1.0.0
*/
public abstract class GemFireUtils {
/**
* Null-safe method to close the given {@link Closeable} object.
*
* @param obj the {@link Closeable} object to close.
* @return true if the {@link Closeable} object is not null and was successfully closed,
* otherwise return false.
* @see java.io.Closeable
*/
public static boolean close(Closeable obj) {
if (obj != null) {
try {
obj.close();
return true;
}
catch (IOException ignore) {
}
}
return false;
}
/**
* Determines whether the GemFire cache is a client.
*
* @param gemFireCache a reference to the GemFire cache.
* @return a boolean value indicating whether the GemFire cache is a client.
* @see com.gemstone.gemfire.cache.client.ClientCache
* @see com.gemstone.gemfire.cache.GemFireCache
*/
public static boolean isClient(GemFireCache gemFireCache) {
boolean client = (gemFireCache instanceof ClientCache);
client &= (!(gemFireCache instanceof GemFireCacheImpl) || ((GemFireCacheImpl) gemFireCache).isClient());
return client;
}
/**
* Determines whether the GemFire cache is a peer.
*
* @param gemFireCache a reference to the GemFire cache.
* @return a boolean value indicating whether the GemFire cache is a peer.
* @see com.gemstone.gemfire.cache.Cache
* @see com.gemstone.gemfire.cache.GemFireCache
*/
public static boolean isPeer(GemFireCache gemFireCache) {
return (gemFireCache instanceof Cache && !isClient(gemFireCache));
}
/**
* Determines whether the given {@link ClientRegionShortcut} is local only.
*
* @param shortcut the ClientRegionShortcut to evaluate.
* @return a boolean value indicating if the {@link ClientRegionShortcut} is local or not.
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
*/
public static boolean isLocal(ClientRegionShortcut shortcut) {
switch (shortcut) {
case LOCAL:
case LOCAL_HEAP_LRU:
case LOCAL_OVERFLOW:
case LOCAL_PERSISTENT:
case LOCAL_PERSISTENT_OVERFLOW:
return true;
default:
return false;
}
}
/**
* Determines whether the client {@link ClientRegionShortcut} is a proxy-based shortcut.
* NOTE: "proxy"-based Regions keep no local state.
*
* @param shortcut the client {@link ClientRegionShortcut} to evaluate.
* @return a boolean value indicating whether the client {@link ClientRegionShortcut} refers to
* a proxy-based shortcut.
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
*/
public static boolean isProxy(ClientRegionShortcut shortcut) {
switch (shortcut) {
case PROXY:
return true;
default:
return false;
}
}
/**
* Determines whether the peer {@link RegionShortcut} is a proxy-based shortcut. NOTE: "proxy"-based Regions
* keep no local state.
*
* @param shortcut the peer {@link RegionShortcut} to evaluate.
* @return a boolean value indicating whether the peer {@link RegionShortcut} refers to a proxy-based shortcut.
* @see com.gemstone.gemfire.cache.RegionShortcut
*/
public static boolean isProxy(RegionShortcut shortcut) {
switch (shortcut) {
case PARTITION_PROXY:
case PARTITION_PROXY_REDUNDANT:
case REPLICATE_PROXY:
return true;
default:
return false;
}
}
/**
* Converts a {@link Region} name to a {@link Region} path.
*
* @param regionName a String specifying the name of the {@link Region}.
* @return a String path for the given {@link Region} by name.
* @see com.gemstone.gemfire.cache.Region#getFullPath()
* @see com.gemstone.gemfire.cache.Region#getName()
*/
public static String toRegionPath(String regionName) {
return String.format("%1$s%2$s", Region.SEPARATOR, regionName);
}
}

View File

@@ -0,0 +1,368 @@
/*
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Matchers.same;
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 static org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.gemfire.GemfireAccessor;
import org.springframework.data.gemfire.GemfireOperations;
import org.springframework.session.ExpiringSession;
import org.springframework.session.events.AbstractSessionEvent;
import org.springframework.session.events.SessionDeletedEvent;
import com.gemstone.gemfire.cache.AttributesMutator;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.query.SelectResults;
/**
* The GemFireOperationsSessionRepositoryTest class is a test suite of test cases testing the contract and functionality
* of the GemFireOperationsSessionRepository class.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
* @since 1.0.0
*/
@RunWith(MockitoJUnitRunner.class)
public class GemFireOperationsSessionRepositoryTest {
protected static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
@Mock
private ApplicationEventPublisher mockApplicationEventPublisher;
@Mock
private AttributesMutator<Object, ExpiringSession> mockAttributesMutator;
@Mock
private Region<Object, ExpiringSession> mockRegion;
@Mock
private GemfireOperationsAccessor mockTemplate;
private GemFireOperationsSessionRepository sessionRepository;
@Before
public void setup() throws Exception {
when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator);
when(mockRegion.getFullPath()).thenReturn("/Example");
when(mockTemplate.<Object, ExpiringSession>getRegion()).thenReturn(mockRegion);
sessionRepository = new GemFireOperationsSessionRepository(mockTemplate);
sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher);
sessionRepository.setMaxInactiveIntervalInSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS);
sessionRepository.afterPropertiesSet();
assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher);
assertThat(sessionRepository.getFullyQualifiedRegionName()).isEqualTo("/Example");
assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
@After
public void tearDown() {
verify(mockAttributesMutator, times(1)).addCacheListener(same(sessionRepository));
verify(mockRegion, times(1)).getFullPath();
verify(mockTemplate, times(1)).getRegion();
}
@Test
@SuppressWarnings("unchecked")
public void findByPrincipalNameFindsMatchingSessions() throws Exception {
ExpiringSession mockSessionOne = mock(ExpiringSession.class, "MockSessionOne");
ExpiringSession mockSessionTwo = mock(ExpiringSession.class, "MockSessionTwo");
ExpiringSession mockSessionThree = mock(ExpiringSession.class, "MockSessionThree");
when(mockSessionOne.getId()).thenReturn("1");
when(mockSessionTwo.getId()).thenReturn("2");
when(mockSessionThree.getId()).thenReturn("3");
SelectResults mockSelectResults = mock(SelectResults.class);
when(mockSelectResults.asList()).thenReturn(Arrays.asList(mockSessionOne, mockSessionTwo, mockSessionThree));
String principalName = "jblum";
String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
sessionRepository.getFullyQualifiedRegionName());
when(mockTemplate.find(eq(expectedOql), eq(principalName))).thenReturn(mockSelectResults);
Map<String, ExpiringSession> sessions = sessionRepository.findByPrincipalName(principalName);
assertThat(sessions).isNotNull();
assertThat(sessions.size()).isEqualTo(3);
assertThat(sessions.get("1")).isEqualTo(mockSessionOne);
assertThat(sessions.get("2")).isEqualTo(mockSessionTwo);
assertThat(sessions.get("3")).isEqualTo(mockSessionThree);
verify(mockTemplate, times(1)).find(eq(expectedOql), eq(principalName));
verify(mockSelectResults, times(1)).asList();
verify(mockSessionOne, times(1)).getId();
verify(mockSessionTwo, times(1)).getId();
verify(mockSessionThree, times(1)).getId();
}
@Test
@SuppressWarnings("unchecked")
public void findByPrincipalNameReturnsNoMatchingSessions() {
SelectResults mockSelectResults = mock(SelectResults.class);
when(mockSelectResults.asList()).thenReturn(Collections.emptyList());
String principalName = "jblum";
String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
sessionRepository.getFullyQualifiedRegionName());
when(mockTemplate.find(eq(expectedOql), eq(principalName))).thenReturn(mockSelectResults);
Map<String, ExpiringSession> sessions = sessionRepository.findByPrincipalName(principalName);
assertThat(sessions).isNotNull();
assertThat(sessions.isEmpty()).isTrue();
verify(mockTemplate, times(1)).find(eq(expectedOql), eq(principalName));
verify(mockSelectResults, times(1)).asList();
}
@Test
public void createProperlyInitializedSession() {
final long beforeOrAtCreationTime = System.currentTimeMillis();
ExpiringSession session = sessionRepository.createSession();
assertThat(session).isInstanceOf(GemFireSession.class);
assertThat(session.getId()).isNotNull();
assertThat(session.getAttributeNames().isEmpty()).isTrue();
assertThat(session.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime);
assertThat(session.getLastAccessedTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime);
assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
@Test
public void getSessionDeletesMatchingExpiredSessionById() {
final String expectedSessionId = "1";
final ExpiringSession mockSession = mock(ExpiringSession.class);
when(mockSession.isExpired()).thenReturn(true);
when(mockSession.getId()).thenReturn(expectedSessionId);
when(mockTemplate.get(eq(expectedSessionId))).thenReturn(mockSession);
when(mockTemplate.remove(eq(expectedSessionId))).thenReturn(mockSession);
doAnswer(new Answer<Void>() {
public Void answer(final InvocationOnMock invocation) throws Throwable {
ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class);
assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class);
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
assertThat(sessionEvent.getSource()).isSameAs(sessionRepository);
assertThat(sessionEvent.getSession()).isSameAs(mockSession);
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
return null;
}
}).when(mockApplicationEventPublisher).publishEvent(any(ApplicationEvent.class));
assertThat(sessionRepository.getSession(expectedSessionId)).isNull();
verify(mockTemplate, times(1)).get(eq(expectedSessionId));
verify(mockTemplate, times(1)).remove(eq(expectedSessionId));
verify(mockSession, times(1)).isExpired();
verify(mockSession, times(2)).getId();
verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDeletedEvent.class));
}
@Test
public void getSessionFindsMatchingNonExpiredSessionById() {
final String expectedId = "1";
final long expectedCreationTime = System.currentTimeMillis();
final long currentLastAccessedTime = (expectedCreationTime + TimeUnit.MINUTES.toMillis(5));
ExpiringSession mockSession = mock(ExpiringSession.class);
when(mockSession.isExpired()).thenReturn(false);
when(mockSession.getId()).thenReturn(expectedId);
when(mockSession.getCreationTime()).thenReturn(expectedCreationTime);
when(mockSession.getLastAccessedTime()).thenReturn(currentLastAccessedTime);
when(mockSession.getAttributeNames()).thenReturn(Collections.singleton("attrOne"));
when(mockSession.getAttribute(eq("attrOne"))).thenReturn("test");
when(mockTemplate.get(eq(expectedId))).thenReturn(mockSession);
ExpiringSession actualSession = sessionRepository.getSession(expectedId);
assertThat(actualSession).isNotSameAs(mockSession);
assertThat(actualSession.getId()).isEqualTo(expectedId);
assertThat(actualSession.getCreationTime()).isEqualTo(expectedCreationTime);
assertThat(actualSession.getLastAccessedTime()).isNotEqualTo(currentLastAccessedTime);
assertThat(actualSession.getLastAccessedTime()).isGreaterThanOrEqualTo(expectedCreationTime);
assertThat(actualSession.getAttributeNames()).isEqualTo(Collections.singleton("attrOne"));
assertThat(String.valueOf(actualSession.getAttribute("attrOne"))).isEqualTo("test");
verify(mockTemplate, times(1)).get(eq(expectedId));
verify(mockSession, times(1)).isExpired();
verify(mockSession, times(1)).getId();
verify(mockSession, times(1)).getCreationTime();
verify(mockSession, times(1)).getLastAccessedTime();
verify(mockSession, times(1)).getAttributeNames();
verify(mockSession, times(1)).getAttribute(eq("attrOne"));
}
@Test
public void getSessionReturnsNull() {
when(mockTemplate.get(anyString())).thenReturn(null);
assertThat(sessionRepository.getSession("1")).isNull();
}
@Test
public void saveStoresSession() {
final String expectedSessionId = "1";
final long expectedCreationTime = System.currentTimeMillis();
final long expectedLastAccessTime = (expectedCreationTime + TimeUnit.MINUTES.toMillis(5));
ExpiringSession mockSession = mock(ExpiringSession.class);
when(mockSession.getId()).thenReturn(expectedSessionId);
when(mockSession.getCreationTime()).thenReturn(expectedCreationTime);
when(mockSession.getLastAccessedTime()).thenReturn(expectedLastAccessTime);
when(mockSession.getMaxInactiveIntervalInSeconds()).thenReturn(MAX_INACTIVE_INTERVAL_IN_SECONDS);
when(mockSession.getAttributeNames()).thenReturn(Collections.<String>emptySet());
when(mockTemplate.put(eq(expectedSessionId), isA(GemFireSession.class)))
.thenAnswer(new Answer<ExpiringSession>() {
public ExpiringSession answer(final InvocationOnMock invocation) throws Throwable {
ExpiringSession session = invocation.getArgumentAt(1, ExpiringSession.class);
assertThat(session).isNotNull();
assertThat(session.getId()).isEqualTo(expectedSessionId);
assertThat(session.getCreationTime()).isEqualTo(expectedCreationTime);
assertThat(session.getLastAccessedTime()).isEqualTo(expectedLastAccessTime);
assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
assertThat(session.getAttributeNames().isEmpty()).isTrue();
return null;
}
});
sessionRepository.save(mockSession);
verify(mockSession, times(2)).getId();
verify(mockSession, times(1)).getCreationTime();
verify(mockSession, times(1)).getLastAccessedTime();
verify(mockSession, times(1)).getMaxInactiveIntervalInSeconds();
verify(mockSession, times(1)).getAttributeNames();
verify(mockTemplate, times(1)).put(eq(expectedSessionId), isA(GemFireSession.class));
}
@Test
public void deleteRemovesExistingSessionAndHandlesDelete() {
final String expectedSessionId = "1";
final ExpiringSession mockSession = mock(ExpiringSession.class);
when(mockSession.getId()).thenReturn(expectedSessionId);
when(mockTemplate.remove(eq(expectedSessionId))).thenReturn(mockSession);
doAnswer(new Answer<Void>() {
public Void answer(final InvocationOnMock invocation) throws Throwable {
ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class);
assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class);
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
assertThat(sessionEvent.getSource()).isSameAs(sessionRepository);
assertThat(sessionEvent.getSession()).isSameAs(mockSession);
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
return null;
}
}).when(mockApplicationEventPublisher).publishEvent(isA(SessionDeletedEvent.class));
sessionRepository.delete(expectedSessionId);
verify(mockSession, times(1)).getId();
verify(mockTemplate, times(1)).remove(eq(expectedSessionId));
verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDeletedEvent.class));
}
@Test
public void deleteRemovesNonExistingSessionAndHandlesDelete() {
final String expectedSessionId = "1";
when(mockTemplate.remove(anyString())).thenReturn(null);
doAnswer(new Answer<Void>() {
public Void answer(final InvocationOnMock invocation) throws Throwable {
ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class);
assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class);
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
assertThat(sessionEvent.getSource()).isSameAs(sessionRepository);
assertThat(sessionEvent.getSession()).isNull();
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
return null;
}
}).when(mockApplicationEventPublisher).publishEvent(isA(SessionDeletedEvent.class));
sessionRepository.delete(expectedSessionId);
verify(mockTemplate, times(1)).remove(eq(expectedSessionId));
verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDeletedEvent.class));
}
protected abstract class GemfireOperationsAccessor extends GemfireAccessor implements GemfireOperations {
}
}

View File

@@ -0,0 +1,255 @@
/*
* Copyright 2002-2015 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.config.annotation.web.http;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.eq;
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.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.GemfireOperations;
import org.springframework.data.gemfire.GemfireTemplate;
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.RegionShortcut;
import com.gemstone.gemfire.cache.client.ClientCache;
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
/**
* The GemFireHttpSessionConfigurationTest class is a test suite of test cases testing the contract and functionality
* of the {@link GemFireHttpSessionConfiguration} class.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mockito
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
* @see com.gemstone.gemfire.cache.Cache
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.Region
* @see com.gemstone.gemfire.cache.client.ClientCache
* @since 1.0.0
*/
public class GemFireHttpSessionConfigurationTest {
private GemFireHttpSessionConfiguration gemfireConfiguration;
@Before
public void setup() {
gemfireConfiguration = new GemFireHttpSessionConfiguration();
}
@Test
public void setAndGetBeanClassLoader() {
assertThat(gemfireConfiguration.getBeanClassLoader()).isNull();
gemfireConfiguration.setBeanClassLoader(Thread.currentThread().getContextClassLoader());
assertThat(gemfireConfiguration.getBeanClassLoader()).isEqualTo(Thread.currentThread().getContextClassLoader());
gemfireConfiguration.setBeanClassLoader(null);
assertThat(gemfireConfiguration.getBeanClassLoader()).isNull();
}
@Test
public void setAndGetClientRegionShortcut() {
assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT);
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY);
assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY);
gemfireConfiguration.setClientRegionShortcut(null);
assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT);
}
@Test
public void setAndGetMaxInactiveIntervalInSeconds() {
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS);
gemfireConfiguration.setMaxInactiveIntervalInSeconds(300);
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(300);
gemfireConfiguration.setMaxInactiveIntervalInSeconds(Integer.MAX_VALUE);
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MAX_VALUE);
gemfireConfiguration.setMaxInactiveIntervalInSeconds(-1);
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(-1);
gemfireConfiguration.setMaxInactiveIntervalInSeconds(Integer.MIN_VALUE);
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MIN_VALUE);
}
@Test
public void setAndGetServerRegionShortcut() {
assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT);
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE_PERSISTENT);
assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE_PERSISTENT);
gemfireConfiguration.setServerRegionShortcut(null);
assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT);
}
@Test
public void setAndGetSpringSessionGemFireRegionName() {
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
gemfireConfiguration.setSpringSessionGemFireRegionName("test");
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("test");
gemfireConfiguration.setSpringSessionGemFireRegionName(" ");
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
gemfireConfiguration.setSpringSessionGemFireRegionName("");
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
gemfireConfiguration.setSpringSessionGemFireRegionName(null);
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
}
@Test
public void setImportMetadata() {
AnnotationMetadata mockAnnotationMetadata = mock(AnnotationMetadata.class, "testSetImportMetadata");
Map<String, Object> annotationAttributes = new HashMap<String, Object>(4);
annotationAttributes.put("clientRegionShortcut", ClientRegionShortcut.CACHING_PROXY);
annotationAttributes.put("maxInactiveIntervalInSeconds", 600);
annotationAttributes.put("serverRegionShortcut", RegionShortcut.REPLICATE);
annotationAttributes.put("regionName", "TEST");
when(mockAnnotationMetadata.getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName())))
.thenReturn(annotationAttributes);
gemfireConfiguration.setImportMetadata(mockAnnotationMetadata);
assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY);
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(600);
assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE);
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("TEST");
verify(mockAnnotationMetadata, times(1)).getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName()));
}
@Test
public void createAndInitializeSpringSessionRepositoryBean() {
GemfireOperations mockGemfireOperations = mock(GemfireOperations.class,
"testCreateAndInitializeSpringSessionRepositoryBean");
gemfireConfiguration.setMaxInactiveIntervalInSeconds(120);
GemFireOperationsSessionRepository sessionRepository = gemfireConfiguration.sessionRepository(
mockGemfireOperations);
assertThat(sessionRepository).isNotNull();
assertThat(sessionRepository.getTemplate()).isSameAs(mockGemfireOperations);
assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(120);
}
@Test
@SuppressWarnings("unchecked")
public void createAndInitializeSpringSessionGemFireRegionTemplate() {
GemFireCache mockGemFireCache = mock(GemFireCache.class);
Region mockRegion = mock(Region.class);
when(mockGemFireCache.getRegion(eq("Example"))).thenReturn(mockRegion);
gemfireConfiguration.setSpringSessionGemFireRegionName("Example");
GemfireTemplate template = gemfireConfiguration.sessionRegionTemplate(mockGemFireCache);
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("Example");
assertThat(template).isNotNull();
assertThat(template.getRegion()).isSameAs(mockRegion);
verify(mockGemFireCache, times(1)).getRegion(eq("Example"));
}
@Test
public void expirationIsAllowed() {
Cache mockCache = mock(Cache.class, "testExpirationIsAllowed.MockCache");
ClientCache mockClientCache = mock(ClientCache.class, "testExpirationIsAllowed.MockClientCache");
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.PROXY);
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE);
assertThat(gemfireConfiguration.isExpirationAllowed(mockCache)).isTrue();
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT_OVERFLOW);
assertThat(gemfireConfiguration.isExpirationAllowed(mockCache)).isTrue();
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY);
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_PROXY);
assertThat(gemfireConfiguration.isExpirationAllowed(mockClientCache)).isTrue();
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW);
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE_PROXY);
assertThat(gemfireConfiguration.isExpirationAllowed(mockClientCache)).isTrue();
}
@Test
public void expirationIsNotAllowed() {
Cache mockCache = mock(Cache.class, "testExpirationIsAllowed.MockCache");
ClientCache mockClientCache = mock(ClientCache.class, "testExpirationIsAllowed.MockClientCache");
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.PROXY);
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION);
assertThat(gemfireConfiguration.isExpirationAllowed(mockClientCache)).isFalse();
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.LOCAL);
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_PROXY);
assertThat(gemfireConfiguration.isExpirationAllowed(mockCache)).isFalse();
}
}

View File

@@ -0,0 +1,245 @@
/*
* Copyright 2002-2015 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.config.annotation.web.http.support;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.data.gemfire.client.Interest;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.InterestResultPolicy;
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;
/**
* The GemFireCacheTypeAwareRegionFactoryBeanTest class is a test suite of test cases testing the contract
* and functionality of the GemFireCacheTypeAwareRegionFactoryBean class.
*
* @author John Blum
* @see org.junit.Rule
* @see org.junit.Test
* @see org.mockito.Mockito
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean
* @see com.gemstone.gemfire.cache.Cache
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.InterestResultPolicy
* @see com.gemstone.gemfire.cache.Region
* @see com.gemstone.gemfire.cache.RegionAttributes
* @see com.gemstone.gemfire.cache.RegionShortcut
* @see com.gemstone.gemfire.cache.client.ClientCache
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
* @since 1.0.0
*/
public class GemFireCacheTypeAwareRegionFactoryBeanTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
private GemFireCacheTypeAwareRegionFactoryBean regionFactoryBean;
@Before
public void setup() {
regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean();
}
@Test
public void afterPropertiesSetCreatesClientRegionForClientCache() throws Exception {
final ClientCache mockClientCache = mock(ClientCache.class);
final Region mockClientRegion = mock(Region.class, "MockClientRegion");
final Region mockServerRegion = mock(Region.class, "MockServerRegion");
regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean() {
@Override protected Region newClientRegion(GemFireCache gemfireCache) throws Exception {
assertThat(gemfireCache).isSameAs(mockClientCache);
return mockClientRegion;
}
@Override protected Region newServerRegion(final GemFireCache gemfireCache) throws Exception {
assertThat(gemfireCache).isSameAs(mockClientCache);
return mockServerRegion;
}
};
regionFactoryBean.setGemfireCache(mockClientCache);
regionFactoryBean.afterPropertiesSet();
assertThat(regionFactoryBean.getGemfireCache()).isSameAs(mockClientCache);
assertThat(regionFactoryBean.getObject()).isEqualTo(mockClientRegion);
}
@Test
public void afterPropertiesSetCreatesServerRegionForPeerCache() throws Exception {
final Cache mockCache = mock(Cache.class);
final Region mockClientRegion = mock(Region.class, "MockClientRegion");
final Region mockServerRegion = mock(Region.class, "MockServerRegion");
regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean() {
@Override protected Region newClientRegion(GemFireCache gemfireCache) throws Exception {
assertThat(gemfireCache).isSameAs(mockCache);
return mockClientRegion;
}
@Override protected Region newServerRegion(final GemFireCache gemfireCache) throws Exception {
assertThat(gemfireCache).isSameAs(mockCache);
return mockServerRegion;
}
};
regionFactoryBean.setGemfireCache(mockCache);
regionFactoryBean.afterPropertiesSet();
assertThat(regionFactoryBean.getGemfireCache()).isSameAs(mockCache);
assertThat(regionFactoryBean.getObject()).isEqualTo(mockServerRegion);
}
@Test
public void allKeysInterestRegistration() {
Interest[] interests = regionFactoryBean.registerInterests(true);
assertThat(interests).isNotNull();
assertThat(interests.length).isEqualTo(1);
assertThat(interests[0].isDurable()).isFalse();
assertThat(interests[0].getKey().toString()).isEqualTo("ALL_KEYS");
assertThat(interests[0].getPolicy()).isEqualTo(InterestResultPolicy.KEYS);
assertThat(interests[0].isReceiveValues()).isTrue();
}
@Test
public void emptyInterestsRegistration() {
Interest[] interests = regionFactoryBean.registerInterests(false);
assertThat(interests).isNotNull();
assertThat(interests.length).isEqualTo(0);
}
@Test
@SuppressWarnings("unchecked")
public void getObjectTypeBeforeInitializationIsRegionClass() {
assertThat((Class<Region>) regionFactoryBean.getObjectType()).isEqualTo(Region.class);
}
@Test
public void isSingletonIsTrue() {
assertThat(regionFactoryBean.isSingleton()).isTrue();
}
@Test
public void setAndGetClientRegionShortcut() {
assertThat(regionFactoryBean.getClientRegionShortcut()).isEqualTo(
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_CLIENT_REGION_SHORTCUT);
regionFactoryBean.setClientRegionShortcut(ClientRegionShortcut.LOCAL_PERSISTENT);
assertThat(regionFactoryBean.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.LOCAL_PERSISTENT);
regionFactoryBean.setClientRegionShortcut(null);
assertThat(regionFactoryBean.getClientRegionShortcut()).isEqualTo(
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_CLIENT_REGION_SHORTCUT);
}
@Test
public void setAndGetGemfireCache() {
Cache mockCache = mock(Cache.class);
regionFactoryBean.setGemfireCache(mockCache);
assertThat(regionFactoryBean.getGemfireCache()).isEqualTo(mockCache);
}
@Test
public void setGemfireCacheToNullThrowsIllegalArgumentException() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("The GemFireCache reference must not be null");
regionFactoryBean.setGemfireCache(null);
}
@Test
public void getGemfireCacheWhenNullThrowsIllegalStateException() {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("A reference to a GemFireCache was not properly configured");
regionFactoryBean.getGemfireCache();
}
@Test
@SuppressWarnings("unchecked")
public void setAndGetRegionAttributes() {
RegionAttributes mockRegionAttributes = mock(RegionAttributes.class);
assertThat(regionFactoryBean.getRegionAttributes()).isNull();
regionFactoryBean.setRegionAttributes(mockRegionAttributes);
assertThat(regionFactoryBean.getRegionAttributes()).isSameAs(mockRegionAttributes);
regionFactoryBean.setRegionAttributes(null);
assertThat(regionFactoryBean.getRegionAttributes()).isNull();
}
@Test
public void setAndGetRegionName() {
assertThat(regionFactoryBean.getRegionName()).isEqualTo(
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
regionFactoryBean.setRegionName("Example");
assertThat(regionFactoryBean.getRegionName()).isEqualTo("Example");
regionFactoryBean.setRegionName(" ");
assertThat(regionFactoryBean.getRegionName()).isEqualTo(
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
regionFactoryBean.setRegionName("");
assertThat(regionFactoryBean.getRegionName()).isEqualTo(
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
regionFactoryBean.setRegionName(null);
assertThat(regionFactoryBean.getRegionName()).isEqualTo(
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
}
@Test
public void setAndGetServerRegionShortcut() {
assertThat(regionFactoryBean.getServerRegionShortcut()).isEqualTo(
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SERVER_REGION_SHORTCUT);
regionFactoryBean.setServerRegionShortcut(RegionShortcut.LOCAL_PERSISTENT);
assertThat(regionFactoryBean.getServerRegionShortcut()).isEqualTo(RegionShortcut.LOCAL_PERSISTENT);
regionFactoryBean.setServerRegionShortcut(null);
assertThat(regionFactoryBean.getServerRegionShortcut()).isEqualTo(
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SERVER_REGION_SHORTCUT);
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2002-2015 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.support;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.io.Closeable;
import java.io.IOException;
import org.junit.Test;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.RegionShortcut;
import com.gemstone.gemfire.cache.client.ClientCache;
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
/**
* The GemFireUtilsTest class is a test suite of test cases testing the contract and functionality of the GemFireUtils
* utility class.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mockito
* @see org.springframework.session.data.gemfire.support.GemFireUtils
* @since 1.0.0
*/
public class GemFireUtilsTest {
@Test
public void closeNonNullCloseableSuccessfullyReturnsTrue() throws IOException {
Closeable mockCloseable = mock(Closeable.class);
assertThat(GemFireUtils.close(mockCloseable)).isTrue();
verify(mockCloseable, times(1)).close();
}
@Test
public void closeNonNullCloseableObjectThrowingIOExceptionReturnsFalse() throws IOException {
Closeable mockCloseable = mock(Closeable.class);
doThrow(new IOException("test")).when(mockCloseable).close();
assertThat(GemFireUtils.close(mockCloseable)).isFalse();
verify(mockCloseable, times(1)).close();
}
@Test
public void closeNullCloseableObjectReturnsFalse() {
assertThat(GemFireUtils.close(null)).isFalse();
}
@Test
public void clientCacheIsClient() {
assertThat(GemFireUtils.isClient(mock(ClientCache.class))).isTrue();
}
@Test
public void genericCacheIsNotClient() {
assertThat(GemFireUtils.isClient(mock(GemFireCache.class))).isFalse();
}
@Test
public void peerCacheIsNotClient() {
assertThat(GemFireUtils.isClient(mock(Cache.class))).isFalse();
}
@Test
public void peerCacheIsPeer() {
assertThat(GemFireUtils.isPeer(mock(Cache.class))).isTrue();
}
@Test
public void genericCacheIsNotPeer() {
assertThat(GemFireUtils.isPeer(mock(GemFireCache.class))).isFalse();
}
@Test
public void clientCacheIsNotPeer() {
assertThat(GemFireUtils.isPeer(mock(ClientCache.class))).isFalse();
}
@Test
public void clientRegionShortcutIsLocal() {
assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL)).isTrue();
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();
}
@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.PROXY)).isFalse();
}
@Test
public void clientRegionShortcutIsProxy() {
assertThat(GemFireUtils.isProxy(ClientRegionShortcut.PROXY)).isTrue();
}
@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.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();
}
@Test
public void regionShortcutIsProxy() {
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PROXY)).isTrue();
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PROXY_REDUNDANT)).isTrue();
assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_PROXY)).isTrue();
}
@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.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.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_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();
}
@Test
public void toRegionPath() {
assertThat(GemFireUtils.toRegionPath("A")).isEqualTo("/A");
assertThat(GemFireUtils.toRegionPath("Example")).isEqualTo("/Example");
assertThat(GemFireUtils.toRegionPath("/Example")).isEqualTo("//Example");
assertThat(GemFireUtils.toRegionPath("/")).isEqualTo("//");
assertThat(GemFireUtils.toRegionPath("")).isEqualTo("/");
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:gfe="http://www.springframework.org/schema/gemfire"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
">
<util:properties id="gemfireProperties">
<prop key="name">GemFireHttpSessionConfigurationXmlTests</prop>
<prop key="mcast-port">0</prop>
<prop key="log-level">warning</prop>
</util:properties>
<gfe:cache properties-ref="gemfireProperties"/>
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
p:maxInactiveIntervalInSeconds="3600" p:springSessionGemFireRegionName="Example"
p:serverRegionShortcut="#{T(com.gemstone.gemfire.cache.RegionShortcut).LOCAL}"/>
</beans>