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); waitForTick(2);
assertTick(2); assertTick(2);
// Save Session with no changes, no delta // Save Session with no changes/no delta
assertThat(session instanceof GemFireSession && ((GemFireSession) session).isDirty()).isFalse(); assertThat(session instanceof GemFireSession && ((GemFireSession) session).hasDelta()).isFalse();
save(session); save(session);
} }
@@ -166,6 +166,7 @@ public abstract class AbstractConcurrentSessionOperationsIntegrationTests extend
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeTwo", "attributeThree"); assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeTwo", "attributeThree");
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("three"); assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("three");
assertThat(session instanceof GemFireSession && ((GemFireSession) session).hasDelta()).isTrue();
save(session); save(session);
} }

View File

@@ -18,8 +18,10 @@ package org.springframework.session.data.gemfire;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -28,6 +30,8 @@ import edu.umd.cs.mtc.TestFramework;
import org.apache.geode.cache.client.ClientRegionShortcut; 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.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.session.Session; 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.EnableGemFireHttpSession;
@@ -36,7 +40,7 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner; 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. * using Spring Session backed by Apache Geode or Pivotal GemFire in a Multi-Threaded context.
* *
* @author John Blum * @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.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
* @see org.springframework.test.context.ContextConfiguration * @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringRunner * @see org.springframework.test.context.junit4.SpringRunner
* @since 2.1.1 * @since 2.1.2
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@ContextConfiguration @ContextConfiguration(
classes = MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests.GemFireClientConfiguration.class
)
public class MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests public class MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests
extends AbstractGemFireIntegrationTests { extends AbstractGemFireIntegrationTests {
@BeforeClass
public static void startGemFireServer() throws IOException {
startGemFireServer(GemFireServerConfiguration.class);
}
@Test @Test
public void multiThreadedSessionOperationsAreCorrect() throws Throwable { public void multiThreadedConcurrentSessionOperationsAreCorrect() throws Throwable {
TestFramework.runOnce(new MultiThreadedSessionOperationsTestCase(this)); TestFramework.runOnce(new MultiThreadedConcurrentSessionOperationsTestCase(this));
} }
@SuppressWarnings("unused") @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 AtomicReference<String> sessionId = new AtomicReference<>(null);
private final MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests testInstance; private final MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests testInstance;
public MultiThreadedSessionOperationsTestCase( public MultiThreadedConcurrentSessionOperationsTestCase(
MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests testInstance) { MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests testInstance) {
this.testInstance = testInstance; this.testInstance = testInstance;
@@ -98,51 +109,21 @@ public class MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests
assertThat(session.isExpired()).isFalse(); assertThat(session.isExpired()).isFalse();
assertThat(session.getAttributeNames()).isEmpty(); assertThat(session.getAttributeNames()).isEmpty();
session.setAttribute("attributeOne", "foo");
save(session); save(session);
Session loadedSession = findById(session.getId()); this.sessionId.set(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());
waitForTick(2); waitForTick(2);
assertTick(2); assertTick(2);
Session reloadedSession = findById(loadedSession.getId()); session.setAttribute("attributeOne", "foo");
session.setAttribute("attributeTwo", "bar");
assertThat(reloadedSession).isNotNull(); assertThat(session.<String>getAttribute("attributeOne")).isEqualTo("foo");
assertThat(reloadedSession.getId()).isEqualTo(loadedSession.getId()); assertThat(session.<String>getAttribute("attributeTwo")).isEqualTo("bar");
assertThat(reloadedSession.isExpired()).isFalse(); assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeTwo");
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); save(session);
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() { public void thread2() {
@@ -157,32 +138,35 @@ public class MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests
assertThat(session).isNotNull(); assertThat(session).isNotNull();
assertThat(session.getId()).isEqualTo(this.sessionId.get()); assertThat(session.getId()).isEqualTo(this.sessionId.get());
assertThat(session.isExpired()).isFalse(); assertThat(session.isExpired()).isFalse();
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeTwo"); assertThat(session.getAttributeNames()).isEmpty();
assertThat(session.<String>getAttribute("attributeOne")).isEqualTo("foo");
assertThat(session.<String>getAttribute("attributeTwo")).isEqualTo("bar"); waitForTick(2);
assertTick(2);
session.setAttribute("attributeThree", "baz"); session.setAttribute("attributeThree", "baz");
assertThat(session.getAttributeNames()).containsOnly("attributeThree");
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("baz");
save(session); save(session);
waitForTick(4); waitForTick(4);
assertTick(4); assertTick(4);
Session endSession = findById(session.getId()); assertThat(session.getAttributeNames()).containsOnly("attributeThree");
assertThat(endSession).isNotNull(); session.setAttribute("attributeFour", "qux");
assertThat(endSession.getId()).isEqualTo(session.getId());
assertThat(endSession.isExpired()).isFalse(); assertThat(session.getAttributeNames()).containsOnly("attributeThree", "attributeFour");
assertThat(endSession.getAttributeNames()).containsOnly("attributeOne", "attributeThree"); assertThat(session.<String>getAttribute("attributeFour")).isEqualTo("qux");
assertThat(endSession.getAttributeNames()).doesNotContain("attributeTwo"); assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("baz");
assertThat(endSession.<String>getAttribute("attributeOne")).isEqualTo("foo");
assertThat(endSession.<String>getAttribute("attributeTwo")).isNull(); save(session);
assertThat(endSession.<String>getAttribute("attributeThree")).isEqualTo("baz");
} }
public void thread3() { public void thread3() {
Thread.currentThread().setName("User Session Three"); Thread.currentThread().setName("User Session Four");
waitForTick(3); waitForTick(3);
assertTick(3); assertTick(3);
@@ -197,7 +181,16 @@ public class MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests
assertThat(session.<String>getAttribute("attributeTwo")).isEqualTo("bar"); assertThat(session.<String>getAttribute("attributeTwo")).isEqualTo("bar");
assertThat(session.<String>getAttribute("attributeThree")).isEqualTo("baz"); 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); save(session);
} }
@@ -205,25 +198,47 @@ public class MultiThreadedClientServerHttpSessionAttributesDeltaIntegrationTests
@Override @Override
public void finish() { public void finish() {
super.finish();
Session session = findById(this.sessionId.get()); Session session = findById(this.sessionId.get());
assertThat(session).isNotNull(); assertThat(session).isNotNull();
assertThat(session.getId()).isEqualTo(this.sessionId.get()); assertThat(session.getId()).isEqualTo(this.sessionId.get());
assertThat(session.isExpired()).isFalse(); assertThat(session.isExpired()).isFalse();
assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeThree"); assertThat(session.getAttributeNames()).containsOnly("attributeOne", "attributeThree", "attributeFour");
assertThat(session.getAttributeNames()).doesNotContain("attributeTwo");
assertThat(session.<String>getAttribute("attributeOne")).isEqualTo("foo"); assertThat(session.<String>getAttribute("attributeOne")).isEqualTo("foo");
assertThat(session.<String>getAttribute("attributeTwo")).isNull(); 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( @EnableGemFireHttpSession(
clientRegionShortcut = ClientRegionShortcut.LOCAL, clientRegionShortcut = ClientRegionShortcut.PROXY,
poolName = "DEFAULT", poolName = "DEFAULT",
regionName = "Sessions",
sessionSerializerBeanName = GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME 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; 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 static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
import java.io.DataInput; import java.io.DataInput;
@@ -28,7 +27,6 @@ import java.util.AbstractMap;
import java.util.AbstractSet; import java.util.AbstractSet;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -56,6 +54,8 @@ import org.springframework.data.gemfire.GemfireAccessor;
import org.springframework.data.gemfire.GemfireOperations; import org.springframework.data.gemfire.GemfireOperations;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser; 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.FindByIndexNameSessionRepository;
import org.springframework.session.Session; import org.springframework.session.Session;
import org.springframework.session.SessionRepository; import org.springframework.session.SessionRepository;
@@ -106,7 +106,6 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
implements ApplicationEventPublisherAware, FindByIndexNameSessionRepository<Session>, InitializingBean { implements ApplicationEventPublisherAware, FindByIndexNameSessionRepository<Session>, InitializingBean {
private static final AtomicBoolean usingDataSerialization = new AtomicBoolean(false); private static final AtomicBoolean usingDataSerialization = new AtomicBoolean(false);
private static final AtomicBoolean usingEagerCommit = new AtomicBoolean(false);
private ApplicationEventPublisher applicationEventPublisher = event -> {}; private ApplicationEventPublisher applicationEventPublisher = event -> {};
@@ -284,25 +283,6 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
return usingDataSerialization.get(); 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 * 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} * 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. * @param sessionId {@link Object} containing the session ID to remember.
* @return a boolean value whether Spring Session is interested in and will 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) { Session toSession(Object obj, String sessionId) {
return obj instanceof Session ? (Session) obj return obj instanceof Session
? (Session) obj
: Optional.ofNullable(sessionId) : Optional.ofNullable(sessionId)
.filter(StringUtils::hasText) .filter(StringUtils::hasText)
.map(SessionIdHolder::create) .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. * Deletes the given {@link Session} from GemFire.
* *
@@ -476,7 +476,9 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
* @see #deleteById(String) * @see #deleteById(String)
*/ */
protected Session delete(Session session) { protected Session delete(Session session) {
deleteById(session.getId()); deleteById(session.getId());
return null; return null;
} }
@@ -579,12 +581,12 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
/** /**
* Updates the {@link Session#setLastAccessedTime(Instant)} property of the {@link Session}. * 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. * @param session {@link Session} to touch.
* @return the {@link Session}. * @return the {@link Session}.
* @see org.springframework.session.Session#setLastAccessedTime(Instant) * @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()); session.setLastAccessedTime(Instant.now());
@@ -616,7 +618,6 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
out.writeLong(getLastAccessedTime().toEpochMilli()); out.writeLong(getLastAccessedTime().toEpochMilli());
out.writeLong(getMaxInactiveInterval().getSeconds()); out.writeLong(getMaxInactiveInterval().getSeconds());
getAttributes().toDelta(out); getAttributes().toDelta(out);
clearDelta();
} }
public synchronized void fromDelta(DataInput in) throws IOException { public synchronized void fromDelta(DataInput in) throws IOException {
@@ -625,7 +626,6 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
setLastAccessedTime(Instant.ofEpochMilli(in.readLong())); setLastAccessedTime(Instant.ofEpochMilli(in.readLong()));
setMaxInactiveInterval(Duration.ofSeconds(in.readLong())); setMaxInactiveInterval(Duration.ofSeconds(in.readLong()));
getAttributes().fromDelta(in); 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 Duration DEFAULT_MAX_INACTIVE_INTERVAL = Duration.ZERO;
protected static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; protected static final String GEMFIRE_SESSION_TO_STRING =
private 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 }"; "{ @type = %1$s, id = %2$s, creationTime = %3$s, lastAccessedTime = %4$s, maxInactiveInterval = %5$s, principalName = %6$s }";
private transient boolean delta = false; protected static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
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);
}
/**
* 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() { public static GemFireSession create() {
return create(DEFAULT_MAX_INACTIVE_INTERVAL); 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) { public static GemFireSession create(Duration maxInactiveInterval) {
GemFireSession session = isUsingDataSerialization() GemFireSession session = isUsingDataSerialization()
@@ -698,48 +679,94 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
return session; 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() return isUsingDataSerialization()
? new DeltaCapableGemFireSession(session) ? new DeltaCapableGemFireSession(session)
: new GemFireSession(session); : new GemFireSession(session);
} }
@SuppressWarnings("all") /**
public static GemFireSession copyCommitted(Session session) { * 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}.
synchronized (session) { *
* @param <T> {@link Class sub-type} of {@link GemFireSession}.
GemFireSession sessionCopy = copy(session); * @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
if (session instanceof GemFireSession) { * of the given {@link Session} as a {@link GemFireSession}
((GemFireSession) session).commit(); * @see #copy(Session)
} */
return sessionCopy;
}
}
@SuppressWarnings("unchecked") @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)); 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 #GemFireSession(String)
* @see java.util.UUID#randomUUID() * @see #generateSessionId()
*/ */
private static String generateId() { protected GemFireSession() {
return UUID.randomUUID().toString(); 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) this.id = validateSessionId(id);
.filter(StringUtils::hasText) this.creationTime = Instant.now();
.orElseThrow(() -> newIllegalArgumentException("ID is required")); 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); 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 @Override
public synchronized String changeSessionId() { public synchronized String changeSessionId() {
this.id = generateId(); this.id = generateSessionId();
markDirty();
triggerDelta(); triggerDelta();
return getId(); 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; this.delta = false;
getAttributes().commit();
} }
public synchronized boolean hasDelta() { public synchronized boolean hasDelta() {
@@ -777,29 +837,12 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
triggerDelta(true); triggerDelta(true);
} }
protected synchronized void triggerDelta(boolean condition) { protected synchronized void triggerDelta(boolean delta) {
this.delta |= condition; this.delta |= delta;
}
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;
} }
synchronized void setId(String id) { synchronized void setId(String id) {
this.id = validateId(id); this.id = validateSessionId(id);
} }
public synchronized String getId() { public synchronized String getId() {
@@ -850,10 +893,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
public synchronized void setLastAccessedTime(Instant lastAccessedTime) { public synchronized void setLastAccessedTime(Instant lastAccessedTime) {
boolean changed = !ObjectUtils.nullSafeEquals(this.lastAccessedTime, lastAccessedTime); triggerDelta(!ObjectUtils.nullSafeEquals(this.lastAccessedTime, lastAccessedTime));
markDirty(changed);
triggerDelta(changed);
this.lastAccessedTime = lastAccessedTime; this.lastAccessedTime = lastAccessedTime;
} }
@@ -862,18 +902,17 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
return this.lastAccessedTime; 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); this.maxInactiveInterval = maxInactiveInterval;
triggerDelta(changed);
this.maxInactiveInterval = maxInactiveIntervalInSeconds;
} }
public synchronized Duration getMaxInactiveInterval() { 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) { public synchronized void setPrincipalName(String principalName) {
@@ -950,6 +989,14 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
super(lock); super(lock);
} }
protected Map<String, Object> getSessionAttributeDeltas() {
synchronized (getLock()) {
return this.sessionAttributeDeltas;
}
}
@Override
public Object setAttribute(String attributeName, Object attributeValue) { public Object setAttribute(String attributeName, Object attributeValue) {
synchronized (getLock()) { synchronized (getLock()) {
@@ -959,7 +1006,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
Object previousAttributeValue = super.setAttribute(attributeName, attributeValue); Object previousAttributeValue = super.setAttribute(attributeName, attributeValue);
if (!attributeValue.equals(previousAttributeValue)) { if (!attributeValue.equals(previousAttributeValue)) {
this.sessionAttributeDeltas.put(attributeName, attributeValue); getSessionAttributeDeltas().put(attributeName, attributeValue);
} }
return previousAttributeValue; return previousAttributeValue;
@@ -977,7 +1024,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
return Optional.ofNullable(super.removeAttribute(attributeName)) return Optional.ofNullable(super.removeAttribute(attributeName))
.map(previousAttributeValue -> { .map(previousAttributeValue -> {
this.sessionAttributeDeltas.put(attributeName, null); getSessionAttributeDeltas().put(attributeName, null);
return previousAttributeValue; return previousAttributeValue;
}) })
.orElse(null); .orElse(null);
@@ -988,14 +1035,14 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
synchronized (getLock()) { 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()); out.writeUTF(entry.getKey());
writeObject(entry.getValue(), out); writeObject(entry.getValue(), out);
} }
clearDelta();
} }
} }
@@ -1007,7 +1054,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
public boolean hasDelta() { public boolean hasDelta() {
synchronized (getLock()) { 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)); deltas.put(in.readUTF(), readObject(in));
} }
Map<String, Object> sessionAttributeDeltas = getSessionAttributeDeltas();
deltas.forEach((key, value) -> { deltas.forEach((key, value) -> {
setAttribute(key, value); setAttribute(key, value);
this.sessionAttributeDeltas.remove(key); sessionAttributeDeltas.remove(key);
}); });
} }
catch (ClassNotFoundException cause) { catch (ClassNotFoundException cause) {
@@ -1040,16 +1089,17 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
} }
@Override @Override
public void clearDelta() { protected void commit() {
synchronized (getLock()) { 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 * both the {@link DataSerializable} and {@link Delta} Pivotal GemFire interfaces for efficient
* storage and distribution (replication) in GemFire. Additionally, GemFireSessionAttributes * storage and distribution (replication) in GemFire. Additionally, GemFireSessionAttributes
* extends {@link AbstractMap} providing {@link Map}-like behavior since attributes of a Session * 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") @SuppressWarnings("serial")
public static class GemFireSessionAttributes extends AbstractMap<String, Object> { 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() { public static GemFireSessionAttributes create() {
return new GemFireSessionAttributes(); return new GemFireSessionAttributes();
} }
@@ -1085,7 +1121,38 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
return new GemFireSessionAttributes(lock); 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; return this.lock;
} }
@@ -1102,7 +1169,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
Object previousAttributeValue = this.sessionAttributes.put(attributeName, attributeValue); Object previousAttributeValue = this.sessionAttributes.put(attributeName, attributeValue);
this.dirty |= !attributeValue.equals(previousAttributeValue); this.delta |= !attributeValue.equals(previousAttributeValue);
return previousAttributeValue; return previousAttributeValue;
} }
@@ -1111,7 +1178,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
synchronized (getLock()) { synchronized (getLock()) {
this.dirty |= this.sessionAttributes.containsKey(attributeName); this.delta |= this.sessionAttributes.containsKey(attributeName);
return this.sessionAttributes.remove(attributeName); return this.sessionAttributes.remove(attributeName);
} }
@@ -1128,7 +1195,7 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
public Set<String> getAttributeNames() { public Set<String> getAttributeNames() {
synchronized (getLock()) { 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") @SuppressWarnings("all")
public Set<Entry<String, Object>> entrySet() { public Set<Entry<String, Object>> entrySet() {
return new AbstractSet<Entry<String, Object>>() { synchronized (getLock()) {
@Override return new AbstractSet<Entry<String, Object>>() {
public Iterator<Entry<String, Object>> iterator() {
return Collections.unmodifiableMap(GemFireSessionAttributes.this.sessionAttributes)
.entrySet().iterator();
}
@Override @Override
public int size() { public Iterator<Entry<String, Object>> iterator() {
return GemFireSessionAttributes.this.sessionAttributes.size(); 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() { synchronized (getLock()) {
this.dirty = false; this.delta = false;
}
} }
public void from(Session session) { public void from(Session session) {
@@ -1181,16 +1252,18 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
} }
public boolean hasDelta() { public boolean hasDelta() {
return false;
}
public boolean isDirty() { synchronized (getLock()) {
return this.dirty; return this.delta;
} }
}
@Override @Override
public String toString() { 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 { public class GemFireOperationsSessionRepository extends AbstractGemFireOperationsSessionRepository {
// Pivotal GemFire OQL query used to lookup Sessions by arbitrary attributes. // 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"; "SELECT s FROM %1$s s WHERE s.attributes['%2$s'] = $1";
// Pivotal GemFire OQL query used to look up Sessions by principal name. // Pivotal GemFire OQL query used to look up Sessions by principal name.
@@ -63,6 +63,44 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
super(template); 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 * Looks up all available Sessions with the particular attribute indexed by name
* having the given value. * having the given value.
@@ -99,47 +137,9 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
*/ */
protected String prepareQuery(String indexName) { 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_PRINCIPAL_NAME_QUERY, getFullyQualifiedRegionName())
: String.format(FIND_SESSIONS_BY_INDEX_NAME_INDEX_VALUE_QUERY, getFullyQualifiedRegionName(), indexName)); : String.format(FIND_SESSIONS_BY_INDEX_NAME_AND_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;
} }
/** /**
@@ -161,41 +161,16 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
} }
private boolean isDirty(@NonNull Session session) { 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 // Commit Session
? GemFireSession.copyCommitted(session) commit(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;
} }
/** /**

View File

@@ -37,10 +37,12 @@ import org.springframework.session.data.gemfire.serialization.data.AbstractDataS
* framework. * framework.
* *
* @author John Blum * @author John Blum
* @see java.io.DataInput
* @see java.io.DataOutput
* @see org.apache.geode.DataSerializer * @see org.apache.geode.DataSerializer
* @see org.springframework.session.Session * @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.DeltaCapableGemFireSessionAttributes
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes
* @see org.springframework.session.data.gemfire.serialization.SessionSerializer * @see org.springframework.session.data.gemfire.serialization.SessionSerializer
* @see org.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer * @see org.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer
* @since 2.0.0 * @since 2.0.0
@@ -85,7 +87,7 @@ public class DataSerializableSessionAttributesSerializer
@Override @Override
public void serialize(GemFireSessionAttributes sessionAttributes, DataOutput out) { public void serialize(GemFireSessionAttributes sessionAttributes, DataOutput out) {
synchronized (sessionAttributes) { synchronized (sessionAttributes.getLock()) {
Set<String> attributeNames = nullSafeSet(sessionAttributes.getAttributeNames()); Set<String> attributeNames = nullSafeSet(sessionAttributes.getAttributeNames());
@@ -95,8 +97,6 @@ public class DataSerializableSessionAttributesSerializer
safeWrite(out, output -> output.writeUTF(attributeName)); safeWrite(out, output -> output.writeUTF(attributeName));
safeWrite(out, output -> serializeObject(sessionAttributes.getAttribute(attributeName), output)); 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.setAttribute(safeRead(in, DataInput::readUTF), safeRead(in, this::deserializeObject));
} }
sessionAttributes.clearDelta();
return sessionAttributes; return sessionAttributes;
} }
} }

View File

@@ -45,10 +45,13 @@ import org.springframework.util.StringUtils;
* @see java.io.DataOutput * @see java.io.DataOutput
* @see org.apache.geode.DataSerializer * @see org.apache.geode.DataSerializer
* @see org.springframework.session.Session * @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.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.SessionSerializer
* @see org.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer * @see org.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer
* @see org.springframework.session.data.gemfire.support.AbstractSession
* @since 2.0.0 * @since 2.0.0
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
@@ -101,18 +104,15 @@ public class DataSerializableSessionSerializer extends AbstractDataSerializableS
String principalName = session.getPrincipalName(); 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 -> output.writeUTF(principalName));
} }
safeWrite(out, output -> serializeObject(session.getAttributes(), output)); 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().from(this.<GemFireSessionAttributes>safeRead(in, this::deserializeObject));
session.getAttributes().clearDelta();
session.clearDelta();
return session; return session;
} }

View File

@@ -120,7 +120,6 @@ public class GemFireOperationsSessionRepositoryTests {
this.sessionRepository.setApplicationEventPublisher(this.mockApplicationEventPublisher); this.sessionRepository.setApplicationEventPublisher(this.mockApplicationEventPublisher);
this.sessionRepository.setMaxInactiveIntervalInSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS); this.sessionRepository.setMaxInactiveIntervalInSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS);
this.sessionRepository.setUseDataSerialization(false); this.sessionRepository.setUseDataSerialization(false);
this.sessionRepository.setUseEagerCommit(false);
this.sessionRepository.afterPropertiesSet(); this.sessionRepository.afterPropertiesSet();
assertThat(this.sessionRepository.getApplicationEventPublisher()).isSameAs(this.mockApplicationEventPublisher); 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.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
assertThat(this.sessionRepository.getTemplate()).isSameAs(this.mockTemplate); assertThat(this.sessionRepository.getTemplate()).isSameAs(this.mockTemplate);
assertThat(GemFireOperationsSessionRepository.isUsingDataSerialization()).isFalse(); assertThat(GemFireOperationsSessionRepository.isUsingDataSerialization()).isFalse();
assertThat(this.sessionRepository.isUsingEagerCommit()).isFalse();
} }
private Session mockSession() { private Session mockSession() {
@@ -150,6 +148,15 @@ public class GemFireOperationsSessionRepositoryTests {
return mockSession; return mockSession;
} }
private GemFireSession newNonDirtyGemFireSession() {
GemFireSession session = GemFireSession.create();
session.commit();;
return session;
}
@After @After
public void tearDown() { public void tearDown() {
@@ -158,9 +165,111 @@ public class GemFireOperationsSessionRepositoryTests {
verify(this.mockTemplate, times(1)).getRegion(); 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 @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void findByIndexNameAndIndexValueFindsMatchingSession() { public void findByIndexNameAndIndexValueReturnsMatchingSession() {
Session mockSession = mock(Session.class, "MockSession"); Session mockSession = mock(Session.class, "MockSession");
@@ -174,7 +283,7 @@ public class GemFireOperationsSessionRepositoryTests {
String indexValue = "rwinch"; String indexValue = "rwinch";
String expectedQql = 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); this.sessionRepository.getFullyQualifiedRegionName(), indexName);
given(this.mockTemplate.find(eq(expectedQql), eq(indexValue))).willReturn(mockSelectResults); given(this.mockTemplate.find(eq(expectedQql), eq(indexValue))).willReturn(mockSelectResults);
@@ -193,7 +302,7 @@ public class GemFireOperationsSessionRepositoryTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void findByPrincipalNameFindsMatchingSessions() throws Exception { public void findByPrincipalNameReturnsMatchingSessions() throws Exception {
Session mockSessionOne = mock(Session.class, "MockSessionOne"); Session mockSessionOne = mock(Session.class, "MockSessionOne");
Session mockSessionTwo = mock(Session.class, "MockSessionTwo"); Session mockSessionTwo = mock(Session.class, "MockSessionTwo");
@@ -265,7 +374,7 @@ public class GemFireOperationsSessionRepositoryTests {
String actualOql = this.sessionRepository.prepareQuery(attributeName); String actualOql = this.sessionRepository.prepareQuery(attributeName);
String expectedOql = 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); this.sessionRepository.getFullyQualifiedRegionName(), attributeName);
assertThat(actualOql).isEqualTo(expectedOql); assertThat(actualOql).isEqualTo(expectedOql);
@@ -274,8 +383,7 @@ public class GemFireOperationsSessionRepositoryTests {
@Test @Test
public void prepareQueryReturnsPrincipalNameOql() { public void prepareQueryReturnsPrincipalNameOql() {
String actualQql = String actualQql = this.sessionRepository.prepareQuery(PRINCIPAL_NAME_INDEX_NAME);
this.sessionRepository.prepareQuery(PRINCIPAL_NAME_INDEX_NAME);
String expectedOql = String expectedOql =
String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
@@ -284,104 +392,6 @@ public class GemFireOperationsSessionRepositoryTests {
assertThat(actualQql).isEqualTo(expectedOql); 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 @Test
public void saveIsNullSafe() { public void saveIsNullSafe() {
@@ -435,20 +445,18 @@ public class GemFireOperationsSessionRepositoryTests {
} }
@Test @Test
public void saveStoresAndThenCommitsGemFireSession() { public void saveStoresAndCommitsGemFireSession() {
assertThat(this.sessionRepository.isUsingEagerCommit()).isFalse();
GemFireSession<?> session = spy(GemFireSession.create()); GemFireSession<?> session = spy(GemFireSession.create());
assertThat(session).isNotNull(); assertThat(session).isNotNull();
assertThat(session.isDirty()).isTrue(); assertThat(session.hasDelta()).isTrue();
this.sessionRepository.save(session); this.sessionRepository.save(session);
InOrder orderVerifier = inOrder(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)).getId();
orderVerifier.verify(session, times(1)).commit(); orderVerifier.verify(session, times(1)).commit();
@@ -460,115 +468,19 @@ public class GemFireOperationsSessionRepositoryTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void saveWillNotStoreNonDirtyGemFireSessions() { public void saveWillNotStoreNonDirtyGemFireSessions() {
GemFireSession session = spy(GemFireSession.from(mockSession())); GemFireSession session = spy(newNonDirtyGemFireSession());
assertThat(session).isNotNull(); assertThat(session).isNotNull();
assertThat(session.hasDelta()).isFalse(); assertThat(session.hasDelta()).isFalse();
assertThat(session.isDirty()).isFalse();
this.sessionRepository.save(session); this.sessionRepository.save(session);
verify(session, times(2)).isDirty(); verify(session, times(2)).hasDelta();
verify(session, never()).getId(); verify(session, never()).getId();
verify(session, never()).commit(); verify(session, never()).commit();
verify(this.mockTemplate, never()).put(any(), any(GemFireSession.class)); 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 @Test
public void deleteRemovesExistingSessionAndHandlesDelete() { public void deleteRemovesExistingSessionAndHandlesDelete() {

View File

@@ -75,6 +75,9 @@ public class DataSerializableSessionAttributesSerializerTests {
GemFireSessionAttributes sessionAttributes = GemFireSessionAttributes.create(); GemFireSessionAttributes sessionAttributes = GemFireSessionAttributes.create();
assertThat(sessionAttributes).isNotNull();
assertThat(sessionAttributes.hasDelta()).isFalse();
sessionAttributes.setAttribute("attrOne", "testOne"); sessionAttributes.setAttribute("attrOne", "testOne");
sessionAttributes.setAttribute("attrTwo", "testTwo"); sessionAttributes.setAttribute("attrTwo", "testTwo");
@@ -88,8 +91,12 @@ public class DataSerializableSessionAttributesSerializerTests {
}).when(sessionAttributesSerializer).serializeObject(any(), any(DataOutput.class)); }).when(sessionAttributesSerializer).serializeObject(any(), any(DataOutput.class));
assertThat(sessionAttributes.hasDelta()).isTrue();
sessionAttributesSerializer.serialize(sessionAttributes, mockDataOutput); sessionAttributesSerializer.serialize(sessionAttributes, mockDataOutput);
assertThat(sessionAttributes.hasDelta()).isTrue();
verify(mockDataOutput, times(1)).writeInt(eq(2)); verify(mockDataOutput, times(1)).writeInt(eq(2));
verify(mockDataOutput, times(1)).writeUTF(eq("attrOne")); verify(mockDataOutput, times(1)).writeUTF(eq("attrOne"));
verify(mockDataOutput, times(1)).writeUTF(eq("testOne")); verify(mockDataOutput, times(1)).writeUTF(eq("testOne"));
@@ -113,10 +120,11 @@ public class DataSerializableSessionAttributesSerializerTests {
GemFireSessionAttributes sessionAttributes = sessionAttributesSerializer.deserialize(mockDataInput); GemFireSessionAttributes sessionAttributes = sessionAttributesSerializer.deserialize(mockDataInput);
assertThat(sessionAttributes).isNotNull(); assertThat(sessionAttributes).isNotNull();
assertThat(sessionAttributes.getAttributeNames()).hasSize(2); assertThat(sessionAttributes).hasSize(2);
assertThat(sessionAttributes.getAttributeNames()).containsAll(asSet("attrOne", "attrTwo")); assertThat(sessionAttributes.getAttributeNames()).containsAll(asSet("attrOne", "attrTwo"));
assertThat(sessionAttributes.<String>getAttribute("attrOne")).isEqualTo("testOne"); assertThat(sessionAttributes.<String>getAttribute("attrOne")).isEqualTo("testOne");
assertThat(sessionAttributes.<String>getAttribute("attrTwo")).isEqualTo("testTwo"); assertThat(sessionAttributes.<String>getAttribute("attrTwo")).isEqualTo("testTwo");
assertThat(sessionAttributes.hasDelta()).isTrue();
verify(mockDataInput, times(1)).readInt(); verify(mockDataInput, times(1)).readInt();
verify(mockDataInput, times(2)).readUTF(); verify(mockDataInput, times(2)).readUTF();

View File

@@ -75,6 +75,7 @@ public class DataSerializableSessionSerializerTests {
@Test @Test
public void supportedClassContainsGemFireSessionAndSubTypes() { public void supportedClassContainsGemFireSessionAndSubTypes() {
assertThat(this.sessionSerializer.getSupportedClasses()).contains(GemFireSession.class); assertThat(this.sessionSerializer.getSupportedClasses()).contains(GemFireSession.class);
assertThat(this.sessionSerializer.getSupportedClasses()).contains(DeltaCapableGemFireSession.class); assertThat(this.sessionSerializer.getSupportedClasses()).contains(DeltaCapableGemFireSession.class);
} }
@@ -100,7 +101,7 @@ public class DataSerializableSessionSerializerTests {
this.sessionSerializer.serialize(session, mockDataOutput); 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)).writeUTF(eq(session.getId()));
verify(mockDataOutput, times(1)).writeLong(eq(session.getCreationTime().toEpochMilli())); 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.getLastAccessedTime()).isEqualTo(Instant.ofEpochMilli(expectedLastAccessedTime));
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(expectedMaxInactiveIntervalInSeconds)); assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(expectedMaxInactiveIntervalInSeconds));
assertThat(session.getPrincipalName()).isEqualTo(expectedPrincipalName); assertThat(session.getPrincipalName()).isEqualTo(expectedPrincipalName);
assertThat(session.hasDelta()).isFalse(); assertThat(session.hasDelta()).isTrue();
assertThat(session.getAttributeNames()).hasSize(3); assertThat(session.getAttributeNames()).hasSize(3);
assertThat(session.getAttributeNames()).containsAll(expectedAttributeNames); assertThat(session.getAttributeNames()).containsAll(expectedAttributeNames);
assertThat(session.<String>getAttribute("attrOne")).isEqualTo("testOne"); assertThat(session.<String>getAttribute("attrOne")).isEqualTo("testOne");