Refactor and simplify non-dirty Session handling.
Resolves gh-16.
This commit is contained in:
@@ -140,8 +140,8 @@ public abstract class AbstractConcurrentSessionOperationsIntegrationTests extend
|
||||
waitForTick(2);
|
||||
assertTick(2);
|
||||
|
||||
// Save Session with no changes, no delta
|
||||
assertThat(session instanceof GemFireSession && ((GemFireSession) session).isDirty()).isFalse();
|
||||
// Save Session with no changes/no delta
|
||||
assertThat(session instanceof GemFireSession && ((GemFireSession) session).hasDelta()).isFalse();
|
||||
|
||||
save(session);
|
||||
}
|
||||
@@ -166,6 +166,7 @@ public abstract class AbstractConcurrentSessionOperationsIntegrationTests extend
|
||||
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeTwo", "attributeThree");
|
||||
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("three");
|
||||
assertThat(session instanceof GemFireSession && ((GemFireSession) session).hasDelta()).isTrue();
|
||||
|
||||
save(session);
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@ package org.springframework.session.data.gemfire;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -28,6 +30,8 @@ import edu.umd.cs.mtc.TestFramework;
|
||||
|
||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.data.gemfire.config.annotation.CacheServerApplication;
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
||||
@@ -36,7 +40,7 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* Integration tests containing test cases asserting the the proper behavior of concurrently accessing a Session
|
||||
* Integration tests containing test cases asserting the the proper behavior of concurrently accessing a {@link Session}
|
||||
* using Spring Session backed by Apache Geode or Pivotal GemFire in a Multi-Threaded context.
|
||||
*
|
||||
* @author John Blum
|
||||
@@ -48,26 +52,33 @@ import org.springframework.test.context.junit4.SpringRunner;
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringRunner
|
||||
* @since 2.1.1
|
||||
* @since 2.1.2
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration
|
||||
@ContextConfiguration(
|
||||
classes = MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests.GemFireClientConfiguration.class
|
||||
)
|
||||
public class MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests
|
||||
extends AbstractGemFireIntegrationTests {
|
||||
|
||||
@BeforeClass
|
||||
public static void startGemFireServer() throws IOException {
|
||||
startGemFireServer(GemFireServerConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiThreadedSessionOperationsAreCorrect() throws Throwable {
|
||||
TestFramework.runOnce(new MultiThreadedSessionOperationsTestCase(this));
|
||||
public void multiThreadedConcurrentSessionOperationsAreCorrect() throws Throwable {
|
||||
TestFramework.runOnce(new MultiThreadedConcurrentSessionOperationsTestCase(this));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class MultiThreadedSessionOperationsTestCase extends MultithreadedTestCase {
|
||||
public static final class MultiThreadedConcurrentSessionOperationsTestCase extends MultithreadedTestCase {
|
||||
|
||||
private final AtomicReference<String> sessionId = new AtomicReference<>(null);
|
||||
|
||||
private final MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests testInstance;
|
||||
|
||||
public MultiThreadedSessionOperationsTestCase(
|
||||
public MultiThreadedConcurrentSessionOperationsTestCase(
|
||||
MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests testInstance) {
|
||||
|
||||
this.testInstance = testInstance;
|
||||
@@ -98,51 +109,21 @@ public class MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests
|
||||
assertThat(session.isExpired()).isFalse();
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
|
||||
session.setAttribute("attributeOne", "foo");
|
||||
|
||||
save(session);
|
||||
|
||||
Session loadedSession = findById(session.getId());
|
||||
|
||||
assertThat(loadedSession).isNotNull();
|
||||
assertThat(loadedSession.getId()).isEqualTo(session.getId());
|
||||
assertThat(loadedSession.isExpired()).isFalse();
|
||||
assertThat(loadedSession.getAttributeNames()).containsExactly("attributeOne");
|
||||
assertThat(loadedSession.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
|
||||
loadedSession.setAttribute("attributeTwo", "bar");
|
||||
|
||||
save(loadedSession);
|
||||
|
||||
this.sessionId.set(loadedSession.getId());
|
||||
this.sessionId.set(session.getId());
|
||||
|
||||
waitForTick(2);
|
||||
assertTick(2);
|
||||
|
||||
Session reloadedSession = findById(loadedSession.getId());
|
||||
session.setAttribute("attributeOne", "foo");
|
||||
session.setAttribute("attributeTwo", "bar");
|
||||
|
||||
assertThat(reloadedSession).isNotNull();
|
||||
assertThat(reloadedSession.getId()).isEqualTo(loadedSession.getId());
|
||||
assertThat(reloadedSession.isExpired()).isFalse();
|
||||
assertThat(reloadedSession.getAttributeNames())
|
||||
.containsOnly("attributeOne", "attributeTwo", "attributeThree");
|
||||
assertThat(reloadedSession.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(reloadedSession.<String>getAttribute("attributeTwo")).isEqualTo("bar");
|
||||
assertThat(reloadedSession.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
assertThat(session.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(session.<String>getAttribute("attributeTwo")).isEqualTo("bar");
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeTwo");
|
||||
|
||||
waitForTick(4);
|
||||
assertTick(4);
|
||||
|
||||
Session endSession = findById(reloadedSession.getId());
|
||||
|
||||
assertThat(endSession).isNotNull();
|
||||
assertThat(endSession.getId()).isEqualTo(reloadedSession.getId());
|
||||
assertThat(endSession.isExpired()).isFalse();
|
||||
assertThat(endSession.getAttributeNames()).containsOnly("attributeOne", "attributeThree");
|
||||
assertThat(endSession.getAttributeNames()).doesNotContain("attributeTwo");
|
||||
assertThat(endSession.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(endSession.<String>getAttribute("attributeTwo")).isNull();
|
||||
assertThat(endSession.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
save(session);
|
||||
}
|
||||
|
||||
public void thread2() {
|
||||
@@ -157,32 +138,35 @@ public class MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(session.getId()).isEqualTo(this.sessionId.get());
|
||||
assertThat(session.isExpired()).isFalse();
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeTwo");
|
||||
assertThat(session.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(session.<String>getAttribute("attributeTwo")).isEqualTo("bar");
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
|
||||
waitForTick(2);
|
||||
assertTick(2);
|
||||
|
||||
session.setAttribute("attributeThree", "baz");
|
||||
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeThree");
|
||||
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
|
||||
save(session);
|
||||
|
||||
waitForTick(4);
|
||||
assertTick(4);
|
||||
|
||||
Session endSession = findById(session.getId());
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeThree");
|
||||
|
||||
assertThat(endSession).isNotNull();
|
||||
assertThat(endSession.getId()).isEqualTo(session.getId());
|
||||
assertThat(endSession.isExpired()).isFalse();
|
||||
assertThat(endSession.getAttributeNames()).containsOnly("attributeOne", "attributeThree");
|
||||
assertThat(endSession.getAttributeNames()).doesNotContain("attributeTwo");
|
||||
assertThat(endSession.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(endSession.<String>getAttribute("attributeTwo")).isNull();
|
||||
assertThat(endSession.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
session.setAttribute("attributeFour", "qux");
|
||||
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeThree", "attributeFour");
|
||||
assertThat(session.<String>getAttribute("attributeFour")).isEqualTo("qux");
|
||||
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
|
||||
save(session);
|
||||
}
|
||||
|
||||
public void thread3() {
|
||||
|
||||
Thread.currentThread().setName("User Session Three");
|
||||
Thread.currentThread().setName("User Session Four");
|
||||
|
||||
waitForTick(3);
|
||||
assertTick(3);
|
||||
@@ -197,7 +181,16 @@ public class MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests
|
||||
assertThat(session.<String>getAttribute("attributeTwo")).isEqualTo("bar");
|
||||
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
|
||||
session.setAttribute("attributeTwo", null);
|
||||
waitForTick(4);
|
||||
assertTick(4);
|
||||
|
||||
session.setAttribute("attributeThree", "bazatch");
|
||||
session.removeAttribute("attributeTwo");
|
||||
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeThree");
|
||||
assertThat(session.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(session.<String>getAttribute("attributeTwo")).isNull();
|
||||
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("bazatch");
|
||||
|
||||
save(session);
|
||||
}
|
||||
@@ -205,25 +198,47 @@ public class MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests
|
||||
@Override
|
||||
public void finish() {
|
||||
|
||||
super.finish();
|
||||
|
||||
Session session = findById(this.sessionId.get());
|
||||
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(session.getId()).isEqualTo(this.sessionId.get());
|
||||
assertThat(session.isExpired()).isFalse();
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeThree");
|
||||
assertThat(session.getAttributeNames()).doesNotContain("attributeTwo");
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeThree", "attributeFour");
|
||||
assertThat(session.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(session.<String>getAttribute("attributeTwo")).isNull();
|
||||
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("bazatch");
|
||||
assertThat(session.<String>getAttribute("attributeFour")).isEqualTo("qux");
|
||||
}
|
||||
}
|
||||
|
||||
@ClientCacheApplication(logLevel = "error")
|
||||
@ClientCacheApplication(logLevel = "error", subscriptionEnabled = true)
|
||||
@EnableGemFireHttpSession(
|
||||
clientRegionShortcut = ClientRegionShortcut.LOCAL,
|
||||
clientRegionShortcut = ClientRegionShortcut.PROXY,
|
||||
poolName = "DEFAULT",
|
||||
regionName = "Sessions",
|
||||
sessionSerializerBeanName = GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME
|
||||
)
|
||||
static class TestConfiguration { }
|
||||
static class GemFireClientConfiguration { }
|
||||
|
||||
@CacheServerApplication(
|
||||
name = "MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests",
|
||||
logLevel = "error"
|
||||
)
|
||||
@EnableGemFireHttpSession(
|
||||
regionName = "Sessions",
|
||||
sessionSerializerBeanName = GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME
|
||||
)
|
||||
static class GemFireServerConfiguration {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
AnnotationConfigApplicationContext applicationContext =
|
||||
new AnnotationConfigApplicationContext(GemFireServerConfiguration.class);
|
||||
|
||||
applicationContext.registerShutdownHook();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2018 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.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import edu.umd.cs.mtc.MultithreadedTestCase;
|
||||
import edu.umd.cs.mtc.TestFramework;
|
||||
|
||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
||||
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
|
||||
import org.springframework.session.Session;
|
||||
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.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* Integration tests containing test cases asserting the the proper behavior of concurrently accessing a {@link Session}
|
||||
* using Spring Session backed by Apache Geode or Pivotal GemFire in a Multi-Threaded context.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see edu.umd.cs.mtc.MultithreadedTestCase
|
||||
* @see edu.umd.cs.mtc.TestFramework
|
||||
* @see org.springframework.session.Session
|
||||
* @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.SpringRunner
|
||||
* @since 2.1.1
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration
|
||||
public class MultiThreadedLocalClientHttpSessionAttributesDeltaIntegrationTests
|
||||
extends AbstractGemFireIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void multiThreadedSessionOperationsAreCorrect() throws Throwable {
|
||||
TestFramework.runOnce(new MultiThreadedSessionOperationsTestCase(this));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class MultiThreadedSessionOperationsTestCase extends MultithreadedTestCase {
|
||||
|
||||
private final AtomicReference<String> sessionId = new AtomicReference<>(null);
|
||||
|
||||
private final MultiThreadedLocalClientHttpSessionAttributesDeltaIntegrationTests testInstance;
|
||||
|
||||
public MultiThreadedSessionOperationsTestCase(
|
||||
MultiThreadedLocalClientHttpSessionAttributesDeltaIntegrationTests testInstance) {
|
||||
|
||||
this.testInstance = testInstance;
|
||||
}
|
||||
|
||||
private Session findById(String id) {
|
||||
return this.testInstance.get(id);
|
||||
}
|
||||
|
||||
private Session newSession() {
|
||||
return this.testInstance.createSession();
|
||||
}
|
||||
|
||||
private <T extends Session> T save(T session) {
|
||||
return this.testInstance.save(this.testInstance.touch(session));
|
||||
}
|
||||
|
||||
public void thread1() {
|
||||
|
||||
Thread.currentThread().setName("User Session One");
|
||||
|
||||
assertTick(0);
|
||||
|
||||
Session session = newSession();
|
||||
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(session.getId()).isNotEmpty();
|
||||
assertThat(session.isExpired()).isFalse();
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
|
||||
session.setAttribute("attributeOne", "foo");
|
||||
|
||||
save(session);
|
||||
|
||||
Session loadedSession = findById(session.getId());
|
||||
|
||||
assertThat(loadedSession).isNotNull();
|
||||
assertThat(loadedSession).isNotSameAs(session);
|
||||
assertThat(loadedSession.getId()).isEqualTo(session.getId());
|
||||
assertThat(loadedSession.isExpired()).isFalse();
|
||||
assertThat(loadedSession.getAttributeNames()).containsExactly("attributeOne");
|
||||
assertThat(loadedSession.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
|
||||
loadedSession.setAttribute("attributeTwo", "bar");
|
||||
|
||||
save(loadedSession);
|
||||
|
||||
this.sessionId.set(loadedSession.getId());
|
||||
|
||||
waitForTick(2);
|
||||
assertTick(2);
|
||||
|
||||
Session reloadedSession = findById(loadedSession.getId());
|
||||
|
||||
assertThat(reloadedSession).isNotNull();
|
||||
assertThat(reloadedSession).isNotSameAs(loadedSession);
|
||||
assertThat(reloadedSession.getId()).isEqualTo(loadedSession.getId());
|
||||
assertThat(reloadedSession.isExpired()).isFalse();
|
||||
assertThat(reloadedSession.getAttributeNames())
|
||||
.containsOnly("attributeOne", "attributeTwo", "attributeThree");
|
||||
assertThat(reloadedSession.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(reloadedSession.<String>getAttribute("attributeTwo")).isEqualTo("bar");
|
||||
assertThat(reloadedSession.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
|
||||
waitForTick(4);
|
||||
assertTick(4);
|
||||
|
||||
Session endSession = findById(reloadedSession.getId());
|
||||
|
||||
assertThat(endSession).isNotNull();
|
||||
assertThat(endSession.getId()).isEqualTo(reloadedSession.getId());
|
||||
assertThat(endSession.isExpired()).isFalse();
|
||||
assertThat(endSession.getAttributeNames()).containsOnly("attributeOne", "attributeThree");
|
||||
assertThat(endSession.getAttributeNames()).doesNotContain("attributeTwo");
|
||||
assertThat(endSession.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(endSession.<String>getAttribute("attributeTwo")).isNull();
|
||||
assertThat(endSession.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
}
|
||||
|
||||
public void thread2() {
|
||||
|
||||
Thread.currentThread().setName("User Session Two");
|
||||
|
||||
waitForTick(1);
|
||||
assertTick(1);
|
||||
|
||||
Session session = findById(this.sessionId.get());
|
||||
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(session.getId()).isEqualTo(this.sessionId.get());
|
||||
assertThat(session.isExpired()).isFalse();
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeTwo");
|
||||
assertThat(session.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(session.<String>getAttribute("attributeTwo")).isEqualTo("bar");
|
||||
|
||||
session.setAttribute("attributeThree", "baz");
|
||||
|
||||
save(session);
|
||||
|
||||
waitForTick(4);
|
||||
assertTick(4);
|
||||
|
||||
Session endSession = findById(session.getId());
|
||||
|
||||
assertThat(endSession).isNotNull();
|
||||
assertThat(endSession.getId()).isEqualTo(session.getId());
|
||||
assertThat(endSession.isExpired()).isFalse();
|
||||
assertThat(endSession.getAttributeNames()).containsOnly("attributeOne", "attributeThree");
|
||||
assertThat(endSession.getAttributeNames()).doesNotContain("attributeTwo");
|
||||
assertThat(endSession.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(endSession.<String>getAttribute("attributeTwo")).isNull();
|
||||
assertThat(endSession.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
}
|
||||
|
||||
public void thread3() {
|
||||
|
||||
Thread.currentThread().setName("User Session Three");
|
||||
|
||||
waitForTick(3);
|
||||
assertTick(3);
|
||||
|
||||
Session session = findById(this.sessionId.get());
|
||||
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(session.getId()).isEqualTo(this.sessionId.get());
|
||||
assertThat(session.isExpired()).isFalse();
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeTwo", "attributeThree");
|
||||
assertThat(session.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(session.<String>getAttribute("attributeTwo")).isEqualTo("bar");
|
||||
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
|
||||
session.setAttribute("attributeTwo", null);
|
||||
|
||||
save(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
|
||||
Session session = findById(this.sessionId.get());
|
||||
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(session.getId()).isEqualTo(this.sessionId.get());
|
||||
assertThat(session.isExpired()).isFalse();
|
||||
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeThree");
|
||||
assertThat(session.getAttributeNames()).doesNotContain("attributeTwo");
|
||||
assertThat(session.<String>getAttribute("attributeOne")).isEqualTo("foo");
|
||||
assertThat(session.<String>getAttribute("attributeTwo")).isNull();
|
||||
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("baz");
|
||||
}
|
||||
}
|
||||
|
||||
@ClientCacheApplication(logLevel = "error", copyOnRead = true)
|
||||
@EnableGemFireHttpSession(
|
||||
clientRegionShortcut = ClientRegionShortcut.LOCAL,
|
||||
poolName = "DEFAULT",
|
||||
sessionSerializerBeanName = GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME
|
||||
)
|
||||
static class TestConfiguration { }
|
||||
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.session.data.gemfire;
|
||||
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.io.DataInput;
|
||||
@@ -28,7 +27,6 @@ import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -56,6 +54,8 @@ import org.springframework.data.gemfire.GemfireAccessor;
|
||||
import org.springframework.data.gemfire.GemfireOperations;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
@@ -106,7 +106,6 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
implements ApplicationEventPublisherAware, FindByIndexNameSessionRepository<Session>, InitializingBean {
|
||||
|
||||
private static final AtomicBoolean usingDataSerialization = new AtomicBoolean(false);
|
||||
private static final AtomicBoolean usingEagerCommit = new AtomicBoolean(false);
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher = event -> {};
|
||||
|
||||
@@ -284,25 +283,6 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
return usingDataSerialization.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a condition to determine whether a {@link Session} is copied and committed before saving.
|
||||
*
|
||||
* @param useEagerCommit boolean to determine whether to copy and commit a {@link Session} before saving.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setUseEagerCommit(boolean useEagerCommit) {
|
||||
usingEagerCommit.set(useEagerCommit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a {@link Session} is copied and committed before saving.
|
||||
*
|
||||
* @return a boolean value indicating whether a {@link Session} is copied and committed before saving.
|
||||
*/
|
||||
protected boolean isUsingEagerCommit() {
|
||||
return usingEagerCommit.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method during Spring bean initialization that will capture the fully-qualified name
|
||||
* of the cache {@link Region} used to manage {@link Session} state and register this {@link SessionRepository}
|
||||
@@ -375,7 +355,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
}
|
||||
|
||||
/**
|
||||
* Rememvers the given {@link Object session ID}.
|
||||
* Remembers the given {@link Object session ID}.
|
||||
*
|
||||
* @param sessionId {@link Object} containing the session ID to remember.
|
||||
* @return a boolean value whether Spring Session is interested in and will remember
|
||||
@@ -402,7 +382,8 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
*/
|
||||
Session toSession(Object obj, String sessionId) {
|
||||
|
||||
return obj instanceof Session ? (Session) obj
|
||||
return obj instanceof Session
|
||||
? (Session) obj
|
||||
: Optional.ofNullable(sessionId)
|
||||
.filter(StringUtils::hasText)
|
||||
.map(SessionIdHolder::create)
|
||||
@@ -467,6 +448,25 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the given {@link Session}.
|
||||
*
|
||||
* @param session {@link Session} to commit, if committable.
|
||||
* @return the given {@link Session}
|
||||
* @see GemFireSession#commit()
|
||||
*/
|
||||
protected @Nullable Session commit(@Nullable Session session) {
|
||||
|
||||
return Optional.ofNullable(session)
|
||||
.filter(GemFireSession.class::isInstance)
|
||||
.map(GemFireSession.class::cast)
|
||||
.<Session>map(gemfireSession -> {
|
||||
gemfireSession.commit();
|
||||
return gemfireSession;
|
||||
})
|
||||
.orElse(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given {@link Session} from GemFire.
|
||||
*
|
||||
@@ -476,7 +476,9 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
* @see #deleteById(String)
|
||||
*/
|
||||
protected Session delete(Session session) {
|
||||
|
||||
deleteById(session.getId());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -579,12 +581,12 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
/**
|
||||
* Updates the {@link Session#setLastAccessedTime(Instant)} property of the {@link Session}.
|
||||
*
|
||||
* @param <T> {@link Class} sub-type of the {@link Session}.
|
||||
* @param session {@link Session} to touch.
|
||||
* @return the {@link Session}.
|
||||
* @see org.springframework.session.Session#setLastAccessedTime(Instant)
|
||||
* @see java.time.Instant#now()
|
||||
*/
|
||||
protected <T extends Session> T touch(T session) {
|
||||
protected Session touch(Session session) {
|
||||
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
|
||||
@@ -616,7 +618,6 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
out.writeLong(getLastAccessedTime().toEpochMilli());
|
||||
out.writeLong(getMaxInactiveInterval().getSeconds());
|
||||
getAttributes().toDelta(out);
|
||||
clearDelta();
|
||||
}
|
||||
|
||||
public synchronized void fromDelta(DataInput in) throws IOException {
|
||||
@@ -625,7 +626,6 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
setLastAccessedTime(Instant.ofEpochMilli(in.readLong()));
|
||||
setMaxInactiveInterval(Duration.ofSeconds(in.readLong()));
|
||||
getAttributes().fromDelta(in);
|
||||
clearDelta();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,52 +641,33 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
|
||||
protected static final Duration DEFAULT_MAX_INACTIVE_INTERVAL = Duration.ZERO;
|
||||
|
||||
protected static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private static final String GEMFIRE_SESSION_TO_STRING =
|
||||
protected static final String GEMFIRE_SESSION_TO_STRING =
|
||||
"{ @type = %1$s, id = %2$s, creationTime = %3$s, lastAccessedTime = %4$s, maxInactiveInterval = %5$s, principalName = %6$s }";
|
||||
|
||||
private transient boolean delta = false;
|
||||
private transient boolean dirty = false;
|
||||
|
||||
private Duration maxInactiveInterval = DEFAULT_MAX_INACTIVE_INTERVAL;
|
||||
|
||||
private final Instant creationTime;
|
||||
private Instant lastAccessedTime;
|
||||
|
||||
private transient final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private String id;
|
||||
|
||||
private transient final T sessionAttributes = newSessionAttributes(this);
|
||||
|
||||
protected GemFireSession() {
|
||||
this(generateId());
|
||||
}
|
||||
|
||||
protected GemFireSession(String id) {
|
||||
|
||||
this.id = validateId(id);
|
||||
this.creationTime = Instant.now();
|
||||
this.dirty = true;
|
||||
this.lastAccessedTime = this.creationTime;
|
||||
}
|
||||
|
||||
protected GemFireSession(Session session) {
|
||||
|
||||
Assert.notNull(session, "The Session to copy must not be null");
|
||||
|
||||
this.id = session.getId();
|
||||
this.creationTime = session.getCreationTime();
|
||||
this.lastAccessedTime = session.getLastAccessedTime();
|
||||
this.maxInactiveInterval = session.getMaxInactiveInterval();
|
||||
this.sessionAttributes.from(session);
|
||||
}
|
||||
protected static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
/**
|
||||
* Factory method used to create a new instance of {@link GemFireSession} initialized with
|
||||
* the {@link #DEFAULT_MAX_INACTIVE_INTERVAL}.
|
||||
*
|
||||
* @return new {@link GemFireSession}.
|
||||
* @see #create(Duration)
|
||||
*/
|
||||
public static GemFireSession create() {
|
||||
return create(DEFAULT_MAX_INACTIVE_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to create a new instance of {@link GemFireSession} initialized with
|
||||
* the given {@link Duration max inactive interval}.
|
||||
*
|
||||
* @param maxInactiveInterval {@link Duration} specifying the max inactive interval before
|
||||
* this {@link Session} will expire.
|
||||
* @return a new instance of {@link GemFireSession} initialized with
|
||||
* the given {@link Duration max inactive interval}.
|
||||
* @see #isUsingDataSerialization()
|
||||
* @see java.time.Duration
|
||||
*/
|
||||
public static GemFireSession create(Duration maxInactiveInterval) {
|
||||
|
||||
GemFireSession session = isUsingDataSerialization()
|
||||
@@ -698,48 +679,94 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
return session;
|
||||
}
|
||||
|
||||
public static GemFireSession copy(Session session) {
|
||||
/**
|
||||
* Copy (i.e. clone) the given {@link Session}.
|
||||
*
|
||||
* @param session {@link Session} to copy/clone.
|
||||
* @return a new instance of {@link GemFireSession} copied from the given {@link Session}.
|
||||
* @see org.springframework.session.Session
|
||||
* @see #isUsingDataSerialization()
|
||||
*/
|
||||
public static GemFireSession copy(@NonNull Session session) {
|
||||
|
||||
return isUsingDataSerialization()
|
||||
? new DeltaCapableGemFireSession(session)
|
||||
: new GemFireSession(session);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static GemFireSession copyCommitted(Session session) {
|
||||
|
||||
synchronized (session) {
|
||||
|
||||
GemFireSession sessionCopy = copy(session);
|
||||
|
||||
if (session instanceof GemFireSession) {
|
||||
((GemFireSession) session).commit();
|
||||
}
|
||||
|
||||
return sessionCopy;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given {@link Session} if the {@link Session} is a {@link GemFireSession} or return a copy
|
||||
* of the given {@link Session} as a {@link GemFireSession}.
|
||||
*
|
||||
* @param <T> {@link Class sub-type} of {@link GemFireSession}.
|
||||
* @param session {@link Session} to evaluate and possibly copy.
|
||||
* @return the given {@link Session} if the {@link Session} is a {@link GemFireSession} or return a copy
|
||||
* of the given {@link Session} as a {@link GemFireSession}
|
||||
* @see #copy(Session)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends GemFireSession> T from(Session session) {
|
||||
public static <T extends GemFireSession> T from(@NonNull Session session) {
|
||||
return (T) (session instanceof GemFireSession ? session : copy(session));
|
||||
}
|
||||
|
||||
private transient boolean delta = true;
|
||||
|
||||
private Duration maxInactiveInterval;
|
||||
|
||||
private final Instant creationTime;
|
||||
private Instant lastAccessedTime;
|
||||
|
||||
private transient final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private String id;
|
||||
|
||||
private transient final T sessionAttributes = newSessionAttributes(this);
|
||||
|
||||
/**
|
||||
* Randomly generates a unique identifier (ID) from {@link UUID} to be used as the {@link Session} ID.
|
||||
* Constructs a new, default instance of {@link GemFireSession} initialized with
|
||||
* a generated {@link Session#getId() Session Identifier}.
|
||||
*
|
||||
* @return a new unique identifier (ID).
|
||||
* @see java.util.UUID#randomUUID()
|
||||
* @see #GemFireSession(String)
|
||||
* @see #generateSessionId()
|
||||
*/
|
||||
private static String generateId() {
|
||||
return UUID.randomUUID().toString();
|
||||
protected GemFireSession() {
|
||||
this(generateSessionId());
|
||||
}
|
||||
|
||||
private static String validateId(String id) {
|
||||
/**
|
||||
* Constructs a new instance of {@link GemFireSession} initialized with
|
||||
* the given {@link Session#getId() Session Identifier}.
|
||||
*
|
||||
* Additionally, the {@link #creationTime} is set to {@link Instant#now()}, {@link #lastAccessedTime}
|
||||
* is set to {@link #creationTime} and the {@link #maxInactiveInterval} is set to {@link Duration#ZERO}.
|
||||
*
|
||||
* @param id {@link String} containing the unique identifier for this {@link Session}.
|
||||
* @see #validateSessionId(String)
|
||||
*/
|
||||
protected GemFireSession(String id) {
|
||||
|
||||
return Optional.ofNullable(id)
|
||||
.filter(StringUtils::hasText)
|
||||
.orElseThrow(() -> newIllegalArgumentException("ID is required"));
|
||||
this.id = validateSessionId(id);
|
||||
this.creationTime = Instant.now();
|
||||
this.lastAccessedTime = this.creationTime;
|
||||
this.maxInactiveInterval = DEFAULT_MAX_INACTIVE_INTERVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link GemFireSession} copied from the given {@link Session}.
|
||||
*
|
||||
* @param session {@link Session} to copy.
|
||||
* @throws IllegalArgumentException if {@link Session} is {@literal null}.
|
||||
* @see org.springframework.session.Session
|
||||
*/
|
||||
protected GemFireSession(Session session) {
|
||||
|
||||
Assert.notNull(session, "The Session to copy must not be null");
|
||||
|
||||
this.id = session.getId();
|
||||
this.creationTime = session.getCreationTime();
|
||||
this.lastAccessedTime = session.getLastAccessedTime();
|
||||
this.maxInactiveInterval = session.getMaxInactiveInterval();
|
||||
this.sessionAttributes.from(session);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -754,19 +781,52 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
return (T) new GemFireSessionAttributes(lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the {@link String identifier} of this {@link Session}.
|
||||
*
|
||||
* @return the new {@link String identifier} of of this {@link Session}.
|
||||
* @see #generateSessionId()
|
||||
* @see #triggerDelta()
|
||||
* @see #getId()
|
||||
*/
|
||||
@Override
|
||||
public synchronized String changeSessionId() {
|
||||
|
||||
this.id = generateId();
|
||||
this.id = generateSessionId();
|
||||
|
||||
markDirty();
|
||||
triggerDelta();
|
||||
|
||||
return getId();
|
||||
}
|
||||
|
||||
public synchronized void clearDelta() {
|
||||
/**
|
||||
* Randomly generates a {@link String unique identifier} (ID) from {@link UUID} to be used as
|
||||
* the {@link Session#getId() ID} for this {@link Session}.
|
||||
*
|
||||
* @return a new {@link String unique identifier (ID)}.
|
||||
* @see java.util.UUID#randomUUID()
|
||||
*/
|
||||
private static String generateSessionId() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given {@link Session} {@link String identifier} (ID) is set and valid.
|
||||
*
|
||||
* @param id {@link String} containing the {@link Session} identifier.
|
||||
* @return the given {@link String ID}.
|
||||
* @throws IllegalArgumentException if {@link String} contains no value.
|
||||
*/
|
||||
private static String validateSessionId(String id) {
|
||||
|
||||
Assert.hasText(id, "ID is required");
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
protected synchronized void commit() {
|
||||
this.delta = false;
|
||||
getAttributes().commit();
|
||||
}
|
||||
|
||||
public synchronized boolean hasDelta() {
|
||||
@@ -777,29 +837,12 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
triggerDelta(true);
|
||||
}
|
||||
|
||||
protected synchronized void triggerDelta(boolean condition) {
|
||||
this.delta |= condition;
|
||||
}
|
||||
|
||||
public synchronized void commit() {
|
||||
this.dirty = false;
|
||||
getAttributes().commit();
|
||||
}
|
||||
|
||||
protected synchronized boolean isDirty() {
|
||||
return this.dirty || getAttributes().isDirty();
|
||||
}
|
||||
|
||||
protected synchronized void markDirty() {
|
||||
markDirty(true);
|
||||
}
|
||||
|
||||
protected synchronized void markDirty(boolean dirty) {
|
||||
this.dirty |= dirty;
|
||||
protected synchronized void triggerDelta(boolean delta) {
|
||||
this.delta |= delta;
|
||||
}
|
||||
|
||||
synchronized void setId(String id) {
|
||||
this.id = validateId(id);
|
||||
this.id = validateSessionId(id);
|
||||
}
|
||||
|
||||
public synchronized String getId() {
|
||||
@@ -850,10 +893,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
|
||||
public synchronized void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
|
||||
boolean changed = !ObjectUtils.nullSafeEquals(this.lastAccessedTime, lastAccessedTime);
|
||||
|
||||
markDirty(changed);
|
||||
triggerDelta(changed);
|
||||
triggerDelta(!ObjectUtils.nullSafeEquals(this.lastAccessedTime, lastAccessedTime));
|
||||
|
||||
this.lastAccessedTime = lastAccessedTime;
|
||||
}
|
||||
@@ -862,18 +902,17 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
return this.lastAccessedTime;
|
||||
}
|
||||
|
||||
public synchronized void setMaxInactiveInterval(Duration maxInactiveIntervalInSeconds) {
|
||||
public synchronized void setMaxInactiveInterval(Duration maxInactiveInterval) {
|
||||
|
||||
boolean changed = !ObjectUtils.nullSafeEquals(this.maxInactiveInterval, maxInactiveIntervalInSeconds);
|
||||
triggerDelta(!ObjectUtils.nullSafeEquals(this.maxInactiveInterval, maxInactiveInterval));
|
||||
|
||||
markDirty(changed);
|
||||
triggerDelta(changed);
|
||||
|
||||
this.maxInactiveInterval = maxInactiveIntervalInSeconds;
|
||||
this.maxInactiveInterval = maxInactiveInterval;
|
||||
}
|
||||
|
||||
public synchronized Duration getMaxInactiveInterval() {
|
||||
return Optional.ofNullable(this.maxInactiveInterval).orElse(DEFAULT_MAX_INACTIVE_INTERVAL);
|
||||
|
||||
return Optional.ofNullable(this.maxInactiveInterval)
|
||||
.orElse(DEFAULT_MAX_INACTIVE_INTERVAL);
|
||||
}
|
||||
|
||||
public synchronized void setPrincipalName(String principalName) {
|
||||
@@ -950,6 +989,14 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
super(lock);
|
||||
}
|
||||
|
||||
protected Map<String, Object> getSessionAttributeDeltas() {
|
||||
|
||||
synchronized (getLock()) {
|
||||
return this.sessionAttributeDeltas;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setAttribute(String attributeName, Object attributeValue) {
|
||||
|
||||
synchronized (getLock()) {
|
||||
@@ -959,7 +1006,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
Object previousAttributeValue = super.setAttribute(attributeName, attributeValue);
|
||||
|
||||
if (!attributeValue.equals(previousAttributeValue)) {
|
||||
this.sessionAttributeDeltas.put(attributeName, attributeValue);
|
||||
getSessionAttributeDeltas().put(attributeName, attributeValue);
|
||||
}
|
||||
|
||||
return previousAttributeValue;
|
||||
@@ -977,7 +1024,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
|
||||
return Optional.ofNullable(super.removeAttribute(attributeName))
|
||||
.map(previousAttributeValue -> {
|
||||
this.sessionAttributeDeltas.put(attributeName, null);
|
||||
getSessionAttributeDeltas().put(attributeName, null);
|
||||
return previousAttributeValue;
|
||||
})
|
||||
.orElse(null);
|
||||
@@ -988,14 +1035,14 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
|
||||
synchronized (getLock()) {
|
||||
|
||||
out.writeInt(this.sessionAttributeDeltas.size());
|
||||
Map<String, Object> sessionAttributeDeltas = getSessionAttributeDeltas();
|
||||
|
||||
for (Map.Entry<String, Object> entry : this.sessionAttributeDeltas.entrySet()) {
|
||||
out.writeInt(sessionAttributeDeltas.size());
|
||||
|
||||
for (Entry<String, Object> entry : sessionAttributeDeltas.entrySet()) {
|
||||
out.writeUTF(entry.getKey());
|
||||
writeObject(entry.getValue(), out);
|
||||
}
|
||||
|
||||
clearDelta();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1007,7 +1054,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
public boolean hasDelta() {
|
||||
|
||||
synchronized (getLock()) {
|
||||
return !this.sessionAttributeDeltas.isEmpty();
|
||||
return !getSessionAttributeDeltas().isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,9 +1071,11 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
deltas.put(in.readUTF(), readObject(in));
|
||||
}
|
||||
|
||||
Map<String, Object> sessionAttributeDeltas = getSessionAttributeDeltas();
|
||||
|
||||
deltas.forEach((key, value) -> {
|
||||
setAttribute(key, value);
|
||||
this.sessionAttributeDeltas.remove(key);
|
||||
sessionAttributeDeltas.remove(key);
|
||||
});
|
||||
}
|
||||
catch (ClassNotFoundException cause) {
|
||||
@@ -1040,16 +1089,17 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearDelta() {
|
||||
protected void commit() {
|
||||
|
||||
synchronized (getLock()) {
|
||||
this.sessionAttributeDeltas.clear();
|
||||
getSessionAttributeDeltas().clear();
|
||||
super.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The GemFireSessionAttributes class is a container for Session attributes implementing
|
||||
* The {@link GemFireSessionAttributes} class is a container for Session attributes implementing
|
||||
* both the {@link DataSerializable} and {@link Delta} Pivotal GemFire interfaces for efficient
|
||||
* storage and distribution (replication) in GemFire. Additionally, GemFireSessionAttributes
|
||||
* extends {@link AbstractMap} providing {@link Map}-like behavior since attributes of a Session
|
||||
@@ -1063,20 +1113,6 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
@SuppressWarnings("serial")
|
||||
public static class GemFireSessionAttributes extends AbstractMap<String, Object> {
|
||||
|
||||
private volatile transient boolean dirty = false;
|
||||
|
||||
private transient final Map<String, Object> sessionAttributes = new HashMap<>();
|
||||
|
||||
private transient final Object lock;
|
||||
|
||||
protected GemFireSessionAttributes() {
|
||||
this.lock = this;
|
||||
}
|
||||
|
||||
protected GemFireSessionAttributes(Object lock) {
|
||||
this.lock = lock != null ? lock : this;
|
||||
}
|
||||
|
||||
public static GemFireSessionAttributes create() {
|
||||
return new GemFireSessionAttributes();
|
||||
}
|
||||
@@ -1085,7 +1121,38 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
return new GemFireSessionAttributes(lock);
|
||||
}
|
||||
|
||||
protected Object getLock() {
|
||||
private transient boolean delta = false;
|
||||
|
||||
private transient final Map<String, Object> sessionAttributes = new HashMap<>();
|
||||
|
||||
private transient final Object lock;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link GemFireSessionAttributes}.
|
||||
*/
|
||||
protected GemFireSessionAttributes() {
|
||||
this.lock = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link GemFireSessionAttributes} initialized with the given {@link Object lock}
|
||||
* to use to guard against concurrent access by multiple {@link Thread Threads}.
|
||||
*
|
||||
* @param lock {@link Object} used as the {@literal mutex} to guard the operations of this object
|
||||
* from concurrent access by multiple {@link Thread Threads}.
|
||||
*/
|
||||
protected GemFireSessionAttributes(@Nullable Object lock) {
|
||||
this.lock = lock != null ? lock : this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Object} used as the {@literal lock} guarding the methods of this object
|
||||
* from concurrent access by multiple {@link Thread Threads}.
|
||||
*
|
||||
* @return the {@link Object lock} guarding the methods of this object from concurrent access
|
||||
* by multiple {@link Thread Threads}.
|
||||
*/
|
||||
public Object getLock() {
|
||||
return this.lock;
|
||||
}
|
||||
|
||||
@@ -1102,7 +1169,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
|
||||
Object previousAttributeValue = this.sessionAttributes.put(attributeName, attributeValue);
|
||||
|
||||
this.dirty |= !attributeValue.equals(previousAttributeValue);
|
||||
this.delta |= !attributeValue.equals(previousAttributeValue);
|
||||
|
||||
return previousAttributeValue;
|
||||
}
|
||||
@@ -1111,7 +1178,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
|
||||
synchronized (getLock()) {
|
||||
|
||||
this.dirty |= this.sessionAttributes.containsKey(attributeName);
|
||||
this.delta |= this.sessionAttributes.containsKey(attributeName);
|
||||
|
||||
return this.sessionAttributes.remove(attributeName);
|
||||
}
|
||||
@@ -1128,7 +1195,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
public Set<String> getAttributeNames() {
|
||||
|
||||
synchronized (getLock()) {
|
||||
return Collections.unmodifiableSet(new HashSet<>(this.sessionAttributes.keySet()));
|
||||
return Collections.unmodifiableSet(this.sessionAttributes.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1136,25 +1203,29 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
@SuppressWarnings("all")
|
||||
public Set<Entry<String, Object>> entrySet() {
|
||||
|
||||
return new AbstractSet<Entry<String, Object>>() {
|
||||
synchronized (getLock()) {
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<String, Object>> iterator() {
|
||||
return Collections.unmodifiableMap(GemFireSessionAttributes.this.sessionAttributes)
|
||||
.entrySet().iterator();
|
||||
}
|
||||
return new AbstractSet<Entry<String, Object>>() {
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return GemFireSessionAttributes.this.sessionAttributes.size();
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public Iterator<Entry<String, Object>> iterator() {
|
||||
return Collections.unmodifiableMap(GemFireSessionAttributes.this.sessionAttributes)
|
||||
.entrySet().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return GemFireSessionAttributes.this.sessionAttributes.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void clearDelta() { }
|
||||
protected void commit() {
|
||||
|
||||
public void commit() {
|
||||
this.dirty = false;
|
||||
synchronized (getLock()) {
|
||||
this.delta = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void from(Session session) {
|
||||
@@ -1181,16 +1252,18 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
}
|
||||
|
||||
public boolean hasDelta() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isDirty() {
|
||||
return this.dirty;
|
||||
}
|
||||
synchronized (getLock()) {
|
||||
return this.delta;
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.sessionAttributes.toString();
|
||||
|
||||
synchronized (getLock()) {
|
||||
return this.sessionAttributes.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ import org.springframework.session.SessionRepository;
|
||||
public class GemFireOperationsSessionRepository extends AbstractGemFireOperationsSessionRepository {
|
||||
|
||||
// Pivotal GemFire OQL query used to lookup Sessions by arbitrary attributes.
|
||||
protected static final String FIND_SESSIONS_BY_INDEX_NAME_INDEX_VALUE_QUERY =
|
||||
protected static final String FIND_SESSIONS_BY_INDEX_NAME_AND_INDEX_VALUE_QUERY =
|
||||
"SELECT s FROM %1$s s WHERE s.attributes['%2$s'] = $1";
|
||||
|
||||
// Pivotal GemFire OQL query used to look up Sessions by principal name.
|
||||
@@ -63,6 +63,44 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
|
||||
super(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Session} instance backed by GemFire.
|
||||
*
|
||||
* @return an instance of {@link Session} backed by GemFire.
|
||||
* @see AbstractGemFireOperationsSessionRepository.GemFireSession#create(Duration)
|
||||
* @see org.springframework.session.Session
|
||||
* @see #getMaxInactiveIntervalInSeconds()
|
||||
*/
|
||||
@NonNull
|
||||
public Session createSession() {
|
||||
return GemFireSession.create(getMaxInactiveInterval());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a copy of an existing, non-expired {@link Session} by ID.
|
||||
*
|
||||
* If the {@link Session} is expired, then the {@link Session }is deleted.
|
||||
*
|
||||
* @param sessionId a String indicating the ID of the Session to get.
|
||||
* @return an existing {@link Session} by ID or null if no {@link Session} exists.
|
||||
* @see AbstractGemFireOperationsSessionRepository.GemFireSession#from(Session)
|
||||
* @see org.springframework.session.Session
|
||||
* @see #deleteById(String)
|
||||
*/
|
||||
@Nullable
|
||||
public Session findById(String sessionId) {
|
||||
|
||||
Session storedSession = getTemplate().get(sessionId);
|
||||
|
||||
if (storedSession != null) {
|
||||
storedSession = storedSession.isExpired()
|
||||
? delete(storedSession)
|
||||
: touch(commit(GemFireSession.from(storedSession)));
|
||||
}
|
||||
|
||||
return storedSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up all available Sessions with the particular attribute indexed by name
|
||||
* having the given value.
|
||||
@@ -99,47 +137,9 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
|
||||
*/
|
||||
protected String prepareQuery(String indexName) {
|
||||
|
||||
return (PRINCIPAL_NAME_INDEX_NAME.equals(indexName)
|
||||
return PRINCIPAL_NAME_INDEX_NAME.equals(indexName)
|
||||
? String.format(FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, getFullyQualifiedRegionName())
|
||||
: String.format(FIND_SESSIONS_BY_INDEX_NAME_INDEX_VALUE_QUERY, getFullyQualifiedRegionName(), indexName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Session} instance backed by GemFire.
|
||||
*
|
||||
* @return an instance of {@link Session} backed by GemFire.
|
||||
* @see AbstractGemFireOperationsSessionRepository.GemFireSession#create(Duration)
|
||||
* @see org.springframework.session.Session
|
||||
* @see #getMaxInactiveIntervalInSeconds()
|
||||
*/
|
||||
@NonNull
|
||||
public Session createSession() {
|
||||
return GemFireSession.create(getMaxInactiveInterval());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a copy of an existing, non-expired {@link Session} by ID.
|
||||
*
|
||||
* If the {@link Session} is expired, then the {@link Session }is deleted.
|
||||
*
|
||||
* @param sessionId a String indicating the ID of the Session to get.
|
||||
* @return an existing {@link Session} by ID or null if no {@link Session} exists.
|
||||
* @see AbstractGemFireOperationsSessionRepository.GemFireSession#from(Session)
|
||||
* @see org.springframework.session.Session
|
||||
* @see #deleteById(String)
|
||||
*/
|
||||
@Nullable
|
||||
public Session findById(String sessionId) {
|
||||
|
||||
Session storedSession = getTemplate().get(sessionId);
|
||||
|
||||
if (storedSession != null) {
|
||||
storedSession = storedSession.isExpired()
|
||||
? delete(storedSession)
|
||||
: touch(GemFireSession.from(storedSession));
|
||||
}
|
||||
|
||||
return storedSession;
|
||||
: String.format(FIND_SESSIONS_BY_INDEX_NAME_AND_INDEX_VALUE_QUERY, getFullyQualifiedRegionName(), indexName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,41 +161,16 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
|
||||
}
|
||||
|
||||
private boolean isDirty(@NonNull Session session) {
|
||||
return !(session instanceof GemFireSession) || ((GemFireSession) session).isDirty();
|
||||
return !(session instanceof GemFireSession) || ((GemFireSession) session).hasDelta();
|
||||
}
|
||||
|
||||
/*private*/ void doSave(@NonNull Session session) {
|
||||
void doSave(@NonNull Session session) {
|
||||
|
||||
boolean usingEagerCommits = isUsingEagerCommit();
|
||||
// Save Session As GemFireSession
|
||||
getTemplate().put(session.getId(), GemFireSession.from(session));
|
||||
|
||||
GemFireSession sessionToSave = usingEagerCommits
|
||||
? GemFireSession.copyCommitted(session)
|
||||
: GemFireSession.from(session);
|
||||
|
||||
try {
|
||||
// Save Session As GemFireSession
|
||||
getTemplate().put(session.getId(), sessionToSave);
|
||||
|
||||
if (isCommittable(usingEagerCommits, session)) {
|
||||
((GemFireSession) session).commit();
|
||||
}
|
||||
}
|
||||
catch (RuntimeException cause) {
|
||||
|
||||
if (isEagerlyCommittable(usingEagerCommits, session)) {
|
||||
((GemFireSession) session).markDirty();
|
||||
}
|
||||
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCommittable(boolean usingEagerCommits, @Nullable Session session) {
|
||||
return !usingEagerCommits && session instanceof GemFireSession;
|
||||
}
|
||||
|
||||
private boolean isEagerlyCommittable(boolean usingEagerCommits, @Nullable Session session) {
|
||||
return usingEagerCommits && session instanceof GemFireSession;
|
||||
// Commit Session
|
||||
commit(session);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,10 +37,12 @@ import org.springframework.session.data.gemfire.serialization.data.AbstractDataS
|
||||
* framework.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.io.DataInput
|
||||
* @see java.io.DataOutput
|
||||
* @see org.apache.geode.DataSerializer
|
||||
* @see org.springframework.session.Session
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.DeltaCapableGemFireSessionAttributes
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes
|
||||
* @see org.springframework.session.data.gemfire.serialization.SessionSerializer
|
||||
* @see org.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer
|
||||
* @since 2.0.0
|
||||
@@ -85,7 +87,7 @@ public class DataSerializableSessionAttributesSerializer
|
||||
@Override
|
||||
public void serialize(GemFireSessionAttributes sessionAttributes, DataOutput out) {
|
||||
|
||||
synchronized (sessionAttributes) {
|
||||
synchronized (sessionAttributes.getLock()) {
|
||||
|
||||
Set<String> attributeNames = nullSafeSet(sessionAttributes.getAttributeNames());
|
||||
|
||||
@@ -95,8 +97,6 @@ public class DataSerializableSessionAttributesSerializer
|
||||
safeWrite(out, output -> output.writeUTF(attributeName));
|
||||
safeWrite(out, output -> serializeObject(sessionAttributes.getAttribute(attributeName), output));
|
||||
});
|
||||
|
||||
sessionAttributes.clearDelta();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,8 +109,6 @@ public class DataSerializableSessionAttributesSerializer
|
||||
sessionAttributes.setAttribute(safeRead(in, DataInput::readUTF), safeRead(in, this::deserializeObject));
|
||||
}
|
||||
|
||||
sessionAttributes.clearDelta();
|
||||
|
||||
return sessionAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +45,13 @@ import org.springframework.util.StringUtils;
|
||||
* @see java.io.DataOutput
|
||||
* @see org.apache.geode.DataSerializer
|
||||
* @see org.springframework.session.Session
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.DeltaCapableGemFireSession
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.DeltaCapableGemFireSessionAttributes
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes
|
||||
* @see org.springframework.session.data.gemfire.serialization.SessionSerializer
|
||||
* @see org.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer
|
||||
* @see org.springframework.session.data.gemfire.support.AbstractSession
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@@ -101,18 +104,15 @@ public class DataSerializableSessionSerializer extends AbstractDataSerializableS
|
||||
|
||||
String principalName = session.getPrincipalName();
|
||||
|
||||
int length = StringUtils.hasText(principalName) ? principalName.length() : 0;
|
||||
int principalNameLength = StringUtils.hasText(principalName) ? principalName.length() : 0;
|
||||
|
||||
safeWrite(out, output -> output.writeInt(length));
|
||||
safeWrite(out, output -> output.writeInt(principalNameLength));
|
||||
|
||||
if (length > 0) {
|
||||
if (principalNameLength > 0) {
|
||||
safeWrite(out, output -> output.writeUTF(principalName));
|
||||
}
|
||||
|
||||
safeWrite(out, output -> serializeObject(session.getAttributes(), output));
|
||||
|
||||
session.getAttributes().clearDelta();
|
||||
session.clearDelta();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,8 +154,6 @@ public class DataSerializableSessionSerializer extends AbstractDataSerializableS
|
||||
}
|
||||
|
||||
session.getAttributes().from(this.<GemFireSessionAttributes>safeRead(in, this::deserializeObject));
|
||||
session.getAttributes().clearDelta();
|
||||
session.clearDelta();
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -120,7 +120,6 @@ public class GemFireOperationsSessionRepositoryTests {
|
||||
this.sessionRepository.setApplicationEventPublisher(this.mockApplicationEventPublisher);
|
||||
this.sessionRepository.setMaxInactiveIntervalInSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
this.sessionRepository.setUseDataSerialization(false);
|
||||
this.sessionRepository.setUseEagerCommit(false);
|
||||
this.sessionRepository.afterPropertiesSet();
|
||||
|
||||
assertThat(this.sessionRepository.getApplicationEventPublisher()).isSameAs(this.mockApplicationEventPublisher);
|
||||
@@ -128,7 +127,6 @@ public class GemFireOperationsSessionRepositoryTests {
|
||||
assertThat(this.sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
assertThat(this.sessionRepository.getTemplate()).isSameAs(this.mockTemplate);
|
||||
assertThat(GemFireOperationsSessionRepository.isUsingDataSerialization()).isFalse();
|
||||
assertThat(this.sessionRepository.isUsingEagerCommit()).isFalse();
|
||||
}
|
||||
|
||||
private Session mockSession() {
|
||||
@@ -150,6 +148,15 @@ public class GemFireOperationsSessionRepositoryTests {
|
||||
return mockSession;
|
||||
}
|
||||
|
||||
private GemFireSession newNonDirtyGemFireSession() {
|
||||
|
||||
GemFireSession session = GemFireSession.create();
|
||||
|
||||
session.commit();;
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
|
||||
@@ -158,9 +165,111 @@ public class GemFireOperationsSessionRepositoryTests {
|
||||
verify(this.mockTemplate, times(1)).getRegion();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createProperlyInitializedSession() {
|
||||
|
||||
Instant beforeOrAtCreationTime = Instant.now();
|
||||
|
||||
Session session = this.sessionRepository.createSession();
|
||||
|
||||
assertThat(session).isInstanceOf(AbstractGemFireOperationsSessionRepository.GemFireSession.class);
|
||||
assertThat(session.getId()).isNotNull();
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
assertThat(session.getCreationTime().compareTo(beforeOrAtCreationTime)).isGreaterThanOrEqualTo(0);
|
||||
assertThat(session.getLastAccessedTime().compareTo(beforeOrAtCreationTime)).isGreaterThanOrEqualTo(0);
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIdDeletesMatchingExpiredSessionReturnsNull() {
|
||||
|
||||
String expectedSessionId = "1";
|
||||
|
||||
Session mockSession = mock(Session.class);
|
||||
|
||||
given(mockSession.isExpired()).willReturn(true);
|
||||
given(mockSession.getId()).willReturn(expectedSessionId);
|
||||
given(this.mockTemplate.get(eq(expectedSessionId))).willReturn(mockSession);
|
||||
given(this.mockTemplate.remove(eq(expectedSessionId))).willReturn(mockSession);
|
||||
|
||||
willAnswer(invocation -> {
|
||||
|
||||
ApplicationEvent applicationEvent = invocation.getArgument(0);
|
||||
|
||||
assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class);
|
||||
|
||||
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
|
||||
|
||||
assertThat(sessionEvent.<Session>getSession()).isSameAs(mockSession);
|
||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
|
||||
assertThat(sessionEvent.getSource())
|
||||
.isSameAs(GemFireOperationsSessionRepositoryTests.this.sessionRepository);
|
||||
|
||||
return null;
|
||||
|
||||
}).given(this.mockApplicationEventPublisher).publishEvent(any(ApplicationEvent.class));
|
||||
|
||||
assertThat(this.sessionRepository.findById(expectedSessionId)).isNull();
|
||||
|
||||
verify(this.mockTemplate, times(1)).get(eq(expectedSessionId));
|
||||
verify(this.mockTemplate, times(1)).remove(eq(expectedSessionId));
|
||||
verify(mockSession, times(1)).isExpired();
|
||||
verify(mockSession, times(2)).getId();
|
||||
verify(this.mockApplicationEventPublisher, times(1))
|
||||
.publishEvent(isA(SessionDeletedEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIdReturnsMatchingNonExpiredSession() {
|
||||
|
||||
String expectedId = "1";
|
||||
|
||||
Instant expectedCreationTime = Instant.now();
|
||||
Instant currentLastAccessedTime = expectedCreationTime.plusMillis(TimeUnit.MINUTES.toMillis(5));
|
||||
|
||||
Session mockSession = mock(Session.class);
|
||||
|
||||
given(mockSession.isExpired()).willReturn(false);
|
||||
given(mockSession.getId()).willReturn(expectedId);
|
||||
given(mockSession.getCreationTime()).willReturn(expectedCreationTime);
|
||||
given(mockSession.getLastAccessedTime()).willReturn(currentLastAccessedTime);
|
||||
given(mockSession.getAttributeNames()).willReturn(Collections.singleton("attrOne"));
|
||||
given(mockSession.getAttribute(eq("attrOne"))).willReturn("test");
|
||||
given(this.mockTemplate.get(eq(expectedId))).willReturn(mockSession);
|
||||
|
||||
Session actualSession = this.sessionRepository.findById(expectedId);
|
||||
|
||||
assertThat(actualSession).isNotNull();
|
||||
assertThat(actualSession).isNotSameAs(mockSession);
|
||||
assertThat(actualSession.getId()).isEqualTo(expectedId);
|
||||
assertThat(actualSession.getCreationTime()).isEqualTo(expectedCreationTime);
|
||||
assertThat(actualSession.getLastAccessedTime()).isNotEqualTo(currentLastAccessedTime);
|
||||
assertThat(actualSession.getLastAccessedTime().compareTo(expectedCreationTime)).isGreaterThanOrEqualTo(0);
|
||||
assertThat(actualSession.getAttributeNames()).isEqualTo(Collections.singleton("attrOne"));
|
||||
assertThat(String.valueOf(actualSession.<String>getAttribute("attrOne"))).isEqualTo("test");
|
||||
|
||||
verify(this.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 findByIdReturnsNull() {
|
||||
|
||||
when(this.mockTemplate.get(anyString())).thenReturn(null);
|
||||
|
||||
assertThat(this.sessionRepository.findById("1")).isNull();
|
||||
|
||||
verify(this.mockTemplate, times(1)).get(eq("1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void findByIndexNameAndIndexValueFindsMatchingSession() {
|
||||
public void findByIndexNameAndIndexValueReturnsMatchingSession() {
|
||||
|
||||
Session mockSession = mock(Session.class, "MockSession");
|
||||
|
||||
@@ -174,7 +283,7 @@ public class GemFireOperationsSessionRepositoryTests {
|
||||
String indexValue = "rwinch";
|
||||
|
||||
String expectedQql =
|
||||
String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_INDEX_NAME_INDEX_VALUE_QUERY,
|
||||
String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_INDEX_NAME_AND_INDEX_VALUE_QUERY,
|
||||
this.sessionRepository.getFullyQualifiedRegionName(), indexName);
|
||||
|
||||
given(this.mockTemplate.find(eq(expectedQql), eq(indexValue))).willReturn(mockSelectResults);
|
||||
@@ -193,7 +302,7 @@ public class GemFireOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void findByPrincipalNameFindsMatchingSessions() throws Exception {
|
||||
public void findByPrincipalNameReturnsMatchingSessions() throws Exception {
|
||||
|
||||
Session mockSessionOne = mock(Session.class, "MockSessionOne");
|
||||
Session mockSessionTwo = mock(Session.class, "MockSessionTwo");
|
||||
@@ -265,7 +374,7 @@ public class GemFireOperationsSessionRepositoryTests {
|
||||
String actualOql = this.sessionRepository.prepareQuery(attributeName);
|
||||
|
||||
String expectedOql =
|
||||
String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_INDEX_NAME_INDEX_VALUE_QUERY,
|
||||
String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_INDEX_NAME_AND_INDEX_VALUE_QUERY,
|
||||
this.sessionRepository.getFullyQualifiedRegionName(), attributeName);
|
||||
|
||||
assertThat(actualOql).isEqualTo(expectedOql);
|
||||
@@ -274,8 +383,7 @@ public class GemFireOperationsSessionRepositoryTests {
|
||||
@Test
|
||||
public void prepareQueryReturnsPrincipalNameOql() {
|
||||
|
||||
String actualQql =
|
||||
this.sessionRepository.prepareQuery(PRINCIPAL_NAME_INDEX_NAME);
|
||||
String actualQql = this.sessionRepository.prepareQuery(PRINCIPAL_NAME_INDEX_NAME);
|
||||
|
||||
String expectedOql =
|
||||
String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
||||
@@ -284,104 +392,6 @@ public class GemFireOperationsSessionRepositoryTests {
|
||||
assertThat(actualQql).isEqualTo(expectedOql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createProperlyInitializedSession() {
|
||||
|
||||
Instant beforeOrAtCreationTime = Instant.now();
|
||||
|
||||
Session session = this.sessionRepository.createSession();
|
||||
|
||||
assertThat(session).isInstanceOf(AbstractGemFireOperationsSessionRepository.GemFireSession.class);
|
||||
assertThat(session.getId()).isNotNull();
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
assertThat(session.getCreationTime().compareTo(beforeOrAtCreationTime)).isGreaterThanOrEqualTo(0);
|
||||
assertThat(session.getLastAccessedTime().compareTo(beforeOrAtCreationTime)).isGreaterThanOrEqualTo(0);
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionDeletesMatchingExpiredSessionById() {
|
||||
|
||||
String expectedSessionId = "1";
|
||||
|
||||
Session mockSession = mock(Session.class);
|
||||
|
||||
given(mockSession.isExpired()).willReturn(true);
|
||||
given(mockSession.getId()).willReturn(expectedSessionId);
|
||||
given(this.mockTemplate.get(eq(expectedSessionId))).willReturn(mockSession);
|
||||
given(this.mockTemplate.remove(eq(expectedSessionId))).willReturn(mockSession);
|
||||
|
||||
willAnswer(invocation -> {
|
||||
|
||||
ApplicationEvent applicationEvent = invocation.getArgument(0);
|
||||
|
||||
assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class);
|
||||
|
||||
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
|
||||
|
||||
assertThat(sessionEvent.<Session>getSession()).isSameAs(mockSession);
|
||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
|
||||
assertThat(sessionEvent.getSource())
|
||||
.isSameAs(GemFireOperationsSessionRepositoryTests.this.sessionRepository);
|
||||
|
||||
return null;
|
||||
|
||||
}).given(this.mockApplicationEventPublisher).publishEvent(any(ApplicationEvent.class));
|
||||
|
||||
assertThat(this.sessionRepository.findById(expectedSessionId)).isNull();
|
||||
|
||||
verify(this.mockTemplate, times(1)).get(eq(expectedSessionId));
|
||||
verify(this.mockTemplate, times(1)).remove(eq(expectedSessionId));
|
||||
verify(mockSession, times(1)).isExpired();
|
||||
verify(mockSession, times(2)).getId();
|
||||
verify(this.mockApplicationEventPublisher, times(1))
|
||||
.publishEvent(isA(SessionDeletedEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionFindsMatchingNonExpiredSessionById() {
|
||||
|
||||
String expectedId = "1";
|
||||
|
||||
Instant expectedCreationTime = Instant.now();
|
||||
Instant currentLastAccessedTime = expectedCreationTime.plusMillis(TimeUnit.MINUTES.toMillis(5));
|
||||
|
||||
Session mockSession = mock(Session.class);
|
||||
|
||||
given(mockSession.isExpired()).willReturn(false);
|
||||
given(mockSession.getId()).willReturn(expectedId);
|
||||
given(mockSession.getCreationTime()).willReturn(expectedCreationTime);
|
||||
given(mockSession.getLastAccessedTime()).willReturn(currentLastAccessedTime);
|
||||
given(mockSession.getAttributeNames()).willReturn(Collections.singleton("attrOne"));
|
||||
given(mockSession.getAttribute(eq("attrOne"))).willReturn("test");
|
||||
given(this.mockTemplate.get(eq(expectedId))).willReturn(mockSession);
|
||||
|
||||
Session actualSession = this.sessionRepository.findById(expectedId);
|
||||
|
||||
assertThat(actualSession).isNotNull();
|
||||
assertThat(actualSession).isNotSameAs(mockSession);
|
||||
assertThat(actualSession.getId()).isEqualTo(expectedId);
|
||||
assertThat(actualSession.getCreationTime()).isEqualTo(expectedCreationTime);
|
||||
assertThat(actualSession.getLastAccessedTime()).isNotEqualTo(currentLastAccessedTime);
|
||||
assertThat(actualSession.getLastAccessedTime().compareTo(expectedCreationTime)).isGreaterThanOrEqualTo(0);
|
||||
assertThat(actualSession.getAttributeNames()).isEqualTo(Collections.singleton("attrOne"));
|
||||
assertThat(String.valueOf(actualSession.<String>getAttribute("attrOne"))).isEqualTo("test");
|
||||
|
||||
verify(this.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() {
|
||||
given(this.mockTemplate.get(anyString())).willReturn(null);
|
||||
assertThat(this.sessionRepository.findById("1")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveIsNullSafe() {
|
||||
|
||||
@@ -435,20 +445,18 @@ public class GemFireOperationsSessionRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveStoresAndThenCommitsGemFireSession() {
|
||||
|
||||
assertThat(this.sessionRepository.isUsingEagerCommit()).isFalse();
|
||||
public void saveStoresAndCommitsGemFireSession() {
|
||||
|
||||
GemFireSession<?> session = spy(GemFireSession.create());
|
||||
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(session.isDirty()).isTrue();
|
||||
assertThat(session.hasDelta()).isTrue();
|
||||
|
||||
this.sessionRepository.save(session);
|
||||
|
||||
InOrder orderVerifier = inOrder(session);
|
||||
|
||||
orderVerifier.verify(session, times(2)).isDirty();
|
||||
orderVerifier.verify(session, times(2)).hasDelta();
|
||||
orderVerifier.verify(session, times(1)).getId();
|
||||
orderVerifier.verify(session, times(1)).commit();
|
||||
|
||||
@@ -460,115 +468,19 @@ public class GemFireOperationsSessionRepositoryTests {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void saveWillNotStoreNonDirtyGemFireSessions() {
|
||||
|
||||
GemFireSession session = spy(GemFireSession.from(mockSession()));
|
||||
GemFireSession session = spy(newNonDirtyGemFireSession());
|
||||
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(session.hasDelta()).isFalse();
|
||||
assertThat(session.isDirty()).isFalse();
|
||||
|
||||
this.sessionRepository.save(session);
|
||||
|
||||
verify(session, times(2)).isDirty();
|
||||
verify(session, times(2)).hasDelta();
|
||||
verify(session, never()).getId();
|
||||
verify(session, never()).commit();
|
||||
verify(this.mockTemplate, never()).put(any(), any(GemFireSession.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveEagerlyCommitsAndThenStoresSession() {
|
||||
|
||||
this.sessionRepository.setUseEagerCommit(true);
|
||||
|
||||
assertThat(this.sessionRepository.isUsingEagerCommit()).isTrue();
|
||||
|
||||
GemFireSession<?> session = spy(GemFireSession.create());
|
||||
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(session.getId()).isNotEmpty();
|
||||
assertThat(session.isDirty()).isTrue();
|
||||
assertThat(session.hasDelta()).isFalse();
|
||||
assertThat(session.isExpired()).isFalse();
|
||||
|
||||
when(this.mockTemplate.put(anyString(), any(GemFireSession.class))).thenAnswer(invocation -> {
|
||||
|
||||
String sessionId = invocation.getArgument(0);
|
||||
GemFireSession<?> sessionToSave = invocation.getArgument(1);
|
||||
|
||||
assertThat(sessionId).isEqualTo(session.getId());
|
||||
assertThat(sessionId).isEqualTo(sessionToSave.getId());
|
||||
assertThat(sessionToSave).isNotSameAs(session);
|
||||
|
||||
return sessionToSave;
|
||||
});
|
||||
|
||||
this.sessionRepository.save(session);
|
||||
|
||||
assertThat(session.isDirty()).isFalse();
|
||||
assertThat(session.isExpired()).isFalse();
|
||||
|
||||
InOrder orderVerifier = inOrder(session);
|
||||
|
||||
orderVerifier.verify(session, times(1)).isDirty();
|
||||
orderVerifier.verify(session, times(1)).commit();
|
||||
orderVerifier.verify(session, times(2)).getId();
|
||||
|
||||
verify(this.sessionRepository, times(1)).doSave(eq(session));
|
||||
verify(this.mockTemplate, times(1)).put(eq(session.getId()), isA(GemFireSession.class));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void saveEagerlyCommitsStoresSessionAndResetsDirtyBitOnRuntimeException() {
|
||||
|
||||
this.sessionRepository.setUseEagerCommit(true);
|
||||
|
||||
assertThat(this.sessionRepository.isUsingEagerCommit()).isTrue();
|
||||
|
||||
GemFireSession<?> session = spy(GemFireSession.create());
|
||||
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(session.getId()).isNotEmpty();
|
||||
assertThat(session.isDirty()).isTrue();
|
||||
assertThat(session.hasDelta()).isFalse();
|
||||
assertThat(session.isExpired()).isFalse();
|
||||
|
||||
when(this.mockTemplate.put(anyString(), any(GemFireSession.class))).thenAnswer(invocation -> {
|
||||
|
||||
String sessionId = invocation.getArgument(0);
|
||||
GemFireSession<?> sessionToSave = invocation.getArgument(1);
|
||||
|
||||
assertThat(sessionId).isEqualTo(session.getId());
|
||||
assertThat(sessionId).isEqualTo(sessionToSave.getId());
|
||||
assertThat(sessionToSave).isNotSameAs(session);
|
||||
|
||||
throw new RuntimeException("TEST");
|
||||
});
|
||||
|
||||
try {
|
||||
this.sessionRepository.save(session);
|
||||
}
|
||||
catch (Exception expected) {
|
||||
|
||||
assertThat(expected).hasMessage("TEST");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
|
||||
assertThat(session.isDirty()).isTrue();
|
||||
assertThat(session.isExpired()).isFalse();
|
||||
|
||||
InOrder orderVerifier = inOrder(session);
|
||||
|
||||
orderVerifier.verify(session, times(1)).isDirty();
|
||||
orderVerifier.verify(session, times(1)).commit();
|
||||
orderVerifier.verify(session, times(2)).getId();
|
||||
|
||||
verify(this.sessionRepository, times(1)).doSave(eq(session));
|
||||
verify(this.mockTemplate, times(1)).put(eq(session.getId()), isA(GemFireSession.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteRemovesExistingSessionAndHandlesDelete() {
|
||||
|
||||
|
||||
@@ -75,6 +75,9 @@ public class DataSerializableSessionAttributesSerializerTests {
|
||||
|
||||
GemFireSessionAttributes sessionAttributes = GemFireSessionAttributes.create();
|
||||
|
||||
assertThat(sessionAttributes).isNotNull();
|
||||
assertThat(sessionAttributes.hasDelta()).isFalse();
|
||||
|
||||
sessionAttributes.setAttribute("attrOne", "testOne");
|
||||
sessionAttributes.setAttribute("attrTwo", "testTwo");
|
||||
|
||||
@@ -88,8 +91,12 @@ public class DataSerializableSessionAttributesSerializerTests {
|
||||
|
||||
}).when(sessionAttributesSerializer).serializeObject(any(), any(DataOutput.class));
|
||||
|
||||
assertThat(sessionAttributes.hasDelta()).isTrue();
|
||||
|
||||
sessionAttributesSerializer.serialize(sessionAttributes, mockDataOutput);
|
||||
|
||||
assertThat(sessionAttributes.hasDelta()).isTrue();
|
||||
|
||||
verify(mockDataOutput, times(1)).writeInt(eq(2));
|
||||
verify(mockDataOutput, times(1)).writeUTF(eq("attrOne"));
|
||||
verify(mockDataOutput, times(1)).writeUTF(eq("testOne"));
|
||||
@@ -113,10 +120,11 @@ public class DataSerializableSessionAttributesSerializerTests {
|
||||
GemFireSessionAttributes sessionAttributes = sessionAttributesSerializer.deserialize(mockDataInput);
|
||||
|
||||
assertThat(sessionAttributes).isNotNull();
|
||||
assertThat(sessionAttributes.getAttributeNames()).hasSize(2);
|
||||
assertThat(sessionAttributes).hasSize(2);
|
||||
assertThat(sessionAttributes.getAttributeNames()).containsAll(asSet("attrOne", "attrTwo"));
|
||||
assertThat(sessionAttributes.<String>getAttribute("attrOne")).isEqualTo("testOne");
|
||||
assertThat(sessionAttributes.<String>getAttribute("attrTwo")).isEqualTo("testTwo");
|
||||
assertThat(sessionAttributes.hasDelta()).isTrue();
|
||||
|
||||
verify(mockDataInput, times(1)).readInt();
|
||||
verify(mockDataInput, times(2)).readUTF();
|
||||
|
||||
@@ -75,6 +75,7 @@ public class DataSerializableSessionSerializerTests {
|
||||
|
||||
@Test
|
||||
public void supportedClassContainsGemFireSessionAndSubTypes() {
|
||||
|
||||
assertThat(this.sessionSerializer.getSupportedClasses()).contains(GemFireSession.class);
|
||||
assertThat(this.sessionSerializer.getSupportedClasses()).contains(DeltaCapableGemFireSession.class);
|
||||
}
|
||||
@@ -100,7 +101,7 @@ public class DataSerializableSessionSerializerTests {
|
||||
|
||||
this.sessionSerializer.serialize(session, mockDataOutput);
|
||||
|
||||
assertThat(session.hasDelta()).isFalse();
|
||||
assertThat(session.hasDelta()).isTrue();
|
||||
|
||||
verify(mockDataOutput, times(1)).writeUTF(eq(session.getId()));
|
||||
verify(mockDataOutput, times(1)).writeLong(eq(session.getCreationTime().toEpochMilli()));
|
||||
@@ -150,7 +151,7 @@ public class DataSerializableSessionSerializerTests {
|
||||
assertThat(session.getLastAccessedTime()).isEqualTo(Instant.ofEpochMilli(expectedLastAccessedTime));
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(expectedMaxInactiveIntervalInSeconds));
|
||||
assertThat(session.getPrincipalName()).isEqualTo(expectedPrincipalName);
|
||||
assertThat(session.hasDelta()).isFalse();
|
||||
assertThat(session.hasDelta()).isTrue();
|
||||
assertThat(session.getAttributeNames()).hasSize(3);
|
||||
assertThat(session.getAttributeNames()).containsAll(expectedAttributeNames);
|
||||
assertThat(session.<String>getAttribute("attrOne")).isEqualTo("testOne");
|
||||
|
||||
Reference in New Issue
Block a user