findByIndexNameAndIndexValue(String indexName, String indexValue) {
+ Assert.notNull(this.sessionIdGenerator, "sessionIdGenerator not initialized.");
+ return Optional.ofNullable(this.mongoSessionConverter.getQueryForIndex(indexName, indexValue))
+ .map((query) -> this.mongoOperations.find(query, Document.class, this.collectionName))
+ .orElse(Collections.emptyList())
+ .stream()
+ .map((dbSession) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, dbSession))
+ .peek((session) -> session.setSessionIdGenerator(this.sessionIdGenerator))
+ .collect(Collectors.toMap(MongoSession::getId, (mapSession) -> mapSession));
+ }
+
+ @Override
+ public void deleteById(String id) {
+
+ Optional.ofNullable(findSession(id)).ifPresent((document) -> {
+ MongoSession session = MongoSessionUtils.convertToSession(this.mongoSessionConverter, document);
+ if (session != null) {
+ publishEvent(new SessionDeletedEvent(this, session));
+ }
+ this.mongoOperations.remove(document, this.collectionName);
+ });
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+
+ IndexOperations indexOperations = this.mongoOperations.indexOps(this.collectionName);
+ this.mongoSessionConverter.ensureIndexes(indexOperations);
+ }
+
+ @Nullable private Document findSession(String id) {
+ return this.mongoOperations.findById(id, Document.class, this.collectionName);
+ }
+
+ @Override
+ public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
+ this.eventPublisher = eventPublisher;
+ }
+
+ private void publishEvent(ApplicationEvent event) {
+ if (this.eventPublisher == null) {
+ logger.error("Error publishing " + event + ". No event publisher set.");
+ } else {
+ try {
+ this.eventPublisher.publishEvent(event);
+ } catch (Throwable ex) {
+ logger.error("Error publishing " + event + ".", ex);
+ }
+ }
+ }
+
+ /**
+ * Set the maximum inactive interval in seconds between requests before newly created sessions will be invalidated.
+ * A negative time indicates that the session will never time out. The default is 30 minutes.
+ *
+ * @param defaultMaxInactiveInterval the default maxInactiveInterval
+ */
+ public void setDefaultMaxInactiveInterval(Duration defaultMaxInactiveInterval) {
+ Assert.notNull(defaultMaxInactiveInterval, "defaultMaxInactiveInterval must not be null");
+ this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
+ }
+
+ /**
+ * Set the maximum inactive interval in seconds between requests before newly created sessions will be invalidated.
+ * A negative time indicates that the session will never time out. The default is 1800 (30 minutes).
+ *
+ * @param defaultMaxInactiveInterval the default maxInactiveInterval in seconds
+ * @deprecated since 3.0.0, in favor of {@link #setDefaultMaxInactiveInterval(Duration)}
+ */
+ @Deprecated(since = "3.0.0")
+ @SuppressWarnings("InlineMeSuggester")
+ public void setMaxInactiveIntervalInSeconds(Integer defaultMaxInactiveInterval) {
+ setDefaultMaxInactiveInterval(Duration.ofSeconds(defaultMaxInactiveInterval));
+ }
+
+ public void setCollectionName(final String collectionName) {
+ this.collectionName = collectionName;
+ }
+
+ public void setMongoSessionConverter(final AbstractMongoSessionConverter mongoSessionConverter) {
+ this.mongoSessionConverter = mongoSessionConverter;
+ }
+
+ /**
+ * Set the {@link SessionIdGenerator} to use to generate session ids.
+ *
+ * @param sessionIdGenerator the {@link SessionIdGenerator} to use
+ * @since 3.2
+ */
+ public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
+ Assert.notNull(sessionIdGenerator, "sessionIdGenerator cannot be null");
+ this.sessionIdGenerator = sessionIdGenerator;
+ }
+}
diff --git a/src/main/java/org/mongodb/spring/session/MongoSession.java b/src/main/java/org/mongodb/spring/session/MongoSession.java
new file mode 100644
index 0000000..4ce9dbb
--- /dev/null
+++ b/src/main/java/org/mongodb/spring/session/MongoSession.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ * Copyright 2014-present 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.mongodb.spring.session;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.springframework.lang.Nullable;
+import org.springframework.session.MapSession;
+import org.springframework.session.Session;
+import org.springframework.session.SessionIdGenerator;
+import org.springframework.session.UuidSessionIdGenerator;
+import org.springframework.util.Assert;
+
+/**
+ * Session object providing additional information about the datetime of expiration.
+ *
+ * @author Jakub Kubrynski
+ * @author Greg Turnquist
+ * @since 1.2
+ */
+public final class MongoSession implements Session {
+
+ /**
+ * Mongo doesn't support {@literal dot} in field names. We replace it with a unicode character from the Private Use
+ * Area.
+ *
+ * NOTE: This was originally stored in unicode format. Delomboking the code caused it to get converted to another
+ * encoding, which isn't supported on all systems, so we migrated back to unicode. The same character is being
+ * represented ensuring binary compatibility. See https://www.compart.com/en/unicode/U+F607
+ */
+ private static final char DOT_COVER_CHAR = '\uF607';
+
+ private String id;
+
+ private final String originalSessionId;
+
+ private long createdMillis = System.currentTimeMillis();
+
+ private long accessedMillis;
+
+ private long intervalSeconds;
+
+ private Date expireAt;
+
+ private final Map attrs = new HashMap<>();
+
+ private transient SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
+
+ /**
+ * Constructs a new instance using the provided session id.
+ *
+ * @param sessionId the session id to use
+ * @since 3.2
+ */
+ public MongoSession(String sessionId) {
+ this(sessionId, MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
+ }
+
+ public MongoSession() {
+ this(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
+ }
+
+ public MongoSession(long maxInactiveIntervalInSeconds) {
+ this(UuidSessionIdGenerator.getInstance().generate(), maxInactiveIntervalInSeconds);
+ }
+
+ public MongoSession(String id, long maxInactiveIntervalInSeconds) {
+ this.id = id;
+ this.originalSessionId = id;
+ this.intervalSeconds = maxInactiveIntervalInSeconds;
+ setLastAccessedTime(Instant.ofEpochMilli(this.createdMillis));
+ }
+
+ /**
+ * Constructs a new instance using the provided {@link SessionIdGenerator}.
+ *
+ * @param sessionIdGenerator the {@link SessionIdGenerator} to use
+ * @since 3.2
+ */
+ public MongoSession(SessionIdGenerator sessionIdGenerator) {
+ this(sessionIdGenerator.generate(), MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
+ this.sessionIdGenerator = sessionIdGenerator;
+ }
+
+ /**
+ * Constructs a new instance using the provided {@link SessionIdGenerator} and max inactive interval.
+ *
+ * @param sessionIdGenerator the {@link SessionIdGenerator} to use
+ * @param maxInactiveIntervalInSeconds the max inactive interval in seconds
+ * @since 3.2
+ */
+ MongoSession(SessionIdGenerator sessionIdGenerator, long maxInactiveIntervalInSeconds) {
+ this(sessionIdGenerator.generate(), maxInactiveIntervalInSeconds);
+ this.sessionIdGenerator = sessionIdGenerator;
+ }
+
+ static String coverDot(String attributeName) {
+ return attributeName.replace('.', DOT_COVER_CHAR);
+ }
+
+ static String uncoverDot(String attributeName) {
+ return attributeName.replace(DOT_COVER_CHAR, '.');
+ }
+
+ @Override
+ public String changeSessionId() {
+
+ String changedId = this.sessionIdGenerator.generate();
+ this.id = changedId;
+ return changedId;
+ }
+
+ @Override
+ @Nullable @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
+ public T getAttribute(String attributeName) {
+ return (T) this.attrs.get(coverDot(attributeName));
+ }
+
+ @Override
+ public Set getAttributeNames() {
+ return this.attrs.keySet().stream().map(MongoSession::uncoverDot).collect(Collectors.toSet());
+ }
+
+ @Override
+ public void setAttribute(String attributeName, Object attributeValue) {
+
+ if (attributeValue == null) {
+ removeAttribute(coverDot(attributeName));
+ } else {
+ this.attrs.put(coverDot(attributeName), attributeValue);
+ }
+ }
+
+ @Override
+ public void removeAttribute(String attributeName) {
+ this.attrs.remove(coverDot(attributeName));
+ }
+
+ @Override
+ public Instant getCreationTime() {
+ return Instant.ofEpochMilli(this.createdMillis);
+ }
+
+ void setCreationTime(long created) {
+ this.createdMillis = created;
+ }
+
+ @Override
+ public Instant getLastAccessedTime() {
+ return Instant.ofEpochMilli(this.accessedMillis);
+ }
+
+ @Override
+ public void setLastAccessedTime(Instant lastAccessedTime) {
+ this.accessedMillis = lastAccessedTime.toEpochMilli();
+ this.expireAt = Date.from(lastAccessedTime.plus(Duration.ofSeconds(this.intervalSeconds)));
+ }
+
+ @Override
+ public Duration getMaxInactiveInterval() {
+ return Duration.ofSeconds(this.intervalSeconds);
+ }
+
+ @Override
+ public void setMaxInactiveInterval(Duration interval) {
+ this.intervalSeconds = interval.toSeconds();
+ }
+
+ @Override
+ public boolean isExpired() {
+ return this.intervalSeconds >= 0 && Instant.now().isAfter(expireAt.toInstant());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ MongoSession that = (MongoSession) o;
+ return Objects.equals(this.id, that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.id);
+ }
+
+ @Override
+ public String getId() {
+ return this.id;
+ }
+
+ Date getExpireAt() {
+ return this.expireAt;
+ }
+
+ void setExpireAt(final Date expireAt) {
+ this.expireAt = expireAt;
+ }
+
+ boolean hasChangedSessionId() {
+ return !getId().equals(this.originalSessionId);
+ }
+
+ String getOriginalSessionId() {
+ return this.originalSessionId;
+ }
+
+ /**
+ * Sets the session id.
+ *
+ * @param id the id to set
+ * @since 3.2
+ */
+ void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Sets the {@link SessionIdGenerator} to use.
+ *
+ * @param sessionIdGenerator the {@link SessionIdGenerator} to use
+ * @since 3.2
+ */
+ void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
+ Assert.notNull(sessionIdGenerator, "sessionIdGenerator cannot be null");
+ this.sessionIdGenerator = sessionIdGenerator;
+ }
+}
diff --git a/src/main/java/org/mongodb/spring/session/MongoSessionUtils.java b/src/main/java/org/mongodb/spring/session/MongoSessionUtils.java
new file mode 100644
index 0000000..415dc4b
--- /dev/null
+++ b/src/main/java/org/mongodb/spring/session/MongoSessionUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ * Copyright 2014-present 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.mongodb.spring.session;
+
+import com.mongodb.ConnectionString;
+import com.mongodb.DBObject;
+import org.bson.Document;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.lang.Nullable;
+
+/**
+ * Utility for MongoSession.
+ *
+ * @author Greg Turnquist
+ */
+final class MongoSessionUtils {
+
+ private static final String DEFAULT_URI = "mongodb://localhost:27017";
+ private static final String URI_SYSTEM_PROPERTY_NAME = "org.mongodb.test.uri";
+ public static final String DEFAULT_DATABASE_NAME = "MongoSpringSessionTest";
+
+ private MongoSessionUtils() {}
+
+ @Nullable static DBObject convertToDBObject(AbstractMongoSessionConverter mongoSessionConverter, MongoSession session) {
+
+ return (DBObject) mongoSessionConverter.convert(
+ session, TypeDescriptor.valueOf(MongoSession.class), TypeDescriptor.valueOf(DBObject.class));
+ }
+
+ @Nullable static MongoSession convertToSession(AbstractMongoSessionConverter mongoSessionConverter, Document session) {
+
+ return (MongoSession) mongoSessionConverter.convert(
+ session, TypeDescriptor.valueOf(Document.class), TypeDescriptor.valueOf(MongoSession.class));
+ }
+
+ static ConnectionString getConnectionString() {
+ String connectionString = System.getProperty(URI_SYSTEM_PROPERTY_NAME, DEFAULT_URI);
+ return new ConnectionString(connectionString);
+ }
+}
diff --git a/src/main/java/org/mongodb/spring/session/ReactiveMongoSessionRepository.java b/src/main/java/org/mongodb/spring/session/ReactiveMongoSessionRepository.java
new file mode 100644
index 0000000..e52cad8
--- /dev/null
+++ b/src/main/java/org/mongodb/spring/session/ReactiveMongoSessionRepository.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ * Copyright 2014-present 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.mongodb.spring.session;
+
+import java.time.Duration;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.bson.Document;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.data.mongodb.core.ReactiveMongoOperations;
+import org.springframework.data.mongodb.core.index.IndexOperations;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.lang.Nullable;
+import org.springframework.session.MapSession;
+import org.springframework.session.ReactiveSessionRepository;
+import org.springframework.session.SessionIdGenerator;
+import org.springframework.session.UuidSessionIdGenerator;
+import org.springframework.session.events.SessionCreatedEvent;
+import org.springframework.session.events.SessionDeletedEvent;
+import org.springframework.util.Assert;
+import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
+
+/**
+ * A {@link ReactiveSessionRepository} implementation that uses Spring Data MongoDB.
+ *
+ * @author Greg Turnquist
+ * @author Vedran Pavic
+ * @since 2.2.0
+ */
+public class ReactiveMongoSessionRepository
+ implements ReactiveSessionRepository, ApplicationEventPublisherAware, InitializingBean {
+
+ /**
+ * The default time period in seconds in which a session will expire.
+ *
+ * @deprecated since 3.0.0 in favor of {@link MapSession#DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS}
+ */
+ @Deprecated
+ public static final int DEFAULT_INACTIVE_INTERVAL = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
+
+ /** The default collection name for storing session. */
+ public static final String DEFAULT_COLLECTION_NAME = "sessions";
+
+ private static final Log logger = LogFactory.getLog(ReactiveMongoSessionRepository.class);
+
+ private final ReactiveMongoOperations mongoOperations;
+
+ private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
+
+ private String collectionName = DEFAULT_COLLECTION_NAME;
+
+ private AbstractMongoSessionConverter mongoSessionConverter =
+ new JdkMongoSessionConverter(this.defaultMaxInactiveInterval);
+
+ @Nullable private MongoOperations blockingMongoOperations;
+
+ @Nullable private ApplicationEventPublisher eventPublisher;
+
+ private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
+
+ public ReactiveMongoSessionRepository(ReactiveMongoOperations mongoOperations) {
+ this.mongoOperations = mongoOperations;
+ }
+
+ /**
+ * Creates a new {@link MongoSession} that is capable of being persisted by this {@link ReactiveSessionRepository}.
+ *
+ * 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 ReactiveSessionRepository}
+ */
+ @Override
+ public Mono createSession() {
+ // @formatter:off
+ return Mono.fromSupplier(() -> this.sessionIdGenerator.generate())
+ .zipWith(Mono.just(this.defaultMaxInactiveInterval.toSeconds()))
+ .map((tuple) -> new MongoSession(tuple.getT1(), tuple.getT2()))
+ .doOnNext((mongoSession) -> mongoSession.setMaxInactiveInterval(this.defaultMaxInactiveInterval))
+ .doOnNext((mongoSession) -> mongoSession.setSessionIdGenerator(this.sessionIdGenerator))
+ .doOnNext((mongoSession) -> publishEvent(new SessionCreatedEvent(this, mongoSession)))
+ .switchIfEmpty(Mono.just(new MongoSession(this.sessionIdGenerator)))
+ .subscribeOn(Schedulers.boundedElastic())
+ .publishOn(Schedulers.parallel());
+ // @formatter:on
+ }
+
+ @Override
+ public Mono save(MongoSession session) {
+
+ return Mono //
+ .justOrEmpty(MongoSessionUtils.convertToDBObject(this.mongoSessionConverter, session)) //
+ .flatMap((dbObject) -> {
+ if (session.hasChangedSessionId()) {
+
+ return this.mongoOperations
+ .remove(
+ Query.query(Criteria.where("_id").is(session.getOriginalSessionId())),
+ this.collectionName) //
+ .then(this.mongoOperations.save(dbObject, this.collectionName));
+ } else {
+
+ return this.mongoOperations.save(dbObject, this.collectionName);
+ }
+ }) //
+ .then();
+ }
+
+ @Override
+ public Mono findById(String id) {
+
+ return findSession(id) //
+ .map((document) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, document)) //
+ .filter((mongoSession) -> !mongoSession.isExpired()) //
+ .doOnNext((mongoSession) -> mongoSession.setSessionIdGenerator(this.sessionIdGenerator))
+ .switchIfEmpty(Mono.defer(() -> this.deleteById(id).then(Mono.empty())));
+ }
+
+ @Override
+ public Mono deleteById(String id) {
+
+ return findSession(id) //
+ .flatMap((document) -> this.mongoOperations
+ .remove(document, this.collectionName) //
+ .then(Mono.just(document))) //
+ .map((document) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, document)) //
+ .doOnNext((mongoSession) -> publishEvent(new SessionDeletedEvent(this, mongoSession))) //
+ .then();
+ }
+
+ /**
+ * Do not use {@link org.springframework.data.mongodb.core.index.ReactiveIndexOperations} to ensure indexes exist.
+ * Instead, get a blocking {@link IndexOperations} and use that instead, if possible.
+ */
+ @Override
+ public void afterPropertiesSet() {
+
+ if (this.blockingMongoOperations != null) {
+
+ IndexOperations indexOperations = this.blockingMongoOperations.indexOps(this.collectionName);
+ this.mongoSessionConverter.ensureIndexes(indexOperations);
+ }
+ }
+
+ private Mono findSession(String id) {
+ return this.mongoOperations.findById(id, Document.class, this.collectionName);
+ }
+
+ @Override
+ public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
+ this.eventPublisher = eventPublisher;
+ }
+
+ private void publishEvent(ApplicationEvent event) {
+ if (this.eventPublisher == null) {
+ logger.error("Error publishing " + event + ". No event publisher set.");
+ } else {
+ try {
+ this.eventPublisher.publishEvent(event);
+ } catch (Throwable ex) {
+ logger.error("Error publishing " + event + ".", ex);
+ }
+ }
+ }
+
+ /**
+ * Set the maximum inactive interval in seconds between requests before newly created sessions will be invalidated.
+ * A negative time indicates that the session will never time out. The default is 30 minutes.
+ *
+ * @param defaultMaxInactiveInterval the default maxInactiveInterval
+ */
+ public void setDefaultMaxInactiveInterval(Duration defaultMaxInactiveInterval) {
+ Assert.notNull(defaultMaxInactiveInterval, "defaultMaxInactiveInterval must not be null");
+ this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
+ }
+
+ /**
+ * Set the maximum inactive interval in seconds between requests before newly created sessions will be invalidated.
+ * A negative time indicates that the session will never time out. The default is 1800 (30 minutes).
+ *
+ * @param defaultMaxInactiveInterval the default maxInactiveInterval in seconds
+ * @deprecated since 3.0.0, in favor of {@link #setDefaultMaxInactiveInterval(Duration)}
+ */
+ @Deprecated(since = "3.0.0")
+ @SuppressWarnings("InlineMeSuggester")
+ public void setMaxInactiveIntervalInSeconds(Integer defaultMaxInactiveInterval) {
+ setDefaultMaxInactiveInterval(Duration.ofSeconds(defaultMaxInactiveInterval));
+ }
+
+ public String getCollectionName() {
+ return this.collectionName;
+ }
+
+ public void setCollectionName(final String collectionName) {
+ this.collectionName = collectionName;
+ }
+
+ public void setMongoSessionConverter(final AbstractMongoSessionConverter mongoSessionConverter) {
+ this.mongoSessionConverter = mongoSessionConverter;
+ }
+
+ public void setBlockingMongoOperations(final MongoOperations blockingMongoOperations) {
+ this.blockingMongoOperations = blockingMongoOperations;
+ }
+
+ public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
+ Assert.notNull(sessionIdGenerator, "sessionIdGenerator cannot be null");
+ this.sessionIdGenerator = sessionIdGenerator;
+ }
+}
diff --git a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/EnableMongoHttpSession.java b/src/main/java/org/mongodb/spring/session/config/annotation/web/http/EnableMongoHttpSession.java
similarity index 64%
rename from src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/EnableMongoHttpSession.java
rename to src/main/java/org/mongodb/spring/session/config/annotation/web/http/EnableMongoHttpSession.java
index a314592..35f1ed7 100644
--- a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/http/EnableMongoHttpSession.java
+++ b/src/main/java/org/mongodb/spring/session/config/annotation/web/http/EnableMongoHttpSession.java
@@ -1,11 +1,12 @@
/*
+ * Copyright 2025-present MongoDB, Inc.
* Copyright 2014-present 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
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,23 +15,21 @@
* limitations under the License.
*/
-package org.springframework.session.data.mongo.config.annotation.web.http;
+package org.mongodb.spring.session.config.annotation.web.http;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-
+import org.mongodb.spring.session.MongoIndexedSessionRepository;
import org.springframework.context.annotation.Import;
import org.springframework.session.MapSession;
-import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
/**
- * Add this annotation to a {@code @Configuration} class to expose the
- * SessionRepositoryFilter as a bean named "springSessionRepositoryFilter" and backed by
- * Mongo. Use {@code collectionName} to change default name of the collection used to
- * store sessions.
+ * Add this annotation to a {@code @Configuration} class to expose the SessionRepositoryFilter as a bean named
+ * "springSessionRepositoryFilter" and backed by Mongo. Use {@code collectionName} to change default name of the
+ * collection used to store sessions.
*
*
*
@@ -55,16 +54,17 @@
@Import(MongoHttpSessionConfiguration.class)
public @interface EnableMongoHttpSession {
- /**
- * The maximum time a session will be kept if it is inactive.
- * @return default max inactive interval in seconds
- */
- int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
-
- /**
- * The collection name to use.
- * @return name of the collection to store session
- */
- String collectionName() default MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME;
+ /**
+ * The maximum time a session will be kept if it is inactive.
+ *
+ * @return default max inactive interval in seconds
+ */
+ int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
+ /**
+ * The collection name to use.
+ *
+ * @return name of the collection to store session
+ */
+ String collectionName() default MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME;
}
diff --git a/src/main/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfiguration.java b/src/main/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfiguration.java
new file mode 100644
index 0000000..041f472
--- /dev/null
+++ b/src/main/java/org/mongodb/spring/session/config/annotation/web/http/MongoHttpSessionConfiguration.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ * Copyright 2014-present 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.mongodb.spring.session.config.annotation.web.http;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.mongodb.spring.session.AbstractMongoSessionConverter;
+import org.mongodb.spring.session.JdkMongoSessionConverter;
+import org.mongodb.spring.session.MongoIndexedSessionRepository;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.EmbeddedValueResolverAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.ImportAware;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.serializer.support.DeserializingConverter;
+import org.springframework.core.serializer.support.SerializingConverter;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.lang.Nullable;
+import org.springframework.session.IndexResolver;
+import org.springframework.session.MapSession;
+import org.springframework.session.Session;
+import org.springframework.session.SessionIdGenerator;
+import org.springframework.session.UuidSessionIdGenerator;
+import org.springframework.session.config.SessionRepositoryCustomizer;
+import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.springframework.util.StringValueResolver;
+
+/**
+ * Configuration class registering {@code MongoSessionRepository} bean. To import this configuration use
+ * {@link EnableMongoHttpSession} annotation.
+ *
+ * @author Jakub Kubrynski
+ * @author Eddú Meléndez
+ * @since 1.2
+ */
+@Configuration(proxyBeanMethods = false)
+@Import(SpringHttpSessionConfiguration.class)
+public class MongoHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
+
+ private AbstractMongoSessionConverter mongoSessionConverter;
+
+ private Duration maxInactiveInterval = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL;
+
+ @Nullable private String collectionName;
+
+ @Nullable private StringValueResolver embeddedValueResolver;
+
+ @Nullable private List> sessionRepositoryCustomizers;
+
+ @Nullable private ClassLoader classLoader;
+
+ @Nullable private IndexResolver indexResolver;
+
+ private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
+
+ @Bean
+ @SuppressWarnings("NullAway")
+ public MongoIndexedSessionRepository mongoSessionRepository(MongoOperations mongoOperations) {
+
+ MongoIndexedSessionRepository repository = new MongoIndexedSessionRepository(mongoOperations);
+ repository.setDefaultMaxInactiveInterval(this.maxInactiveInterval);
+
+ if (this.mongoSessionConverter != null) {
+ repository.setMongoSessionConverter(this.mongoSessionConverter);
+
+ if (this.indexResolver != null) {
+ this.mongoSessionConverter.setIndexResolver(this.indexResolver);
+ }
+ } else {
+ JdkMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(
+ new SerializingConverter(),
+ new DeserializingConverter(this.classLoader),
+ Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
+
+ if (this.indexResolver != null) {
+ mongoSessionConverter.setIndexResolver(this.indexResolver);
+ }
+
+ repository.setMongoSessionConverter(mongoSessionConverter);
+ }
+
+ if (StringUtils.hasText(this.collectionName)) {
+ repository.setCollectionName(this.collectionName);
+ }
+ repository.setSessionIdGenerator(this.sessionIdGenerator);
+
+ Assert.notNull(this.sessionRepositoryCustomizers, "SessionRepositoryCustomizers not initialized.");
+ this.sessionRepositoryCustomizers.forEach(
+ (sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(repository));
+
+ return repository;
+ }
+
+ public void setCollectionName(String collectionName) {
+ this.collectionName = collectionName;
+ }
+
+ public void setMaxInactiveInterval(Duration maxInactiveInterval) {
+ this.maxInactiveInterval = maxInactiveInterval;
+ }
+
+ @Deprecated
+ @SuppressWarnings("InlineMeSuggester")
+ public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
+ setMaxInactiveInterval(Duration.ofSeconds(maxInactiveIntervalInSeconds));
+ }
+
+ @Override
+ @SuppressWarnings("NullAway")
+ public void setImportMetadata(AnnotationMetadata importMetadata) {
+
+ AnnotationAttributes attributes = AnnotationAttributes.fromMap(
+ importMetadata.getAnnotationAttributes(EnableMongoHttpSession.class.getName()));
+
+ if (attributes != null) {
+ this.maxInactiveInterval =
+ Duration.ofSeconds(attributes.getNumber("maxInactiveIntervalInSeconds"));
+ }
+
+ String collectionNameValue = (attributes != null) ? attributes.getString("collectionName") : "";
+ if (StringUtils.hasText(collectionNameValue)) {
+ Assert.notNull(this.embeddedValueResolver, "EmbeddedValueResolver not initialized.");
+ this.collectionName = this.embeddedValueResolver.resolveStringValue(collectionNameValue);
+ }
+ }
+
+ @Autowired(required = false)
+ public void setMongoSessionConverter(AbstractMongoSessionConverter mongoSessionConverter) {
+ this.mongoSessionConverter = mongoSessionConverter;
+ }
+
+ @Autowired(required = false)
+ public void setSessionRepositoryCustomizers(
+ ObjectProvider> sessionRepositoryCustomizers) {
+ this.sessionRepositoryCustomizers =
+ sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
+ }
+
+ @Override
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ public void setEmbeddedValueResolver(StringValueResolver resolver) {
+ this.embeddedValueResolver = resolver;
+ }
+
+ @Autowired(required = false)
+ public void setIndexResolver(IndexResolver indexResolver) {
+ this.indexResolver = indexResolver;
+ }
+
+ @Autowired(required = false)
+ public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
+ this.sessionIdGenerator = sessionIdGenerator;
+ }
+}
diff --git a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/EnableMongoWebSession.java b/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/EnableMongoWebSession.java
similarity index 59%
rename from src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/EnableMongoWebSession.java
rename to src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/EnableMongoWebSession.java
index 9581e45..07b543f 100644
--- a/src/main/java/org/springframework/session/data/mongo/config/annotation/web/reactive/EnableMongoWebSession.java
+++ b/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/EnableMongoWebSession.java
@@ -1,11 +1,12 @@
/*
+ * Copyright 2025-present MongoDB, Inc.
* Copyright 2014-present 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
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,21 +15,19 @@
* limitations under the License.
*/
-package org.springframework.session.data.mongo.config.annotation.web.reactive;
+package org.mongodb.spring.session.config.annotation.web.reactive;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
-
+import org.mongodb.spring.session.ReactiveMongoSessionRepository;
import org.springframework.context.annotation.Import;
import org.springframework.session.MapSession;
-import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
/**
- * Add this annotation to a {@code @Configuration} class to configure a MongoDB-based
- * {@code WebSessionManager} for a WebFlux application. This annotation assumes a
- * {@code ReactorMongoOperations} is defined somewhere in the application context. If not,
- * it will fail with a clear error messages. For example:
+ * Add this annotation to a {@code @Configuration} class to configure a MongoDB-based {@code WebSessionManager} for a
+ * WebFlux application. This annotation assumes a {@code ReactorMongoOperations} is defined somewhere in the application
+ * context. If not, it will fail with a clear error messages. For example:
*
*
*
@@ -49,21 +48,22 @@
* @since 2.0
*/
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
-@Target({ java.lang.annotation.ElementType.TYPE })
+@Target({java.lang.annotation.ElementType.TYPE})
@Documented
@Import(ReactiveMongoWebSessionConfiguration.class)
public @interface EnableMongoWebSession {
- /**
- * The maximum time a session will be kept if it is inactive.
- * @return default max inactive interval in seconds
- */
- int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
-
- /**
- * The collection name to use.
- * @return name of the collection to store session
- */
- String collectionName() default ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME;
+ /**
+ * The maximum time a session will be kept if it is inactive.
+ *
+ * @return default max inactive interval in seconds
+ */
+ int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
+ /**
+ * The collection name to use.
+ *
+ * @return name of the collection to store session
+ */
+ String collectionName() default ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME;
}
diff --git a/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java b/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
new file mode 100644
index 0000000..43f4972
--- /dev/null
+++ b/src/main/java/org/mongodb/spring/session/config/annotation/web/reactive/ReactiveMongoWebSessionConfiguration.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ * Copyright 2014-present 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.mongodb.spring.session.config.annotation.web.reactive;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.mongodb.spring.session.AbstractMongoSessionConverter;
+import org.mongodb.spring.session.JdkMongoSessionConverter;
+import org.mongodb.spring.session.ReactiveMongoSessionRepository;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.EmbeddedValueResolverAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.ImportAware;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.serializer.support.DeserializingConverter;
+import org.springframework.core.serializer.support.SerializingConverter;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.data.mongodb.core.ReactiveMongoOperations;
+import org.springframework.lang.Nullable;
+import org.springframework.session.IndexResolver;
+import org.springframework.session.MapSession;
+import org.springframework.session.Session;
+import org.springframework.session.SessionIdGenerator;
+import org.springframework.session.UuidSessionIdGenerator;
+import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
+import org.springframework.session.config.annotation.web.server.SpringWebSessionConfiguration;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.springframework.util.StringValueResolver;
+
+/**
+ * Configure a {@link ReactiveMongoSessionRepository} using a provided {@link ReactiveMongoOperations}.
+ *
+ * @author Greg Turnquist
+ * @author Vedran Pavic
+ */
+@Configuration(proxyBeanMethods = false)
+@Import(SpringWebSessionConfiguration.class)
+public class ReactiveMongoWebSessionConfiguration
+ implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
+
+ @Nullable private AbstractMongoSessionConverter mongoSessionConverter;
+
+ private Duration maxInactiveInterval = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL;
+
+ @Nullable private String collectionName;
+
+ @Nullable private StringValueResolver embeddedValueResolver;
+
+ private List> sessionRepositoryCustomizers;
+
+ @Autowired(required = false)
+ @Nullable private MongoOperations mongoOperations;
+
+ @Nullable private ClassLoader classLoader;
+
+ @Nullable private IndexResolver indexResolver;
+
+ private SessionIdGenerator sessionIdGenerator = UuidSessionIdGenerator.getInstance();
+
+ @Bean
+ public ReactiveMongoSessionRepository reactiveMongoSessionRepository(ReactiveMongoOperations operations) {
+
+ ReactiveMongoSessionRepository repository = new ReactiveMongoSessionRepository(operations);
+
+ if (this.mongoSessionConverter != null) {
+ repository.setMongoSessionConverter(this.mongoSessionConverter);
+
+ if (this.indexResolver != null) {
+ this.mongoSessionConverter.setIndexResolver(this.indexResolver);
+ }
+
+ } else {
+ JdkMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(
+ new SerializingConverter(),
+ new DeserializingConverter(this.classLoader),
+ Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
+
+ if (this.indexResolver != null) {
+ mongoSessionConverter.setIndexResolver(this.indexResolver);
+ }
+
+ repository.setMongoSessionConverter(mongoSessionConverter);
+ }
+
+ repository.setDefaultMaxInactiveInterval(this.maxInactiveInterval);
+
+ if (this.collectionName != null) {
+ repository.setCollectionName(this.collectionName);
+ }
+
+ if (this.mongoOperations != null) {
+ repository.setBlockingMongoOperations(this.mongoOperations);
+ }
+
+ this.sessionRepositoryCustomizers.forEach(
+ (sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(repository));
+
+ repository.setSessionIdGenerator(this.sessionIdGenerator);
+
+ return repository;
+ }
+
+ @Autowired(required = false)
+ public void setMongoSessionConverter(AbstractMongoSessionConverter mongoSessionConverter) {
+ this.mongoSessionConverter = mongoSessionConverter;
+ }
+
+ @Override
+ @SuppressWarnings("NullAway")
+ public void setImportMetadata(AnnotationMetadata importMetadata) {
+
+ AnnotationAttributes attributes = AnnotationAttributes.fromMap(
+ importMetadata.getAnnotationAttributes(EnableMongoWebSession.class.getName()));
+
+ if (attributes != null) {
+ this.maxInactiveInterval =
+ Duration.ofSeconds(attributes.getNumber("maxInactiveIntervalInSeconds"));
+ }
+
+ String collectionNameValue = (attributes != null) ? attributes.getString("collectionName") : "";
+ if (StringUtils.hasText(collectionNameValue)) {
+ Assert.notNull(this.embeddedValueResolver, "EmbeddedValueResolver not initialized.");
+ this.collectionName = this.embeddedValueResolver.resolveStringValue(collectionNameValue);
+ }
+ }
+
+ @Override
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) {
+ this.embeddedValueResolver = embeddedValueResolver;
+ }
+
+ public Duration getMaxInactiveInterval() {
+ return this.maxInactiveInterval;
+ }
+
+ public void setMaxInactiveInterval(Duration maxInactiveInterval) {
+ this.maxInactiveInterval = maxInactiveInterval;
+ }
+
+ @Deprecated
+ @SuppressWarnings("InlineMeSuggester")
+ public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
+ setMaxInactiveInterval(Duration.ofSeconds(maxInactiveIntervalInSeconds));
+ }
+
+ @Nullable public String getCollectionName() {
+ return this.collectionName;
+ }
+
+ public void setCollectionName(String collectionName) {
+ this.collectionName = collectionName;
+ }
+
+ @Autowired(required = false)
+ public void setSessionRepositoryCustomizers(
+ ObjectProvider>
+ sessionRepositoryCustomizers) {
+ this.sessionRepositoryCustomizers =
+ sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
+ }
+
+ @Autowired(required = false)
+ public void setIndexResolver(IndexResolver indexResolver) {
+ this.indexResolver = indexResolver;
+ }
+
+ @Autowired(required = false)
+ public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
+ this.sessionIdGenerator = sessionIdGenerator;
+ }
+}
diff --git a/src/main/java/org/springframework/session/data/mongo/package-info.java b/src/main/java/org/mongodb/spring/session/package-info.java
similarity index 94%
rename from src/main/java/org/springframework/session/data/mongo/package-info.java
rename to src/main/java/org/mongodb/spring/session/package-info.java
index f7881e7..c4c4138 100644
--- a/src/main/java/org/springframework/session/data/mongo/package-info.java
+++ b/src/main/java/org/mongodb/spring/session/package-info.java
@@ -20,6 +20,6 @@
* @author Greg Turnquist
*/
@NonNullApi
-package org.springframework.session.data.mongo;
+package org.mongodb.spring.session;
import org.springframework.lang.NonNullApi;
diff --git a/src/main/java/org/springframework/session/data/mongo/AbstractMongoSessionConverter.java b/src/main/java/org/springframework/session/data/mongo/AbstractMongoSessionConverter.java
deleted file mode 100644
index ef7c328..0000000
--- a/src/main/java/org/springframework/session/data/mongo/AbstractMongoSessionConverter.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2014-present 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
- *
- * https://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 java.util.Collections;
-import java.util.Set;
-
-import com.mongodb.DBObject;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.bson.Document;
-
-import org.springframework.core.convert.TypeDescriptor;
-import org.springframework.core.convert.converter.GenericConverter;
-import org.springframework.data.domain.Sort;
-import org.springframework.data.mongodb.core.index.Index;
-import org.springframework.data.mongodb.core.index.IndexInfo;
-import org.springframework.data.mongodb.core.index.IndexOperations;
-import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.lang.Nullable;
-import org.springframework.session.DelegatingIndexResolver;
-import org.springframework.session.FindByIndexNameSessionRepository;
-import org.springframework.session.IndexResolver;
-import org.springframework.session.PrincipalNameIndexResolver;
-import org.springframework.session.Session;
-import org.springframework.util.Assert;
-
-/**
- * Base class for serializing and deserializing session objects. To create custom
- * serializer you have to implement this interface and simply register your class as a
- * bean.
- *
- * @author Jakub Kubrynski
- * @author Greg Turnquist
- * @since 1.2
- */
-public abstract class AbstractMongoSessionConverter implements GenericConverter {
-
- static final String EXPIRE_AT_FIELD_NAME = "expireAt";
-
- private static final Log LOG = LogFactory.getLog(AbstractMongoSessionConverter.class);
-
- private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
-
- private IndexResolver indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
-
- /**
- * Returns query to be executed to return sessions based on a particular index.
- * @param indexName name of the index
- * @param indexValue value to query against
- * @return built query or null if indexName is not supported
- */
- @Nullable
- protected abstract Query getQueryForIndex(String indexName, Object indexValue);
-
- /**
- * Method ensures that there is a TTL index on {@literal expireAt} field. It's has
- * {@literal expireAfterSeconds} set to zero seconds, so the expiration time is
- * controlled by the application. It can be extended in custom converters when there
- * is a need for creating additional custom indexes.
- * @param sessionCollectionIndexes {@link IndexOperations} to use
- */
- protected void ensureIndexes(IndexOperations sessionCollectionIndexes) {
-
- for (IndexInfo info : sessionCollectionIndexes.getIndexInfo()) {
- if (EXPIRE_AT_FIELD_NAME.equals(info.getName())) {
- LOG.debug("TTL index on field " + EXPIRE_AT_FIELD_NAME + " already exists");
- return;
- }
- }
-
- LOG.info("Creating TTL index on field " + EXPIRE_AT_FIELD_NAME);
-
- sessionCollectionIndexes
- .ensureIndex(new Index(EXPIRE_AT_FIELD_NAME, Sort.Direction.ASC).named(EXPIRE_AT_FIELD_NAME).expire(0));
- }
-
- protected String extractPrincipal(MongoSession expiringSession) {
-
- return this.indexResolver.resolveIndexesFor(expiringSession)
- .get(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
- }
-
- public Set getConvertibleTypes() {
-
- return Collections.singleton(new ConvertiblePair(DBObject.class, MongoSession.class));
- }
-
- @SuppressWarnings("unchecked")
- @Nullable
- public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
-
- if (source == null) {
- return null;
- }
-
- if (DBObject.class.isAssignableFrom(sourceType.getType())) {
- return convert(new Document(((DBObject) source).toMap()));
- }
- else if (Document.class.isAssignableFrom(sourceType.getType())) {
- return convert((Document) source);
- }
- else {
- return convert((MongoSession) source);
- }
- }
-
- protected abstract DBObject convert(MongoSession session);
-
- protected abstract MongoSession convert(Document sessionWrapper);
-
- public void setIndexResolver(IndexResolver indexResolver) {
- Assert.notNull(indexResolver, "indexResolver must not be null");
- this.indexResolver = indexResolver;
- }
-
-}
diff --git a/src/main/java/org/springframework/session/data/mongo/JacksonMongoSessionConverter.java b/src/main/java/org/springframework/session/data/mongo/JacksonMongoSessionConverter.java
deleted file mode 100644
index f66333c..0000000
--- a/src/main/java/org/springframework/session/data/mongo/JacksonMongoSessionConverter.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright 2014-present 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
- *
- * https://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 java.io.IOException;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.Module;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import com.mongodb.BasicDBObject;
-import com.mongodb.DBObject;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.bson.Document;
-import org.bson.json.JsonMode;
-import org.bson.json.JsonWriterSettings;
-
-import org.springframework.data.mongodb.core.query.Criteria;
-import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.lang.Nullable;
-import org.springframework.security.jackson2.SecurityJackson2Modules;
-import org.springframework.session.FindByIndexNameSessionRepository;
-import org.springframework.util.Assert;
-
-/**
- * {@code AbstractMongoSessionConverter} implementation using Jackson.
- *
- * @author Jakub Kubrynski
- * @author Greg Turnquist
- * @author Michael Ruf
- * @since 1.2
- */
-public class JacksonMongoSessionConverter extends AbstractMongoSessionConverter {
-
- private static final Log LOG = LogFactory.getLog(JacksonMongoSessionConverter.class);
-
- private static final String ATTRS_FIELD_NAME = "attrs.";
-
- private static final String PRINCIPAL_FIELD_NAME = "principal";
-
- private static final String EXPIRE_AT_FIELD_NAME = "expireAt";
-
- private final ObjectMapper objectMapper;
-
- public JacksonMongoSessionConverter() {
- this(Collections.emptyList());
- }
-
- public JacksonMongoSessionConverter(Iterable modules) {
-
- this.objectMapper = buildObjectMapper();
- this.objectMapper.registerModules(modules);
- }
-
- public JacksonMongoSessionConverter(ObjectMapper objectMapper) {
-
- Assert.notNull(objectMapper, "ObjectMapper can NOT be null!");
- this.objectMapper = objectMapper;
- }
-
- @Nullable
- protected Query getQueryForIndex(String indexName, Object indexValue) {
-
- if (FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
- return Query.query(Criteria.where(PRINCIPAL_FIELD_NAME).is(indexValue));
- }
- else {
- return Query.query(Criteria.where(ATTRS_FIELD_NAME + MongoSession.coverDot(indexName)).is(indexValue));
- }
- }
-
- private ObjectMapper buildObjectMapper() {
-
- ObjectMapper objectMapper = new ObjectMapper();
-
- // serialize fields instead of properties
- objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
- objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
-
- // ignore unresolved fields (mostly 'principal')
- objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
-
- objectMapper.setPropertyNamingStrategy(new MongoIdNamingStrategy());
-
- objectMapper.registerModules(SecurityJackson2Modules.getModules(getClass().getClassLoader()));
- objectMapper.addMixIn(MongoSession.class, MongoSessionMixin.class);
- objectMapper.addMixIn(HashMap.class, HashMapMixin.class);
-
- return objectMapper;
- }
-
- @Override
- protected DBObject convert(MongoSession source) {
-
- try {
- DBObject dbSession = BasicDBObject.parse(this.objectMapper.writeValueAsString(source));
-
- // Override default serialization with proper values.
- dbSession.put(PRINCIPAL_FIELD_NAME, extractPrincipal(source));
- dbSession.put(EXPIRE_AT_FIELD_NAME, source.getExpireAt());
- return dbSession;
- }
- catch (JsonProcessingException ex) {
- throw new IllegalStateException("Cannot convert MongoExpiringSession", ex);
- }
- }
-
- @Override
- @Nullable
- protected MongoSession convert(Document source) {
-
- Date expireAt = (Date) source.remove(EXPIRE_AT_FIELD_NAME);
- source.remove("originalSessionId");
- String json = source.toJson(JsonWriterSettings.builder().outputMode(JsonMode.RELAXED).build());
-
- try {
- MongoSession mongoSession = this.objectMapper.readValue(json, MongoSession.class);
- mongoSession.setExpireAt(expireAt);
- return mongoSession;
- }
- catch (IOException ex) {
- LOG.error("Error during Mongo Session deserialization", ex);
- return null;
- }
- }
-
- /**
- * Used to whitelist {@link MongoSession} for {@link SecurityJackson2Modules}.
- */
- private static class MongoSessionMixin {
-
- @JsonCreator
- MongoSessionMixin(@JsonProperty("_id") String id,
- @JsonProperty("intervalSeconds") long maxInactiveIntervalInSeconds) {
- }
-
- }
-
- /**
- * Used to whitelist {@link HashMap} for {@link SecurityJackson2Modules}.
- */
- private static class HashMapMixin {
-
- // Nothing special
-
- }
-
- private static class MongoIdNamingStrategy extends PropertyNamingStrategies.NamingBase {
-
- @Override
- public String translate(String propertyName) {
-
- switch (propertyName) {
- case "id":
- return "_id";
- case "_id":
- return "id";
- default:
- return propertyName;
- }
- }
-
- }
-
-}
diff --git a/src/main/java/org/springframework/session/data/mongo/JdkMongoSessionConverter.java b/src/main/java/org/springframework/session/data/mongo/JdkMongoSessionConverter.java
deleted file mode 100644
index 19a8b56..0000000
--- a/src/main/java/org/springframework/session/data/mongo/JdkMongoSessionConverter.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright 2014-present 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
- *
- * https://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 java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
-import com.mongodb.BasicDBObject;
-import com.mongodb.DBObject;
-import org.bson.Document;
-import org.bson.types.Binary;
-
-import org.springframework.core.convert.converter.Converter;
-import org.springframework.core.serializer.support.DeserializingConverter;
-import org.springframework.core.serializer.support.SerializingConverter;
-import org.springframework.data.mongodb.core.query.Criteria;
-import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.lang.Nullable;
-import org.springframework.session.FindByIndexNameSessionRepository;
-import org.springframework.session.Session;
-import org.springframework.util.Assert;
-
-/**
- * {@code AbstractMongoSessionConverter} implementation using standard Java serialization.
- *
- * @author Jakub Kubrynski
- * @author Rob Winch
- * @author Greg Turnquist
- * @since 1.2
- */
-public class JdkMongoSessionConverter extends AbstractMongoSessionConverter {
-
- private static final String ID = "_id";
-
- private static final String CREATION_TIME = "created";
-
- private static final String LAST_ACCESSED_TIME = "accessed";
-
- private static final String MAX_INTERVAL = "interval";
-
- private static final String ATTRIBUTES = "attr";
-
- private static final String PRINCIPAL_FIELD_NAME = "principal";
-
- private final Converter