Browse Source
We now support storing GridFS content with predefined id, which allows to replace an existing resource instead of having to delete and reupload it. Original pull request: #842.pull/846/head
13 changed files with 981 additions and 55 deletions
@ -0,0 +1,167 @@
@@ -0,0 +1,167 @@
|
||||
/* |
||||
* Copyright 2020 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.data.mongodb.gridfs; |
||||
|
||||
import org.bson.Document; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
import com.mongodb.client.gridfs.model.GridFSFile; |
||||
|
||||
/** |
||||
* A common interface when dealing with GridFs items using Spring Data.o |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 3.0 |
||||
*/ |
||||
public interface GridFsObject<ID, CONTENT> { |
||||
|
||||
/** |
||||
* The {@link GridFSFile#getId()} value converted into its simple java type. <br /> |
||||
* A {@link org.bson.BsonString} will be converted to plain {@link String}. |
||||
* |
||||
* @return can be {@literal null} depending on the implementation. |
||||
*/ |
||||
@Nullable |
||||
ID getFileId(); |
||||
|
||||
/** |
||||
* The filename. |
||||
* |
||||
* @return |
||||
*/ |
||||
String getFilename(); |
||||
|
||||
/** |
||||
* The actual file content. |
||||
* |
||||
* @return |
||||
*/ |
||||
CONTENT getContent(); |
||||
|
||||
/** |
||||
* Additional information like file metadata (eg. contentType). |
||||
* |
||||
* @return never {@literal null}. |
||||
*/ |
||||
Options getOptions(); |
||||
|
||||
/** |
||||
* Additional, context relevant information. |
||||
* |
||||
* @author Christoph Strobl |
||||
*/ |
||||
class Options { |
||||
|
||||
private Document metadata = new Document(); |
||||
private int chunkSize = -1; |
||||
|
||||
private Options(Document metadata, int chunkSize) { |
||||
|
||||
this.metadata = metadata; |
||||
this.chunkSize = chunkSize; |
||||
} |
||||
|
||||
/** |
||||
* Static factory to create empty options. |
||||
* |
||||
* @return new instance of {@link Options}. |
||||
*/ |
||||
public static Options none() { |
||||
return new Options(new Document(), -1); |
||||
} |
||||
|
||||
/** |
||||
* Static factory method to create {@link Options} with given chunk size. |
||||
* |
||||
* @param chunkSize |
||||
* @return new instance of {@link Options}. |
||||
*/ |
||||
public static Options chunked(int chunkSize) { |
||||
return new Options(new Document(), chunkSize); |
||||
} |
||||
|
||||
/** |
||||
* Static factory method to create {@link Options} with given content type. |
||||
* |
||||
* @param contentType |
||||
* @return new instance of {@link Options}. |
||||
*/ |
||||
public static Options typed(String contentType) { |
||||
return new Options(new Document("_contentType", contentType), -1); |
||||
} |
||||
|
||||
/** |
||||
* Static factory method to create {@link Options} by extracting information from the given {@link GridFSFile}. |
||||
* |
||||
* @param gridFSFile can be {@literal null}, returns {@link #none()} in that case. |
||||
* @return new instance of {@link Options}. |
||||
*/ |
||||
public static Options from(@Nullable GridFSFile gridFSFile) { |
||||
return gridFSFile != null ? new Options(gridFSFile.getMetadata(), gridFSFile.getChunkSize()) : none(); |
||||
} |
||||
|
||||
/** |
||||
* Set the associated content type. |
||||
* |
||||
* @param contentType must not be {@literal null}. |
||||
* @return new instance of {@link Options}. |
||||
*/ |
||||
public Options contentType(String contentType) { |
||||
|
||||
Options target = new Options(new Document(metadata), chunkSize); |
||||
target.metadata.put("_contentType", contentType); |
||||
return target; |
||||
} |
||||
|
||||
/** |
||||
* @param metadata |
||||
* @return new instance of {@link Options}. |
||||
*/ |
||||
public Options metadata(Document metadata) { |
||||
return new Options(metadata, chunkSize); |
||||
} |
||||
|
||||
/** |
||||
* @param chunkSize the file chunk size to use. |
||||
* @return new instance of {@link Options}. |
||||
*/ |
||||
public Options chunkSize(int chunkSize) { |
||||
return new Options(metadata, chunkSize); |
||||
} |
||||
|
||||
/** |
||||
* @return never {@literal null}. |
||||
*/ |
||||
public Document getMetadata() { |
||||
return metadata; |
||||
} |
||||
|
||||
/** |
||||
* @return the chunk size to use. |
||||
*/ |
||||
public int getChunkSize() { |
||||
return chunkSize; |
||||
} |
||||
|
||||
/** |
||||
* @return {@literal null} if not set. |
||||
*/ |
||||
@Nullable |
||||
String getContentType() { |
||||
return (String) metadata.get("_contentType"); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,214 @@
@@ -0,0 +1,214 @@
|
||||
/* |
||||
* Copyright 2020 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.data.mongodb.gridfs; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
|
||||
import org.bson.Document; |
||||
import org.bson.types.ObjectId; |
||||
import org.springframework.data.util.Lazy; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
import com.mongodb.client.gridfs.model.GridFSFile; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
* @since 3.0 |
||||
*/ |
||||
public class GridFsUpload<ID> implements GridFsObject<ID, InputStream> { |
||||
|
||||
private static final InputStream EMPTY_STREAM = new InputStream() { |
||||
@Override |
||||
public int read() throws IOException { |
||||
return -1; |
||||
} |
||||
}; |
||||
|
||||
private ID id; |
||||
private Lazy<InputStream> dataStream; |
||||
private String filename; |
||||
private Options options; |
||||
|
||||
/** |
||||
* The {@link GridFSFile#getId()} value converted into its simple java type. <br /> |
||||
* A {@link org.bson.BsonString} will be converted to plain {@link String}. |
||||
* |
||||
* @return can be {@literal null}. |
||||
* @see org.springframework.data.mongodb.gridfs.GridFsObject#getFileId() |
||||
*/ |
||||
@Override |
||||
public ID getFileId() { |
||||
return id; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.mongodb.gridfs.GridFsObject#getFielname() |
||||
*/ |
||||
@Override |
||||
public String getFilename() { |
||||
return filename; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.mongodb.gridfs.GridFsObject#getContent() |
||||
*/ |
||||
@Override |
||||
public InputStream getContent() { |
||||
return dataStream.orElse(EMPTY_STREAM); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.mongodb.gridfs.GridFsObject#getOptions() |
||||
*/ |
||||
@Override |
||||
public Options getOptions() { |
||||
return options; |
||||
} |
||||
|
||||
/** |
||||
* Create a new instance of {@link GridFsUpload} for the given {@link InputStream}. |
||||
* |
||||
* @param stream must not be {@literal null}. |
||||
* @return new instance of {@link GridFsUpload}. |
||||
*/ |
||||
public static GridFsUploadBuilder<ObjectId> fromStream(InputStream stream) { |
||||
return new GridFsUploadBuilder().content(stream); |
||||
} |
||||
|
||||
/** |
||||
* Builder to create {@link GridFsUpload} in a fluent way. |
||||
* |
||||
* @param <T> the target id type. |
||||
*/ |
||||
public static class GridFsUploadBuilder<T> { |
||||
|
||||
private GridFsUpload upload; |
||||
|
||||
public GridFsUploadBuilder() { |
||||
this.upload = new GridFsUpload(); |
||||
this.upload.options = Options.none(); |
||||
} |
||||
|
||||
/** |
||||
* Define the content of the file to upload. |
||||
* |
||||
* @param stream the upload content. |
||||
* @return this. |
||||
*/ |
||||
public GridFsUploadBuilder<T> content(InputStream stream) { |
||||
|
||||
upload.dataStream = Lazy.of(() -> stream); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the id to use. |
||||
* |
||||
* @param id the id to save the content to. |
||||
* @param <T1> |
||||
* @return this. |
||||
*/ |
||||
public <T1> GridFsUploadBuilder<T1> id(T1 id) { |
||||
|
||||
upload.id = id; |
||||
return (GridFsUploadBuilder<T1>) this; |
||||
} |
||||
|
||||
/** |
||||
* Set the filename. |
||||
* |
||||
* @param filename the filename to use. |
||||
* @return this. |
||||
*/ |
||||
public GridFsUploadBuilder<T> filename(String filename) { |
||||
|
||||
upload.filename = filename; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set additional file information. |
||||
* |
||||
* @param options must not be {@literal null}. |
||||
* @return this. |
||||
*/ |
||||
public GridFsUploadBuilder<T> options(Options options) { |
||||
|
||||
upload.options = options; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the file metadata. |
||||
* |
||||
* @param metadata must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
public GridFsUploadBuilder<T> metadata(Document metadata) { |
||||
|
||||
upload.options = upload.options.metadata(metadata); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the upload chunk size in bytes. |
||||
* |
||||
* @param chunkSize use negative number for default. |
||||
* @return this. |
||||
*/ |
||||
public GridFsUploadBuilder<T> chunkSize(int chunkSize) { |
||||
|
||||
upload.options = upload.options.chunkSize(chunkSize); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set id, filename, metadata and chunk size from given file. |
||||
* |
||||
* @param gridFSFile must not be {@literal null}. |
||||
* @return this. |
||||
*/ |
||||
public GridFsUploadBuilder<T> gridFsFile(GridFSFile gridFSFile) { |
||||
|
||||
upload.id = gridFSFile.getId(); |
||||
upload.filename = gridFSFile.getFilename(); |
||||
upload.options = upload.options.metadata(gridFSFile.getMetadata()); |
||||
upload.options = upload.options.chunkSize(gridFSFile.getChunkSize()); |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the content type. |
||||
* |
||||
* @param contentType must not be {@literal null}. |
||||
* @return this. |
||||
*/ |
||||
public GridFsUploadBuilder<T> contentType(String contentType) { |
||||
|
||||
upload.options = upload.options.contentType(contentType); |
||||
return this; |
||||
} |
||||
|
||||
public GridFsUpload<T> build() { |
||||
return (GridFsUpload<T>) upload; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,205 @@
@@ -0,0 +1,205 @@
|
||||
/* |
||||
* Copyright 2020 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.data.mongodb.gridfs; |
||||
|
||||
import org.bson.Document; |
||||
import org.bson.types.ObjectId; |
||||
import org.reactivestreams.Publisher; |
||||
import org.springframework.core.io.buffer.DataBuffer; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
import com.mongodb.client.gridfs.model.GridFSFile; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
* @since 3.0 |
||||
*/ |
||||
public class ReactiveGridFsUpload<ID> implements GridFsObject<ID, Publisher<DataBuffer>> { |
||||
|
||||
private ID id; |
||||
private Publisher<DataBuffer> dataStream; |
||||
private String filename; |
||||
private Options options; |
||||
|
||||
/** |
||||
* The {@link GridFSFile#getId()} value converted into its simple java type. <br /> |
||||
* A {@link org.bson.BsonString} will be converted to plain {@link String}. |
||||
* |
||||
* @return can be {@literal null}. |
||||
* @see org.springframework.data.mongodb.gridfs.GridFsObject#getFileId() |
||||
*/ |
||||
@Override |
||||
public ID getFileId() { |
||||
return id; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.mongodb.gridfs.GridFsObject#getFielname() |
||||
*/ |
||||
@Override |
||||
public String getFilename() { |
||||
return filename; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.mongodb.gridfs.GridFsObject#getContent() |
||||
*/ |
||||
@Override |
||||
public Publisher<DataBuffer> getContent() { |
||||
return dataStream; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.mongodb.gridfs.GridFsObject#getOptions() |
||||
*/ |
||||
@Override |
||||
public Options getOptions() { |
||||
return options; |
||||
} |
||||
|
||||
/** |
||||
* Create a new instance of {@link ReactiveGridFsUpload} for the given {@link Publisher}. |
||||
* |
||||
* @param source must not be {@literal null}. |
||||
* @return new instance of {@link GridFsUpload}. |
||||
*/ |
||||
public static ReactiveGridFsUploadBuilder<ObjectId> fromPublisher(Publisher<DataBuffer> source) { |
||||
return new ReactiveGridFsUploadBuilder().content(source); |
||||
} |
||||
|
||||
/** |
||||
* Builder to create {@link ReactiveGridFsUpload} in a fluent way. |
||||
* |
||||
* @param <T> the target id type. |
||||
*/ |
||||
public static class ReactiveGridFsUploadBuilder<T> { |
||||
|
||||
ReactiveGridFsUpload upload; |
||||
|
||||
public ReactiveGridFsUploadBuilder() { |
||||
|
||||
this.upload = new ReactiveGridFsUpload(); |
||||
this.upload.options = Options.none(); |
||||
} |
||||
|
||||
/** |
||||
* Define the content of the file to upload. |
||||
* |
||||
* @param source the upload content. |
||||
* @return this. |
||||
*/ |
||||
public ReactiveGridFsUploadBuilder<T> content(Publisher<DataBuffer> source) { |
||||
upload.dataStream = source; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the id to use. |
||||
* |
||||
* @param id the id to save the content to. |
||||
* @param <T1> |
||||
* @return this. |
||||
*/ |
||||
public <T1> ReactiveGridFsUploadBuilder<T1> id(T1 id) { |
||||
|
||||
upload.id = id; |
||||
return (ReactiveGridFsUploadBuilder<T1>) this; |
||||
} |
||||
|
||||
/** |
||||
* Set the filename. |
||||
* |
||||
* @param filename the filename to use. |
||||
* @return this. |
||||
*/ |
||||
public ReactiveGridFsUploadBuilder<T> filename(String filename) { |
||||
|
||||
upload.filename = filename; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set additional file information. |
||||
* |
||||
* @param options must not be {@literal null}. |
||||
* @return this. |
||||
*/ |
||||
public ReactiveGridFsUploadBuilder<T> options(Options options) { |
||||
|
||||
upload.options = options; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the file metadata. |
||||
* |
||||
* @param metadata must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
public ReactiveGridFsUploadBuilder<T> metadata(Document metadata) { |
||||
|
||||
upload.options = upload.options.metadata(metadata); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the upload chunk size in bytes. |
||||
* |
||||
* @param chunkSize use negative number for default. |
||||
* @return |
||||
*/ |
||||
public ReactiveGridFsUploadBuilder<T> chunkSize(int chunkSize) { |
||||
|
||||
upload.options = upload.options.chunkSize(chunkSize); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set id, filename, metadata and chunk size from given file. |
||||
* |
||||
* @param gridFSFile must not be {@literal null}. |
||||
* @return this. |
||||
*/ |
||||
public ReactiveGridFsUploadBuilder<T> gridFsFile(GridFSFile gridFSFile) { |
||||
|
||||
upload.id = gridFSFile.getId(); |
||||
upload.filename = gridFSFile.getFilename(); |
||||
upload.options = upload.options.metadata(gridFSFile.getMetadata()); |
||||
upload.options = upload.options.chunkSize(gridFSFile.getChunkSize()); |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the content type. |
||||
* |
||||
* @param contentType must not be {@literal null}. |
||||
* @return this. |
||||
*/ |
||||
public ReactiveGridFsUploadBuilder<T> contentType(String contentType) { |
||||
|
||||
upload.options = upload.options.contentType(contentType); |
||||
return this; |
||||
} |
||||
|
||||
public ReactiveGridFsUpload<T> build() { |
||||
return (ReactiveGridFsUpload<T>) upload; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
/* |
||||
* Copyright 2020 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.data.mongodb.util.json; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import org.bson.BsonDouble; |
||||
import org.bson.BsonInt32; |
||||
import org.bson.BsonInt64; |
||||
import org.bson.BsonObjectId; |
||||
import org.bson.BsonString; |
||||
import org.bson.types.ObjectId; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.springframework.data.mongodb.util.BsonUtils; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
*/ |
||||
class BsonUtilsTest { |
||||
|
||||
@Test // DATAMONGO-625
|
||||
void simpleToBsonValue() { |
||||
|
||||
assertThat(BsonUtils.simpleToBsonValue(Long.valueOf(10))).isEqualTo(new BsonInt64(10)); |
||||
assertThat(BsonUtils.simpleToBsonValue(new Integer(10))).isEqualTo(new BsonInt32(10)); |
||||
assertThat(BsonUtils.simpleToBsonValue(Double.valueOf(0.1D))).isEqualTo(new BsonDouble(0.1D)); |
||||
assertThat(BsonUtils.simpleToBsonValue("value")).isEqualTo(new BsonString("value")); |
||||
} |
||||
|
||||
@Test // DATAMONGO-625
|
||||
void primitiveToBsonValue() { |
||||
assertThat(BsonUtils.simpleToBsonValue(10L)).isEqualTo(new BsonInt64(10)); |
||||
} |
||||
|
||||
@Test // DATAMONGO-625
|
||||
void objectIdToBsonValue() { |
||||
|
||||
ObjectId source = new ObjectId(); |
||||
assertThat(BsonUtils.simpleToBsonValue(source)).isEqualTo(new BsonObjectId(source)); |
||||
} |
||||
|
||||
@Test // DATAMONGO-625
|
||||
void bsonValueToBsonValue() { |
||||
|
||||
BsonObjectId source = new BsonObjectId(new ObjectId()); |
||||
assertThat(BsonUtils.simpleToBsonValue(source)).isSameAs(source); |
||||
} |
||||
|
||||
@Test // DATAMONGO-625
|
||||
void unsupportedToBsonValue() { |
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> BsonUtils.simpleToBsonValue(new Object())); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue