Refactor and simplify non-dirty Session handling.

Resolves gh-16.
This commit is contained in:
John Blum
2018-11-27 00:17:02 -08:00
parent 1c915b27a4
commit adf29e7702
11 changed files with 1311 additions and 1090 deletions

View File

@@ -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);
}

View File

@@ -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();
}
}
}

View File

@@ -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 { }
}

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
/**

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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() {

View File

@@ -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();

View File

@@ -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");