{
+
+ /**
+ * The default time period in seconds in which a session will expire.
+ */
+ public static final int DEFAULT_INACTIVE_INTERVAL = 1800;
+
+ /**
+ * the default collection name for storing session.
+ */
+ public static final String DEFAULT_COLLECTION_NAME = "sessions";
+
+ private final ReactiveMongoOperations mongoOperations;
+
+ private AbstractMongoSessionConverter mongoSessionConverter =
+ SessionConverterProvider.getDefaultMongoConverter();
+
+ private Integer maxInactiveIntervalInSeconds = DEFAULT_INACTIVE_INTERVAL;
+ private String collectionName = DEFAULT_COLLECTION_NAME;
+
+ public ReactiveMongoOperationsSessionRepository(ReactiveMongoOperations mongoOperations) {
+ this.mongoOperations = mongoOperations;
+ }
+
+ /**
+ * Creates a new {@link MongoSession} that is capable of being persisted by this
+ * {@link ReactorSessionRepository}.
+ *
+ * This allows optimizations and customizations in how the {@link MongoSession} is
+ * persisted. For example, the implementation returned might keep track of the changes
+ * ensuring that only the delta needs to be persisted on a save.
+ *
+ *
+ * @return a new {@link MongoSession} that is capable of being persisted by this
+ * {@link ReactorSessionRepository}
+ */
+ @Override
+ public Mono createSession() {
+
+ return Mono.defer(() -> {
+ MongoSession session = new MongoSession();
+
+ if (this.maxInactiveIntervalInSeconds != null) {
+ session.setMaxInactiveInterval(Duration.ofSeconds(this.maxInactiveIntervalInSeconds));
+ }
+
+ return Mono.just(session);
+ });
+ }
+
+ /**
+ * Ensures the {@link MongoSession} created by
+ * {@link ReactorSessionRepository#createSession()} is saved.
+ *
+ * Some implementations may choose to save as the {@link MongoSession} is updated by
+ * returning a {@link MongoSession} that immediately persists any changes. In this case,
+ * this method may not actually do anything.
+ *
+ *
+ * @param session the {@link MongoSession} to save
+ */
+ @Override
+ public Mono save(MongoSession session) {
+
+ return this.mongoOperations
+ .save(convertToDBObject(this.mongoSessionConverter, session), this.collectionName)
+ .then();
+ }
+
+ /**
+ * Gets the {@link MongoSession} by the {@link MongoSession#getId()} or null if no
+ * {@link MongoSession} is found.
+ *
+ * @param id the {@link MongoSession#getId()} to lookup
+ * @return the {@link MongoSession} by the {@link MongoSession#getId()} or null if no
+ * {@link MongoSession} is found.
+ */
+ @Override
+ public Mono findById(String id) {
+
+ return findSession(id)
+ .map(document -> convertToSession(this.mongoSessionConverter, document))
+ .filter(mongoSession -> !mongoSession.isExpired())
+ .switchIfEmpty(Mono.defer(() -> delete(id).then(Mono.empty())));
+ }
+
+ /**
+ * Deletes the {@link MongoSession} with the given {@link MongoSession#getId()} or does nothing
+ * if the {@link MongoSession} is not found.
+ *
+ * @param id the {@link MongoSession#getId()} to delete
+ */
+ @Override
+ public Mono delete(String id) {
+ return this.mongoOperations.remove(findSession(id), this.collectionName).then();
+ }
+
+ Mono findSession(String id) {
+ return this.mongoOperations.findById(id, Document.class, this.collectionName);
+ }
+
+ public void setMongoSessionConverter(AbstractMongoSessionConverter mongoSessionConverter) {
+ this.mongoSessionConverter = mongoSessionConverter;
+ }
+
+ public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
+ this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
+ }
+
+ public void setCollectionName(String collectionName) {
+ this.collectionName = collectionName;
+ }
+
+}
diff --git a/src/test/java/org/springframework/session/data/mongo/AuthenticationParserTest.java b/src/test/java/org/springframework/session/data/mongo/AuthenticationParserTest.java
index 1ccd6b1..f4d76a6 100644
--- a/src/test/java/org/springframework/session/data/mongo/AuthenticationParserTest.java
+++ b/src/test/java/org/springframework/session/data/mongo/AuthenticationParserTest.java
@@ -17,8 +17,6 @@ package org.springframework.session.data.mongo;
import static org.assertj.core.api.Assertions.*;
-import java.util.Optional;
-
import org.junit.Test;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -39,7 +37,7 @@ public class AuthenticationParserTest {
context.setAuthentication(new UsernamePasswordAuthenticationToken(principalName, null));
// when
- String extractedName = AuthenticationParser.extractName(Optional.ofNullable(context));
+ String extractedName = AuthenticationParser.extractName(context);
// then
assertThat(extractedName).isEqualTo(principalName);
diff --git a/src/test/java/org/springframework/session/data/mongo/ReactiveMongoOperationsSessionRepositoryTest.java b/src/test/java/org/springframework/session/data/mongo/ReactiveMongoOperationsSessionRepositoryTest.java
new file mode 100644
index 0000000..2b3ca4e
--- /dev/null
+++ b/src/test/java/org/springframework/session/data/mongo/ReactiveMongoOperationsSessionRepositoryTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2014-2017 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.mongo;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.BDDMockito.*;
+import static org.mockito.Mockito.verify;
+
+import java.util.UUID;
+
+import org.bson.Document;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.data.mongodb.core.ReactiveMongoOperations;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBObject;
+import com.mongodb.client.result.DeleteResult;
+
+/**
+ * Tests for {@link ReactiveMongoOperationsSessionRepository}.
+ *
+ * @author Jakub Kubrynski
+ * @author Vedran Pavic
+ * @author Greg Turnquist
+ */
+@RunWith(MockitoJUnitRunner.Silent.class)
+public class ReactiveMongoOperationsSessionRepositoryTest {
+
+ @Mock
+ private AbstractMongoSessionConverter converter;
+
+ @Mock
+ private ReactiveMongoOperations mongoOperations;
+
+ private ReactiveMongoOperationsSessionRepository repository;
+
+ @Before
+ public void setUp() throws Exception {
+
+ this.repository = new ReactiveMongoOperationsSessionRepository(this.mongoOperations);
+ this.repository.setMongoSessionConverter(this.converter);
+ }
+
+ @Test
+ public void shouldCreateSession() throws Exception {
+
+ // when
+ Mono session = this.repository.createSession();
+
+ // then
+ StepVerifier.create(session)
+ .expectNextMatches(mongoSession -> {
+ assertThat(mongoSession.getId()).isNotEmpty();
+ assertThat(mongoSession.getMaxInactiveInterval().getSeconds())
+ .isEqualTo(ReactiveMongoOperationsSessionRepository.DEFAULT_INACTIVE_INTERVAL);
+ return true;
+ });
+ }
+
+ @Test
+ public void shouldCreateSessionWhenMaxInactiveIntervalNotDefined() throws Exception {
+
+ // when
+ this.repository.setMaxInactiveIntervalInSeconds(null);
+ Mono session = this.repository.createSession();
+
+ // then
+ StepVerifier.create(session)
+ .expectNextMatches(mongoSession -> {
+ assertThat(mongoSession.getId()).isNotEmpty();
+ assertThat(mongoSession.getMaxInactiveInterval().getSeconds())
+ .isEqualTo(ReactiveMongoOperationsSessionRepository.DEFAULT_INACTIVE_INTERVAL);
+ return true;
+ });
+ }
+
+ @Test
+ public void shouldSaveSession() throws Exception {
+
+ // given
+ MongoSession session = new MongoSession();
+ BasicDBObject dbSession = new BasicDBObject();
+
+ given(this.converter.convert(session,
+ TypeDescriptor.valueOf(MongoSession.class),
+ TypeDescriptor.valueOf(DBObject.class))).willReturn(dbSession);
+
+ given(this.mongoOperations.save(dbSession, "sessions")).willReturn(Mono.just(dbSession));
+
+ // when
+ StepVerifier.create(this.repository.save(session))
+ .expectNextMatches(aVoid -> {
+ // then
+ verify(this.mongoOperations).save(dbSession, ReactiveMongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME);
+ return true;
+ });
+ }
+
+ @Test
+ public void shouldGetSession() throws Exception {
+
+ // given
+ String sessionId = UUID.randomUUID().toString();
+ Document sessionDocument = new Document();
+
+ given(this.mongoOperations.findById(sessionId, Document.class,
+ ReactiveMongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME)).willReturn(Mono.just(sessionDocument));
+
+ MongoSession session = new MongoSession();
+
+ given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
+ TypeDescriptor.valueOf(MongoSession.class))).willReturn(session);
+
+ // when
+ StepVerifier.create(this.repository.findById(sessionId))
+ .expectNextMatches(retrievedSession -> {
+ // then
+ assertThat(retrievedSession).isEqualTo(session);
+ return true;
+ });
+ }
+
+ @Test
+ public void shouldHandleExpiredSession() throws Exception {
+ // given
+ String sessionId = UUID.randomUUID().toString();
+ Document sessionDocument = new Document();
+
+ given(this.mongoOperations.findById(sessionId, Document.class,
+ ReactiveMongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME)).willReturn(Mono.just(sessionDocument));
+
+ MongoSession session = mock(MongoSession.class);
+
+ given(session.isExpired()).willReturn(true);
+ given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
+ TypeDescriptor.valueOf(MongoSession.class))).willReturn(session);
+
+ // when
+ StepVerifier.create(this.repository.findById(sessionId))
+ .expectNextMatches(mongoSession -> {
+ // then
+ verify(this.mongoOperations).remove(any(Document.class),
+ eq(ReactiveMongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME));
+ return true;
+ });
+
+ }
+
+ @Test
+ public void shouldDeleteSession() throws Exception {
+ // given
+ String sessionId = UUID.randomUUID().toString();
+
+ Document sessionDocument = new Document();
+
+ given(this.mongoOperations.findById(eq(sessionId), eq(Document.class),
+ eq(ReactiveMongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME))).willReturn(Mono.just(sessionDocument));
+ given(this.mongoOperations.remove((Mono extends Object>) any(), eq("sessions"))).willReturn(Mono.just(DeleteResult.acknowledged(1)));
+
+ // when
+ StepVerifier.create(this.repository.delete(sessionId))
+ .expectNextMatches(aVoid -> {
+ // then
+ verify(this.mongoOperations).remove(any(Document.class),
+ eq(ReactiveMongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME));
+ return true;
+ });
+ }
+}
diff --git a/src/test/java/org/springframework/session/data/mongo/integration/AbstractMongoRepositoryITest.java b/src/test/java/org/springframework/session/data/mongo/integration/AbstractMongoRepositoryITest.java
index ce799c6..9c74dce 100644
--- a/src/test/java/org/springframework/session/data/mongo/integration/AbstractMongoRepositoryITest.java
+++ b/src/test/java/org/springframework/session/data/mongo/integration/AbstractMongoRepositoryITest.java
@@ -22,7 +22,6 @@ import java.net.UnknownHostException;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
-import java.util.Optional;
import java.util.UUID;
import de.flapdoodle.embed.mongo.MongodExecutable;
@@ -108,8 +107,8 @@ abstract public class AbstractMongoRepositoryITest extends AbstractITest {
Session session = this.repository.findById(toSave.getId());
assertThat(session.getAttributeNames().size()).isEqualTo(2);
- assertThat(session.getAttribute("a")).isEqualTo(Optional.of("b"));
- assertThat(session.getAttribute("1")).isEqualTo(Optional.of("2"));
+ assertThat(session.getAttribute("a")).isEqualTo("b");
+ assertThat(session.getAttribute("1")).isEqualTo("2");
this.repository.deleteById(toSave.getId());
}