Introduce ReactiveMongoSessionRepository.

This commit is contained in:
Greg Turnquist
2019-09-27 10:42:47 -05:00
parent e3b4c38214
commit 4fbb1d3781
7 changed files with 221 additions and 223 deletions

View File

@@ -15,183 +15,20 @@
*/
package org.springframework.session.data.mongo;
import static org.springframework.session.data.mongo.MongoSessionUtils.*;
import java.time.Duration;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
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.session.ReactiveSessionRepository;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import com.mongodb.DBObject;
/**
* This {@link ReactiveSessionRepository} implementation is kept to support migration to
* {@link ReactiveMongoSessionRepository} in a backwards compatible manner.
*
* @author Greg Turnquist
* @deprecated since 2.2.0 in favor of {@link ReactiveMongoSessionRepository}.
*/
public class ReactiveMongoOperationsSessionRepository
implements ReactiveSessionRepository<MongoSession>, ApplicationEventPublisherAware, InitializingBean {
/**
* 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 static final Logger logger = LoggerFactory.getLogger(ReactiveMongoOperationsSessionRepository.class);
private final ReactiveMongoOperations mongoOperations;
private Integer maxInactiveIntervalInSeconds = DEFAULT_INACTIVE_INTERVAL;
private String collectionName = DEFAULT_COLLECTION_NAME;
private AbstractMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(
Duration.ofSeconds(this.maxInactiveIntervalInSeconds));
private MongoOperations blockingMongoOperations;
private ApplicationEventPublisher eventPublisher;
@Deprecated
public class ReactiveMongoOperationsSessionRepository extends ReactiveMongoSessionRepository {
public ReactiveMongoOperationsSessionRepository(ReactiveMongoOperations mongoOperations) {
this.mongoOperations = mongoOperations;
}
/**
* Creates a new {@link MongoSession} that is capable of being persisted by this {@link ReactiveSessionRepository}.
* <p>
* 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.
* </p>
*
* @return a new {@link MongoSession} that is capable of being persisted by this {@link ReactiveSessionRepository}
*/
@Override
public Mono<MongoSession> createSession() {
return Mono.justOrEmpty(this.maxInactiveIntervalInSeconds) //
.map(MongoSession::new) //
.doOnNext(mongoSession -> publishEvent(new SessionCreatedEvent(this, mongoSession))) //
.switchIfEmpty(Mono.just(new MongoSession()));
}
/**
* Ensures the {@link MongoSession} created by {@link ReactiveSessionRepository#createSession()} is saved.
* <p>
* 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.
* </p>
*
* @param session the {@link MongoSession} to save
*/
@Override
public Mono<Void> save(MongoSession session) {
DBObject dbObject = convertToDBObject(this.mongoSessionConverter, session);
if (dbObject != null) {
return this.mongoOperations.save(dbObject, this.collectionName).then();
} else {
return Mono.empty();
}
}
/**
* Gets the {@link MongoSession} by the {@link MongoSession#getId()} or {@link Mono#empty()} if no
* {@link MongoSession} is found.
*
* @param id the {@link MongoSession#getId()} to lookup
* @return the {@link MongoSession} by the {@link MongoSession#getId()} or {@link Mono#empty()} if no
* {@link MongoSession} is found.
*/
@Override
public Mono<MongoSession> findById(String id) {
return findSession(id) //
.map(document -> convertToSession(this.mongoSessionConverter, document)) //
.filter(mongoSession -> !mongoSession.isExpired()) //
.switchIfEmpty(Mono.defer(() -> this.deleteById(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<Void> deleteById(String id) {
return findSession(id) //
.flatMap(document -> this.mongoOperations.remove(document, this.collectionName) //
.then(Mono.just(document))) //
.map(document -> 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<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) {
try {
this.eventPublisher.publishEvent(event);
} catch (Throwable ex) {
logger.error("Error publishing " + event + ".", ex);
}
}
public Integer getMaxInactiveIntervalInSeconds() {
return this.maxInactiveIntervalInSeconds;
}
public void setMaxInactiveIntervalInSeconds(final Integer maxInactiveIntervalInSeconds) {
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}
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;
super(mongoOperations);
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright 2019 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 static org.springframework.session.data.mongo.MongoSessionUtils.*;
import java.time.Duration;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
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.session.ReactiveSessionRepository;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import com.mongodb.DBObject;
/**
* A {@link ReactiveSessionRepository} implementation that uses Spring Data MongoDB.
*
* @author Greg Turnquist
* @since 2.2.0
*/
public class ReactiveMongoSessionRepository
implements ReactiveSessionRepository<MongoSession>, ApplicationEventPublisherAware, InitializingBean {
/**
* 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 static final Logger logger = LoggerFactory.getLogger(ReactiveMongoSessionRepository.class);
private final ReactiveMongoOperations mongoOperations;
private Integer maxInactiveIntervalInSeconds = DEFAULT_INACTIVE_INTERVAL;
private String collectionName = DEFAULT_COLLECTION_NAME;
private AbstractMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(
Duration.ofSeconds(this.maxInactiveIntervalInSeconds));
private MongoOperations blockingMongoOperations;
private ApplicationEventPublisher eventPublisher;
public ReactiveMongoSessionRepository(ReactiveMongoOperations mongoOperations) {
this.mongoOperations = mongoOperations;
}
/**
* Creates a new {@link MongoSession} that is capable of being persisted by this {@link ReactiveSessionRepository}.
* <p>
* 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.
* </p>
*
* @return a new {@link MongoSession} that is capable of being persisted by this {@link ReactiveSessionRepository}
*/
@Override
public Mono<MongoSession> createSession() {
return Mono.justOrEmpty(this.maxInactiveIntervalInSeconds) //
.map(MongoSession::new) //
.doOnNext(mongoSession -> publishEvent(new SessionCreatedEvent(this, mongoSession))) //
.switchIfEmpty(Mono.just(new MongoSession()));
}
@Override
public Mono<Void> save(MongoSession session) {
DBObject dbObject = convertToDBObject(this.mongoSessionConverter, session);
if (dbObject != null) {
return this.mongoOperations.save(dbObject, this.collectionName).then();
} else {
return Mono.empty();
}
}
@Override
public Mono<MongoSession> findById(String id) {
return findSession(id) //
.map(document -> convertToSession(this.mongoSessionConverter, document)) //
.filter(mongoSession -> !mongoSession.isExpired()) //
.switchIfEmpty(Mono.defer(() -> this.deleteById(id).then(Mono.empty())));
}
@Override
public Mono<Void> deleteById(String id) {
return findSession(id) //
.flatMap(document -> this.mongoOperations.remove(document, this.collectionName) //
.then(Mono.just(document))) //
.map(document -> 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<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) {
try {
this.eventPublisher.publishEvent(event);
} catch (Throwable ex) {
logger.error("Error publishing " + event + ".", ex);
}
}
public Integer getMaxInactiveIntervalInSeconds() {
return this.maxInactiveIntervalInSeconds;
}
public void setMaxInactiveIntervalInSeconds(final Integer maxInactiveIntervalInSeconds) {
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}
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;
}
}

View File

@@ -21,7 +21,7 @@ import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.session.data.mongo.ReactiveMongoOperationsSessionRepository;
import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
/**
* Add this annotation to a {@code @Configuration} class to configure a MongoDB-based {@code WebSessionManager} for a
@@ -59,12 +59,12 @@ public @interface EnableMongoWebSession {
*
* @return default max inactive interval in seconds
*/
int maxInactiveIntervalInSeconds() default ReactiveMongoOperationsSessionRepository.DEFAULT_INACTIVE_INTERVAL;
int maxInactiveIntervalInSeconds() default ReactiveMongoSessionRepository.DEFAULT_INACTIVE_INTERVAL;
/**
* The collection name to use.
*
* @return name of the collection to store session
*/
String collectionName() default ReactiveMongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME;
String collectionName() default ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME;
}

View File

@@ -32,12 +32,12 @@ import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.session.config.annotation.web.server.SpringWebSessionConfiguration;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.JdkMongoSessionConverter;
import org.springframework.session.data.mongo.ReactiveMongoOperationsSessionRepository;
import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
/**
* Configure a {@link ReactiveMongoOperationsSessionRepository} using a provided {@link ReactiveMongoOperations}.
* Configure a {@link ReactiveMongoSessionRepository} using a provided {@link ReactiveMongoOperations}.
*
* @author Greg Turnquist
* @author Vedran Pavić
@@ -55,17 +55,16 @@ public class ReactiveMongoWebSessionConfiguration extends SpringWebSessionConfig
private ClassLoader classLoader;
@Bean
public ReactiveMongoOperationsSessionRepository reactiveMongoOperationsSessionRepository(
ReactiveMongoOperations operations) {
public ReactiveMongoSessionRepository reactiveMongoSessionRepository(ReactiveMongoOperations operations) {
ReactiveMongoOperationsSessionRepository repository = new ReactiveMongoOperationsSessionRepository(operations);
ReactiveMongoSessionRepository repository = new ReactiveMongoSessionRepository(operations);
if (this.mongoSessionConverter != null) {
repository.setMongoSessionConverter(this.mongoSessionConverter);
} else {
JdkMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(new SerializingConverter(),
new DeserializingConverter(this.classLoader),
Duration.ofSeconds(ReactiveMongoOperationsSessionRepository.DEFAULT_INACTIVE_INTERVAL));
Duration.ofSeconds(ReactiveMongoSessionRepository.DEFAULT_INACTIVE_INTERVAL));
repository.setMongoSessionConverter(mongoSessionConverter);
}
@@ -98,7 +97,7 @@ public class ReactiveMongoWebSessionConfiguration extends SpringWebSessionConfig
if (attributes != null) {
this.maxInactiveIntervalInSeconds = attributes.getNumber("maxInactiveIntervalInSeconds");
} else {
this.maxInactiveIntervalInSeconds = ReactiveMongoOperationsSessionRepository.DEFAULT_INACTIVE_INTERVAL;
this.maxInactiveIntervalInSeconds = ReactiveMongoSessionRepository.DEFAULT_INACTIVE_INTERVAL;
}
String collectionNameValue = attributes != null ? attributes.getString("collectionName") : "";