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:
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user