diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepository.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepository.java index 53eb809..621b26b 100644 --- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepository.java +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepository.java @@ -166,20 +166,36 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation /*private*/ void doSave(@NonNull Session session) { - GemFireSession sessionToSave = isUsingEagerCommit() + boolean usingEagerCommits = isUsingEagerCommit(); + + GemFireSession sessionToSave = usingEagerCommits ? GemFireSession.copyCommitted(session) : GemFireSession.from(session); - // Save Session As GemFireSession - getTemplate().put(session.getId(), sessionToSave); + try { + // Save Session As GemFireSession + getTemplate().put(session.getId(), sessionToSave); - if (isCommittable(session)) { - ((GemFireSession) session).commit(); + if (isCommittable(usingEagerCommits, session)) { + ((GemFireSession) session).commit(); + } + } + catch (RuntimeException cause) { + + if (isEagerlyCommittable(usingEagerCommits, session)) { + ((GemFireSession) session).markDirty(); + } + + throw cause; } } - private boolean isCommittable(@Nullable Session session) { - return !isUsingEagerCommit() && session instanceof GemFireSession; + 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; } /** diff --git a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepositoryTests.java b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepositoryTests.java index 624b9dd..1ffe49a 100644 --- a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepositoryTests.java +++ b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepositoryTests.java @@ -22,8 +22,6 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.same; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -130,38 +128,40 @@ public class AbstractGemFireOperationsSessionRepositoryTests { GemfireTemplate gemfireTemplate = new GemfireTemplate(mock(Region.class)); this.sessionRepository = spy(new TestGemFireOperationsSessionRepository(gemfireTemplate)); + this.sessionRepository.setUseDataSerialization(false); + this.sessionRepository.setUseEagerCommit(false); doReturn(this.mockLog).when(this.sessionRepository).getLogger(); } @SuppressWarnings("unchecked") - protected EntryEvent mockEntryEvent(Operation operation, K key, V oldValue, V newValue) { + private EntryEvent mockEntryEvent(Operation operation, K key, V oldValue, V newValue) { EntryEvent mockEntryEvent = mock(EntryEvent.class); - given(mockEntryEvent.getOperation()).willReturn(operation); - given(mockEntryEvent.getKey()).willReturn(key); - given(mockEntryEvent.getOldValue()).willReturn(oldValue); - given(mockEntryEvent.getNewValue()).willReturn(newValue); + when(mockEntryEvent.getOperation()).thenReturn(operation); + when(mockEntryEvent.getKey()).thenReturn(key); + when(mockEntryEvent.getOldValue()).thenReturn(oldValue); + when(mockEntryEvent.getNewValue()).thenReturn(newValue); return mockEntryEvent; } @SuppressWarnings("unchecked") - protected Region mockRegion(String name, DataPolicy dataPolicy) { + private Region mockRegion(String name, DataPolicy dataPolicy) { Region mockRegion = mock(Region.class, name); RegionAttributes mockRegionAttributes = mockRegionAttributes(name); - given(mockRegion.getAttributes()).willReturn(mockRegionAttributes); - given(mockRegionAttributes.getDataPolicy()).willReturn(dataPolicy); + when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes); + when(mockRegionAttributes.getDataPolicy()).thenReturn(dataPolicy); return mockRegion; } @SuppressWarnings("unchecked") - protected RegionAttributes mockRegionAttributes(String name) { + private RegionAttributes mockRegionAttributes(String name) { return mock(RegionAttributes.class, name); } @@ -184,27 +184,27 @@ public class AbstractGemFireOperationsSessionRepositoryTests { return mockSession; } - protected Session mockSession(String sessionId, long creationAndLastAccessedTime, + private Session mockSession(String sessionId, long creationAndLastAccessedTime, long maxInactiveIntervalInSeconds) { return mockSession(sessionId, creationAndLastAccessedTime, creationAndLastAccessedTime, maxInactiveIntervalInSeconds); } - protected Session mockSession(String sessionId, long creationTime, long lastAccessedTime, + private Session mockSession(String sessionId, long creationTime, long lastAccessedTime, long maxInactiveIntervalInSeconds) { Session mockSession = mock(Session.class, sessionId); - given(mockSession.getId()).willReturn(sessionId); - given(mockSession.getCreationTime()).willReturn(Instant.ofEpochMilli(creationTime)); - given(mockSession.getLastAccessedTime()).willReturn(Instant.ofEpochMilli(lastAccessedTime)); - given(mockSession.getMaxInactiveInterval()).willReturn(Duration.ofSeconds(maxInactiveIntervalInSeconds)); + when(mockSession.getId()).thenReturn(sessionId); + when(mockSession.getCreationTime()).thenReturn(Instant.ofEpochMilli(creationTime)); + when(mockSession.getLastAccessedTime()).thenReturn(Instant.ofEpochMilli(lastAccessedTime)); + when(mockSession.getMaxInactiveInterval()).thenReturn(Duration.ofSeconds(maxInactiveIntervalInSeconds)); return mockSession; } - protected AbstractGemFireOperationsSessionRepository withRegion( + private AbstractGemFireOperationsSessionRepository withRegion( AbstractGemFireOperationsSessionRepository sessionRepository, Region region) { ((GemfireTemplate) sessionRepository.getTemplate()).setRegion(region); @@ -306,6 +306,55 @@ public class AbstractGemFireOperationsSessionRepositoryTests { assertThat(this.sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MAX_VALUE); } + @Test + public void setAndGetMaxInactiveInterval() { + + assertThat(this.sessionRepository.getMaxInactiveInterval()) + .isEqualTo(Duration.ofSeconds(GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS)); + + Duration tenMinutes = Duration.ofMinutes(10); + + this.sessionRepository.setMaxInactiveInterval(tenMinutes); + + assertThat(this.sessionRepository.getMaxInactiveInterval()).isEqualTo(tenMinutes); + + this.sessionRepository.setMaxInactiveIntervalInSeconds(300); + + assertThat(this.sessionRepository.getMaxInactiveInterval()).isEqualTo(Duration.ofMinutes(5)); + + this.sessionRepository.setMaxInactiveInterval(null); + + assertThat(this.sessionRepository.getMaxInactiveInterval()).isNull(); + } + + @Test + public void setAndIsUsingDataSerialization() { + + assertThat(GemFireOperationsSessionRepository.isUsingDataSerialization()).isFalse(); + + this.sessionRepository.setUseDataSerialization(true); + + assertThat(GemFireOperationsSessionRepository.isUsingDataSerialization()).isTrue(); + + this.sessionRepository.setUseDataSerialization(false); + + assertThat(GemFireOperationsSessionRepository.isUsingDataSerialization()).isFalse(); + } + + @Test + public void setAndIsUsingEagerCommit() { + + assertThat(this.sessionRepository.isUsingEagerCommit()).isFalse(); + + this.sessionRepository.setUseEagerCommit(true); + + assertThat(this.sessionRepository.isUsingEagerCommit()).isTrue(); + + this.sessionRepository.setUseEagerCommit(false); + + assertThat(this.sessionRepository.isUsingEagerCommit()).isFalse(); + } + @Test public void isCreateWithCreateOperationReturnsTrue() { @@ -971,7 +1020,7 @@ public class AbstractGemFireOperationsSessionRepositoryTests { ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); - willAnswer(new Answer() { + doAnswer(new Answer() { int index = 0; @@ -993,7 +1042,7 @@ public class AbstractGemFireOperationsSessionRepositoryTests { return null; } - }).given(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); + }).when(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); EntryEvent mockCreateEvent = this.mockEntryEvent(Operation.CREATE, sessionId, null, this.mockSession); @@ -1110,7 +1159,7 @@ public class AbstractGemFireOperationsSessionRepositoryTests { } @Test - public void constructGemFireSessionWithDefaultInitialization() { + public void constructDefaultGemFireSession() { Instant beforeOrAtCreationTime = Instant.now(); @@ -1139,26 +1188,25 @@ public class AbstractGemFireOperationsSessionRepositoryTests { assertThat(session.getAttributeNames()).isEmpty(); } + @Test(expected = IllegalArgumentException.class) + public void constructGemFireSessionWithEmptyId() { + testConstructGemFireSessionWithInvalidId(""); + } + @Test(expected = IllegalArgumentException.class) public void constructGemFireSessionWithNullId() { - - try { - new GemFireSession((String) null); - } - catch (IllegalArgumentException expected) { - - assertThat(expected).hasMessage("ID is required"); - assertThat(expected).hasNoCause(); - - throw expected; - } + testConstructGemFireSessionWithInvalidId(null); } @Test(expected = IllegalArgumentException.class) public void constructGemFireSessionWithUnspecifiedId() { + testConstructGemFireSessionWithInvalidId(" "); + } + + private void testConstructGemFireSessionWithInvalidId(String id) { try { - new GemFireSession(" "); + new GemFireSession(id); } catch (IllegalArgumentException expected) { @@ -1182,9 +1230,9 @@ public class AbstractGemFireOperationsSessionRepositoryTests { Set expectedAttributedNames = asSet("attrOne", "attrTwo"); - given(mockSession.getAttributeNames()).willReturn(expectedAttributedNames); - given(mockSession.getAttribute(eq("attrOne"))).willReturn("testOne"); - given(mockSession.getAttribute(eq("attrTwo"))).willReturn("testTwo"); + when(mockSession.getAttributeNames()).thenReturn(expectedAttributedNames); + when(mockSession.getAttribute(eq("attrOne"))).thenReturn("testOne"); + when(mockSession.getAttribute(eq("attrTwo"))).thenReturn("testTwo"); GemFireSession gemfireSession = new GemFireSession(mockSession); @@ -1254,6 +1302,97 @@ public class AbstractGemFireOperationsSessionRepositoryTests { assertThat(session.getAttributeNames()).isEmpty(); } + @Test(expected = IllegalArgumentException.class) + public void copyNullSessionThrowsIllegalArgumentException() { + + try { + GemFireSession.copy(null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("The Session to copy must not be null"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void copySessionWhenNotUsingDataSerialization() { + + assertThat(AbstractGemFireOperationsSessionRepository.isUsingDataSerialization()).isFalse(); + + Session mockSession = mockSession(); + + when(mockSession.getAttributeNames()).thenReturn(Collections.singleton("attributeOne")); + when(mockSession.getAttribute(eq("attributeOne"))).thenReturn("test"); + + GemFireSession sessionCopy = GemFireSession.copy(mockSession); + + assertThat(sessionCopy).isNotNull(); + assertThat(sessionCopy).isNotInstanceOf(DeltaCapableGemFireSession.class); + assertThat(sessionCopy.getId()).isEqualTo(mockSession.getId()); + assertThat(sessionCopy.getAttributeNames()).containsExactly("attributeOne"); + assertThat(sessionCopy.getAttribute("attributeOne")).isEqualTo("test"); + assertThat(sessionCopy.getCreationTime()).isEqualTo(mockSession.getCreationTime()); + assertThat(sessionCopy.getLastAccessedTime()).isEqualTo(mockSession.getCreationTime()); + assertThat(sessionCopy.getMaxInactiveInterval()).isEqualTo(mockSession.getMaxInactiveInterval()); + } + + @Test + public void copySessionWhenUsingDataSerialization() { + + this.sessionRepository.setUseDataSerialization(true); + + assertThat(AbstractGemFireOperationsSessionRepository.isUsingDataSerialization()).isTrue(); + + Session mockSession = mockSession(); + + when(mockSession.getAttributeNames()).thenReturn(Collections.singleton("attributeOne")); + when(mockSession.getAttribute(eq("attributeOne"))).thenReturn("test"); + + GemFireSession sessionCopy = GemFireSession.copy(mockSession); + + assertThat(sessionCopy).isInstanceOf(DeltaCapableGemFireSession.class); + assertThat(sessionCopy.getId()).isEqualTo(mockSession.getId()); + assertThat(sessionCopy.getAttributeNames()).containsExactly("attributeOne"); + assertThat(sessionCopy.getAttribute("attributeOne")).isEqualTo("test"); + assertThat(sessionCopy.getCreationTime()).isEqualTo(mockSession.getCreationTime()); + assertThat(sessionCopy.getLastAccessedTime()).isEqualTo(mockSession.getCreationTime()); + assertThat(sessionCopy.getMaxInactiveInterval()).isEqualTo(mockSession.getMaxInactiveInterval()); + } + + @Test + public void copyCommittedCopiesAndThenCommitsGemFireSession() { + + GemFireSession session = spy(GemFireSession.create()); + GemFireSession sessionCopy = GemFireSession.copyCommitted(session); + + assertThat(sessionCopy).isNotNull(); + assertThat(sessionCopy).isNotSameAs(session); + assertThat(sessionCopy).isEqualTo(session); + + verify(session, times(1)).getAttributeNames(); + verify(session, times(1)).commit(); + } + + @Test + public void copyCommittedOnlyCopiesStandardSession() { + + Session mockSession = mockSession(); + + GemFireSession sessionCopy = GemFireSession.copyCommitted(mockSession); + + assertThat(sessionCopy).isNotNull(); + assertThat(sessionCopy).isNotSameAs(mockSession); + assertThat(sessionCopy.getId()).isEqualTo(mockSession.getId()); + } + + @Test + public void copyCommittedIsAtomic() throws Throwable { + TestFramework.runOnce(new ThreadSafeCopyCommittedSessionTestCase()); + } + @Test public void fromExistingSession() { @@ -1265,7 +1404,7 @@ public class AbstractGemFireOperationsSessionRepositoryTests { Session mockSession = mockSession("4", expectedCreationTime.toEpochMilli(), expectedLastAccessedTime.toEpochMilli(), MAX_INACTIVE_INTERVAL_IN_SECONDS); - given(mockSession.getAttributeNames()).willReturn(Collections.emptySet()); + when(mockSession.getAttributeNames()).thenReturn(Collections.emptySet()); GemFireSession gemfireSession = GemFireSession.from(mockSession); @@ -1505,9 +1644,9 @@ public class AbstractGemFireOperationsSessionRepositoryTests { DataInput mockDataInput = mock(DataInput.class); - given(mockDataInput.readUTF()).willReturn("1"); - given(mockDataInput.readLong()).willReturn(1L).willReturn(600L); - given(mockDataInput.readInt()).willReturn(0); + when(mockDataInput.readUTF()).thenReturn("1"); + when(mockDataInput.readLong()).thenReturn(1L).thenReturn(600L); + when(mockDataInput.readInt()).thenReturn(0); @SuppressWarnings("serial") DeltaCapableGemFireSession session = new DeltaCapableGemFireSession(); @@ -2056,8 +2195,8 @@ public class AbstractGemFireOperationsSessionRepositoryTests { DataInput mockDataInput = mock(DataInput.class); - given(mockDataInput.readInt()).willReturn(2); - given(mockDataInput.readUTF()).willReturn("attrOne").willReturn("attrTwo"); + when(mockDataInput.readInt()).thenReturn(2); + when(mockDataInput.readUTF()).thenReturn("attrOne").thenReturn("attrTwo"); @SuppressWarnings("serial") DeltaCapableGemFireSessionAttributes sessionAttributes = new DeltaCapableGemFireSessionAttributes() { @@ -2093,8 +2232,8 @@ public class AbstractGemFireOperationsSessionRepositoryTests { verify(mockDataInput, times(2)).readUTF(); reset(mockDataInput); - given(mockDataInput.readInt()).willReturn(1); - given(mockDataInput.readUTF()).willReturn("attrTwo"); + when(mockDataInput.readInt()).thenReturn(1); + when(mockDataInput.readUTF()).thenReturn("attrTwo"); sessionAttributes.setAttribute("attrOne", "one"); sessionAttributes.setAttribute("attrTwo", "two"); @@ -2169,11 +2308,68 @@ public class AbstractGemFireOperationsSessionRepositoryTests { @Test public void sessionWithAttributesAreThreadSafe() throws Throwable { - TestFramework.runOnce(new ThreadSafeSessionTest()); + TestFramework.runOnce(new ThreadSafeSessionTestCase()); } @SuppressWarnings("unused") - protected static final class ThreadSafeSessionTest extends MultithreadedTestCase { + protected static final class ThreadSafeCopyCommittedSessionTestCase extends MultithreadedTestCase { + + private GemFireSession session; + + private Instant originalLastAccessedTime; + + @Override + public void initialize() { + + super.initialize(); + + this.session = spy(GemFireSession.create()); + this.originalLastAccessedTime = this.session.getLastAccessedTime(); + + doAnswer(invocation -> { + waitForTick(2); + return "123"; + }).when(this.session).getId(); + } + + public void thread1() { + + Thread.currentThread().setName("User Session One"); + + assertTick(0); + assertThat(this.session.isDirty()).isTrue(); + + GemFireSession sessionCopy = GemFireSession.copyCommitted(this.session); + + assertTick(2); + assertThat(sessionCopy).isNotNull(); + assertThat(sessionCopy).isNotSameAs(this.session); + assertThat(sessionCopy.getId()).isEqualTo("123"); + assertThat(sessionCopy.getLastAccessedTime()).isEqualTo(this.originalLastAccessedTime); + + verify(this.session, times(1)).commit(); + } + + public void thread2() { + + Thread.currentThread().setName("User Session Two"); + + waitForTick(1); + assertTick(1); + + this.session.setLastAccessedTime(this.originalLastAccessedTime.plusSeconds(5L)); + + waitForTick(3); + assertTick(3); + + assertThat(this.session.getId()).isEqualTo("123"); + assertThat(this.session.getLastAccessedTime()).isEqualTo(this.originalLastAccessedTime.plusSeconds(5L)); + assertThat(this.session.isDirty()).isTrue(); + } + } + + @SuppressWarnings("unused") + protected static final class ThreadSafeSessionTestCase extends MultithreadedTestCase { private GemFireSession session; diff --git a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepositoryTests.java b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepositoryTests.java index 85d024c..bebd2e0 100644 --- a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepositoryTests.java +++ b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepositoryTests.java @@ -116,14 +116,19 @@ public class GemFireOperationsSessionRepositoryTests { when(this.mockRegion.getFullPath()).thenReturn(GemFireUtils.toRegionPath("Example")); when(this.mockTemplate.getRegion()).thenReturn(this.mockRegion); - this.sessionRepository = new GemFireOperationsSessionRepository(this.mockTemplate); + this.sessionRepository = spy(new GemFireOperationsSessionRepository(this.mockTemplate)); 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); assertThat(this.sessionRepository.getFullyQualifiedRegionName()).isEqualTo(GemFireUtils.toRegionPath("Example")); 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() { @@ -377,6 +382,14 @@ public class GemFireOperationsSessionRepositoryTests { assertThat(this.sessionRepository.findById("1")).isNull(); } + @Test + public void saveIsNullSafe() { + + this.sessionRepository.save(null); + + verify(this.mockTemplate, never()).put(any(), any()); + } + @Test public void saveStoresSession() { @@ -389,14 +402,14 @@ public class GemFireOperationsSessionRepositoryTests { Session mockSession = mock(Session.class); - given(mockSession.getId()).willReturn(expectedSessionId); - given(mockSession.getCreationTime()).willReturn(expectedCreationTime); - given(mockSession.getLastAccessedTime()).willReturn(expectedLastAccessTime); - given(mockSession.getMaxInactiveInterval()).willReturn(expectedMaxInactiveInterval); - given(mockSession.getAttributeNames()).willReturn(Collections.emptySet()); + when(mockSession.getId()).thenReturn(expectedSessionId); + when(mockSession.getCreationTime()).thenReturn(expectedCreationTime); + when(mockSession.getLastAccessedTime()).thenReturn(expectedLastAccessTime); + when(mockSession.getMaxInactiveInterval()).thenReturn(expectedMaxInactiveInterval); + when(mockSession.getAttributeNames()).thenReturn(Collections.emptySet()); - given(this.mockTemplate.put(eq(expectedSessionId), isA(GemFireSession.class))) - .willAnswer(invocation -> { + when(this.mockTemplate.put(eq(expectedSessionId), isA(GemFireSession.class))) + .thenAnswer(invocation -> { Session session = invocation.getArgument(1); @@ -422,7 +435,9 @@ public class GemFireOperationsSessionRepositoryTests { } @Test - public void saveStoresAndCommitsGemFireSession() { + public void saveStoresAndThenCommitsGemFireSession() { + + assertThat(this.sessionRepository.isUsingEagerCommit()).isFalse(); GemFireSession session = spy(GemFireSession.create()); @@ -438,11 +453,12 @@ public class GemFireOperationsSessionRepositoryTests { orderVerifier.verify(session, times(1)).commit(); verify(this.mockTemplate, times(1)).put(eq(session.getId()), eq(session)); + verify(this.mockTemplate, times(1)).put(eq(session.getId()), same(session)); } @Test @SuppressWarnings("unchecked") - public void saveDoesNotStoreNonDirtyGemFireSessions() { + public void saveWillNotStoreNonDirtyGemFireSessions() { GemFireSession session = spy(GemFireSession.from(mockSession())); @@ -459,11 +475,98 @@ public class GemFireOperationsSessionRepositoryTests { } @Test - public void saveIsNullSafe() { + public void saveEagerlyCommitsAndThenStoresSession() { - this.sessionRepository.save(null); + this.sessionRepository.setUseEagerCommit(true); - verify(this.mockTemplate, never()).put(any(), any()); + 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