|
|
|
@ -18,9 +18,13 @@ package org.springframework.data.mongodb.gridfs; |
|
|
|
import static org.springframework.data.mongodb.core.query.Query.*; |
|
|
|
import static org.springframework.data.mongodb.core.query.Query.*; |
|
|
|
import static org.springframework.data.mongodb.gridfs.GridFsCriteria.*; |
|
|
|
import static org.springframework.data.mongodb.gridfs.GridFsCriteria.*; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import lombok.RequiredArgsConstructor; |
|
|
|
import reactor.core.publisher.Flux; |
|
|
|
import reactor.core.publisher.Flux; |
|
|
|
import reactor.core.publisher.Mono; |
|
|
|
import reactor.core.publisher.Mono; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import java.nio.ByteBuffer; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import org.bson.BsonValue; |
|
|
|
import org.bson.Document; |
|
|
|
import org.bson.Document; |
|
|
|
import org.bson.types.ObjectId; |
|
|
|
import org.bson.types.ObjectId; |
|
|
|
import org.reactivestreams.Publisher; |
|
|
|
import org.reactivestreams.Publisher; |
|
|
|
@ -39,7 +43,6 @@ import org.springframework.util.StringUtils; |
|
|
|
|
|
|
|
|
|
|
|
import com.mongodb.client.gridfs.model.GridFSFile; |
|
|
|
import com.mongodb.client.gridfs.model.GridFSFile; |
|
|
|
import com.mongodb.client.gridfs.model.GridFSUploadOptions; |
|
|
|
import com.mongodb.client.gridfs.model.GridFSUploadOptions; |
|
|
|
import com.mongodb.reactivestreams.client.MongoDatabase; |
|
|
|
|
|
|
|
import com.mongodb.reactivestreams.client.gridfs.GridFSBucket; |
|
|
|
import com.mongodb.reactivestreams.client.gridfs.GridFSBucket; |
|
|
|
import com.mongodb.reactivestreams.client.gridfs.GridFSBuckets; |
|
|
|
import com.mongodb.reactivestreams.client.gridfs.GridFSBuckets; |
|
|
|
import com.mongodb.reactivestreams.client.gridfs.GridFSFindPublisher; |
|
|
|
import com.mongodb.reactivestreams.client.gridfs.GridFSFindPublisher; |
|
|
|
@ -53,6 +56,7 @@ import com.mongodb.reactivestreams.client.gridfs.GridFSUploadPublisher; |
|
|
|
* @author Nick Stolwijk |
|
|
|
* @author Nick Stolwijk |
|
|
|
* @author Denis Zavedeev |
|
|
|
* @author Denis Zavedeev |
|
|
|
* @author Christoph Strobl |
|
|
|
* @author Christoph Strobl |
|
|
|
|
|
|
|
* @author Mathieu Ouellet |
|
|
|
* @since 2.2 |
|
|
|
* @since 2.2 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public class ReactiveGridFsTemplate extends GridFsOperationsSupport implements ReactiveGridFsOperations { |
|
|
|
public class ReactiveGridFsTemplate extends GridFsOperationsSupport implements ReactiveGridFsOperations { |
|
|
|
@ -130,18 +134,15 @@ public class ReactiveGridFsTemplate extends GridFsOperationsSupport implements R |
|
|
|
uploadOptions.chunkSizeBytes(upload.getOptions().getChunkSize()); |
|
|
|
uploadOptions.chunkSizeBytes(upload.getOptions().getChunkSize()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (upload.getFileId() == null) { |
|
|
|
String filename = upload.getFilename(); |
|
|
|
GridFSUploadPublisher<ObjectId> publisher = getGridFs().uploadFromPublisher(upload.getFilename(), |
|
|
|
Flux<ByteBuffer> source = Flux.from(upload.getContent()).map(DataBuffer::asByteBuffer); |
|
|
|
Flux.from(upload.getContent()).map(DataBuffer::asByteBuffer), uploadOptions); |
|
|
|
T fileId = upload.getFileId(); |
|
|
|
|
|
|
|
if (fileId == null) { |
|
|
|
return (Mono<T>) Mono.from(publisher); |
|
|
|
return (Mono<T>) createMono(new AutoIdCreatingUploadCallback(filename, source, uploadOptions)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
GridFSUploadPublisher<Void> publisher = getGridFs().uploadFromPublisher( |
|
|
|
UploadCallback callback = new UploadCallback(BsonUtils.simpleToBsonValue(fileId), filename, source, uploadOptions); |
|
|
|
BsonUtils.simpleToBsonValue(upload.getFileId()), upload.getFilename(), |
|
|
|
return createMono(callback).then(Mono.just(fileId)); |
|
|
|
Flux.from(upload.getContent()).map(DataBuffer::asByteBuffer), uploadOptions); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Mono.from(publisher).then(Mono.just(upload.getFileId())); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
/* |
|
|
|
@ -150,7 +151,11 @@ public class ReactiveGridFsTemplate extends GridFsOperationsSupport implements R |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public Flux<GridFSFile> find(Query query) { |
|
|
|
public Flux<GridFSFile> find(Query query) { |
|
|
|
return Flux.from(prepareQuery(query)); |
|
|
|
|
|
|
|
|
|
|
|
Document queryObject = getMappedQuery(query.getQueryObject()); |
|
|
|
|
|
|
|
Document sortObject = getMappedQuery(query.getSortObject()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return createFlux(new FindCallback(query, queryObject, sortObject)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
/* |
|
|
|
@ -160,7 +165,10 @@ public class ReactiveGridFsTemplate extends GridFsOperationsSupport implements R |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public Mono<GridFSFile> findOne(Query query) { |
|
|
|
public Mono<GridFSFile> findOne(Query query) { |
|
|
|
|
|
|
|
|
|
|
|
return Flux.from(prepareQuery(query).limit(2)) //
|
|
|
|
Document queryObject = getMappedQuery(query.getQueryObject()); |
|
|
|
|
|
|
|
Document sortObject = getMappedQuery(query.getSortObject()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return createFlux(new FindLimitCallback(query, queryObject, sortObject, 2)) //
|
|
|
|
.collectList() //
|
|
|
|
.collectList() //
|
|
|
|
.flatMap(it -> { |
|
|
|
.flatMap(it -> { |
|
|
|
if (it.isEmpty()) { |
|
|
|
if (it.isEmpty()) { |
|
|
|
@ -182,7 +190,11 @@ public class ReactiveGridFsTemplate extends GridFsOperationsSupport implements R |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public Mono<GridFSFile> findFirst(Query query) { |
|
|
|
public Mono<GridFSFile> findFirst(Query query) { |
|
|
|
return Flux.from(prepareQuery(query).limit(1)).next(); |
|
|
|
|
|
|
|
|
|
|
|
Document queryObject = getMappedQuery(query.getQueryObject()); |
|
|
|
|
|
|
|
Document sortObject = getMappedQuery(query.getSortObject()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return createFlux(new FindLimitCallback(query, queryObject, sortObject, 1)).next(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
/* |
|
|
|
@ -191,7 +203,7 @@ public class ReactiveGridFsTemplate extends GridFsOperationsSupport implements R |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public Mono<Void> delete(Query query) { |
|
|
|
public Mono<Void> delete(Query query) { |
|
|
|
return find(query).flatMap(it -> getGridFs().delete(it.getId())).then(); |
|
|
|
return find(query).flatMap(it -> createMono(new DeleteCallback(it.getId()))).then(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
/* |
|
|
|
@ -216,9 +228,8 @@ public class ReactiveGridFsTemplate extends GridFsOperationsSupport implements R |
|
|
|
|
|
|
|
|
|
|
|
Assert.notNull(file, "GridFSFile must not be null!"); |
|
|
|
Assert.notNull(file, "GridFSFile must not be null!"); |
|
|
|
|
|
|
|
|
|
|
|
return Mono.fromSupplier(() -> { |
|
|
|
return this.doGetBucket() |
|
|
|
return new ReactiveGridFsResource(file, getGridFs().downloadToPublisher(file.getId()), dataBufferFactory); |
|
|
|
.map(it -> new ReactiveGridFsResource(file, it.downloadToPublisher(file.getId()), dataBufferFactory)); |
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
/* |
|
|
|
@ -243,34 +254,117 @@ public class ReactiveGridFsTemplate extends GridFsOperationsSupport implements R |
|
|
|
return getResource(locationPattern).flux(); |
|
|
|
return getResource(locationPattern).flux(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected GridFSFindPublisher prepareQuery(Query query) { |
|
|
|
/** |
|
|
|
|
|
|
|
* Create a reusable Mono for a {@link ReactiveBucketCallback}. It's up to the developer to choose to obtain a new |
|
|
|
|
|
|
|
* {@link Flux} or to reuse the {@link Flux}. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param callback must not be {@literal null} |
|
|
|
|
|
|
|
* @return a {@link Mono} wrapping the {@link ReactiveBucketCallback}. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public <T> Mono<T> createMono(ReactiveBucketCallback<T> callback) { |
|
|
|
|
|
|
|
|
|
|
|
Assert.notNull(query, "Query must not be null!"); |
|
|
|
Assert.notNull(callback, "ReactiveBucketCallback must not be null!"); |
|
|
|
|
|
|
|
|
|
|
|
Document queryObject = getMappedQuery(query.getQueryObject()); |
|
|
|
return Mono.defer(this::doGetBucket).flatMap(bucket -> Mono.from(callback.doInBucket(bucket))); |
|
|
|
Document sortObject = getMappedQuery(query.getSortObject()); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
GridFSFindPublisher publisherToUse = getGridFs().find(queryObject).sort(sortObject); |
|
|
|
/** |
|
|
|
|
|
|
|
* Create a reusable Flux for a {@link ReactiveBucketCallback}. It's up to the developer to choose to obtain a new |
|
|
|
|
|
|
|
* {@link Flux} or to reuse the {@link Flux}. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param callback must not be {@literal null} |
|
|
|
|
|
|
|
* @return a {@link Flux} wrapping the {@link ReactiveBucketCallback}. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public <T> Flux<T> createFlux(ReactiveBucketCallback<T> callback) { |
|
|
|
|
|
|
|
|
|
|
|
if (query.getLimit() > 0) { |
|
|
|
Assert.notNull(callback, "ReactiveBucketCallback must not be null!"); |
|
|
|
publisherToUse = publisherToUse.limit(query.getLimit()); |
|
|
|
|
|
|
|
|
|
|
|
return Mono.defer(this::doGetBucket).flatMapMany(callback::doInBucket); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (query.getSkip() > 0) { |
|
|
|
protected Mono<GridFSBucket> doGetBucket() { |
|
|
|
publisherToUse = publisherToUse.skip(Math.toIntExact(query.getSkip())); |
|
|
|
return dbFactory.getMongoDatabase() |
|
|
|
|
|
|
|
.map(db -> bucket == null ? GridFSBuckets.create(db) : GridFSBuckets.create(db, bucket)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface ReactiveBucketCallback<T> { |
|
|
|
|
|
|
|
Publisher<T> doInBucket(GridFSBucket bucket); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@RequiredArgsConstructor |
|
|
|
|
|
|
|
private static class FindCallback implements ReactiveBucketCallback<GridFSFile> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final Query query; |
|
|
|
|
|
|
|
private final Document queryObject; |
|
|
|
|
|
|
|
private final Document sortObject; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public GridFSFindPublisher doInBucket(GridFSBucket bucket) { |
|
|
|
|
|
|
|
GridFSFindPublisher findPublisher = bucket.find(queryObject).sort(sortObject); |
|
|
|
|
|
|
|
if (query.getLimit() > 0) { |
|
|
|
|
|
|
|
findPublisher = findPublisher.limit(query.getLimit()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (query.getSkip() > 0) { |
|
|
|
|
|
|
|
findPublisher = findPublisher.skip(Math.toIntExact(query.getSkip())); |
|
|
|
|
|
|
|
} |
|
|
|
Integer cursorBatchSize = query.getMeta().getCursorBatchSize(); |
|
|
|
Integer cursorBatchSize = query.getMeta().getCursorBatchSize(); |
|
|
|
if (cursorBatchSize != null) { |
|
|
|
if (cursorBatchSize != null) { |
|
|
|
publisherToUse = publisherToUse.batchSize(cursorBatchSize); |
|
|
|
findPublisher = findPublisher.batchSize(cursorBatchSize); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return findPublisher; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static class FindLimitCallback extends FindCallback { |
|
|
|
|
|
|
|
|
|
|
|
return publisherToUse; |
|
|
|
private final int limit; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public FindLimitCallback(Query query, Document queryObject, Document sortObject, int limit) { |
|
|
|
|
|
|
|
super(query, queryObject, sortObject); |
|
|
|
|
|
|
|
this.limit = limit; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected GridFSBucket getGridFs() { |
|
|
|
@Override |
|
|
|
|
|
|
|
public GridFSFindPublisher doInBucket(GridFSBucket bucket) { |
|
|
|
|
|
|
|
return super.doInBucket(bucket).limit(limit); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
MongoDatabase db = dbFactory.getMongoDatabase(); |
|
|
|
@RequiredArgsConstructor |
|
|
|
return bucket == null ? GridFSBuckets.create(db) : GridFSBuckets.create(db, bucket); |
|
|
|
private static class UploadCallback implements ReactiveBucketCallback<Void> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final BsonValue fileId; |
|
|
|
|
|
|
|
private final String filename; |
|
|
|
|
|
|
|
private final Publisher<ByteBuffer> source; |
|
|
|
|
|
|
|
private final GridFSUploadOptions uploadOptions; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public GridFSUploadPublisher<Void> doInBucket(GridFSBucket bucket) { |
|
|
|
|
|
|
|
return bucket.uploadFromPublisher(fileId, filename, source, uploadOptions); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@RequiredArgsConstructor |
|
|
|
|
|
|
|
private static class AutoIdCreatingUploadCallback implements ReactiveBucketCallback<ObjectId> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final String filename; |
|
|
|
|
|
|
|
private final Publisher<ByteBuffer> source; |
|
|
|
|
|
|
|
private final GridFSUploadOptions uploadOptions; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public GridFSUploadPublisher<ObjectId> doInBucket(GridFSBucket bucket) { |
|
|
|
|
|
|
|
return bucket.uploadFromPublisher(filename, source, uploadOptions); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@RequiredArgsConstructor |
|
|
|
|
|
|
|
private static class DeleteCallback implements ReactiveBucketCallback<Void> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final BsonValue id; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public Publisher<Void> doInBucket(GridFSBucket bucket) { |
|
|
|
|
|
|
|
return bucket.delete(id); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|