Reputation: 11818
I can't figure out how to stream a binary file from GridFS with spring-data-mongodb and its GridFSTemplate
when I already have the right ObjectId
.
GridFSTemplate returns either GridFSResource
(getResource()
) or GridFSFile
(findX()
).
I can get the GridFSFile
by ID:
// no way to get the InputStream?
GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(id)))
but there is no obvious way how to get an InputStream
for that GridFSFile
.
Only GridFSResource
allows me to get hold of the corresonding InputStream
with InputStreamResource#getInputstream
. But the only way to get a GridFSResource
is by its filename
.
// no way to get GridFSResource by ID?
GridFSResource resource = gridFsTemplate.getResource("test.jpeg");
return resource.getInputStream();
Somehow the GridFsTemplate
API implies that filenames are unique - which they are not. The GridFsTemplate
implementation just returns the first element.
Now I'm using the native MongoDB API and everything makes sense again:
GridFS gridFs = new GridFs(mongo);
GridFSDBFile nativeFile = gridFs.find(blobId);
return nativeFile.getInputStream();
It looks like I'm misunderstanding the fundamental concepts behind the Spring Data Mongo GridFS abstraction. I'd expect (at least) one of the following things to be possible/true:
GridFSResource
by its IDGridFSResource
or InputStream
for a GridFsFile
I already haveAm I wrong or is there something odd with this particular piece of the Spring Data MongoDB API?
Upvotes: 17
Views: 14467
Reputation: 21239
Spring Data 2.1.0 added an overload of getResource()
to GridFsTemplate
that returns the GridFsResource
for a given GridFsFile
. GridFsResource
has a method to get the InputStream
. Therefore, if you're on at least this version of Spring Data, you can get the InputStream
by making two calls to the GridFsTemplate
:
GridFSFile gridFsFile =
gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(id)));
// In real code, perform necessary null checks in case the file doesn't exist
GridFsResource resource = gridFsTemplate.getResource(gridFsFile);
InputStream inputStream = resource.getInputStream();
Upvotes: 2
Reputation: 2479
Have you considered using Spring Content for Mongo for the content storage piece on your solution?
Assuming you are using Spring Boot as well as Spring Data Mongo then it might look something like the following:
pom.xml
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-mongo-boot-starter</artifactId>
<version>0.0.10</version>
</dependency>
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest-boot-starter</artifactId>
<version>0.0.10</version>
</dependency>
Update your Spring Data Mongo entity, with the following attributes:
@ContentId
private String contentId;
@ContentLength
private long contentLength = 0L;
@MimeType
private String mimeType;
Add a store interface:
@StoreRestResource(path="content")
public interface MongoContentStore extends ContentStore<YourEntity, String> {
}
That's all that you need. When you application starts Spring Content will see the dependencies on the Spring Content Mongo/REST modules and it will inject an implementation of the MongonContenStore
store for GridFs as well as an implementation of a controller that supports full CRUD functionality and maps those operations down onto the underlying store interface. The REST endpoint will be available under /content
.
i.e.
curl -X PUT /content/{entityId}
will create or update an entity's image
curl -X GET /content/{entityId}
will fetch the entity's image
curl -X DELETE /content/{entityId}
will delete the entity's image
There are a couple of getting started guides here. They use Spring Content for the filesystem but the modules are interchangeable. The Mongo reference guide is here. And there is a tutorial video here.
HTH
Upvotes: 2
Reputation: 3657
Old question I know, but trying to do this in 2019 using WebFlux, I had to do the following
public Mono<GridFsResource> getImageFromDatabase(final String id) {
return Mono.fromCallable(
() ->
this.gridFsTemplate.getResource(
Objects.requireNonNull(
this.gridFsTemplate.findOne(new Query(Criteria.where("_id").is(id))))
.getFilename()));
}
Which will give you a Mono
which can be returned in a controller. I'm sure there is a nicer solution however.
Upvotes: 0
Reputation: 218
@RequestMapping(value = "/api ")
public class AttachmentController {
private final GridFsOperations gridFsOperations;
@Autowired
public AttachmentController(GridFsOperations gridFsOperations) {
this.gridFsOperations = gridFsOperations;
}
@GetMapping("/file/{fileId}")
public ResponseEntity<Resource> getFile(@PathVariable String fileId) {
GridFSFile file =
gridFsOperations.findOne(Query.query(Criteria.where("_id").is(fileId)));
return ResponseEntity.ok()
.contentLength(file.getLength())
.body(gridFsOperations.getResource(file));
}
Upvotes: 0
Reputation: 218
getResource(com.mongodb.client.gridfs.model.GridFSFile file) function of GridFsTemplate returns the GridFsResource for a GridFSFile.
GridFSFile gridfsFile= gridFsTemplate.findOne(new
Query(Criteria.where("filename").is(fileName)));
GridFsResource gridFSResource= gridFsTemplate.getResource(gridfsFile);
InputStream inputStream= gridFSResource.getInputStream();
If the above one is not working in some higher version of Spring boot, use the bellow:
GridFSFile gridfsFile= gridFsTemplate.findOne(new
Query(Criteria.where("filename").is(fileName)));
//or
GridFSFile gridfsFile =
gridFsOperations.findOne(Query.query(Criteria.where("filename").is(fileName)));
return ResponseEntity.ok()
.contentLength(gridFsdbFile.getLength())
.contentType(MediaType.valueOf("image/png"))
.body(gridFsOperations.getResource(gridFsdbFile));
Upvotes: 0
Reputation: 161
Wrap the GridFSFile in a GridFsResource or use this
GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
GridFsResource resource = gridFsTemplate.getResource(file);
return resource.getInputStream();
Upvotes: 0
Reputation: 55
i discovered the solution to this problem!
Just wrap the GridFSFile in a GridFsResource! This is designed to be instantiated with a GridFSFile.
public GridFsResource getUploadedFileResource(String id) {
var file = this.gridFsTemplate.findOne(new Query(Criteria.where("_id").is(id)));
return new GridFsResource(file);
}
@GetMapping("/{userId}/files/{id}")
public ResponseEntity<InputStreamResource> getUploadedFile(
@PathVariable Long userId,
@PathVariable String id
){
var user = userService
.getCurrentUser()
.orElseThrow(EntityNotFoundException::new);
var resource = userService.getUploadedFileResource(id);
try {
return ResponseEntity
.ok()
.contentType(MediaType.parseMediaType(resource.getContentType()))
.contentLength(resource.contentLength())
.body(resource);
} catch (IOException e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
The great advantage of this is, that you can directly pass the GridFsResource to a ResponseEntity due to the fact, that the GridFsResource extends a InputStreamResource.
Hope this helps!
Greetings Niklas
Upvotes: 1
Reputation: 126
I stumbled upon this, too. And I am actually pretty shocked that the GridFsTemplate has been designed like this... Anyway, my ugly "solution" to this so far:
public GridFsResource download(String fileId) {
GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
return new GridFsResource(file, getGridFs().openDownloadStream(file.getObjectId()));
}
private GridFSBucket getGridFs() {
MongoDatabase db = mongoDbFactory.getDb();
return GridFSBuckets.create(db);
}
Note: You have to inject the MongoDbFactory for this to work...
Upvotes: 11
Reputation: 113
There is a bit mess in these types:
From Spring GridFsTemplate source:
public getResource(String location) {
GridFSFile file = findOne(query(whereFilename().is(location)));
return file != null ? new GridFsResource(file, getGridFs().openDownloadStream(location)) : null;
}
There is an ugly solution:
@Autowired
private GridFsTemplate template;
@Autowired
private GridFsOperations operations;
public InputStream loadResource(ObjectId id) throws IOException {
GridFSFile file = template.findOne(query(where("_id").is(id)));
GridFsResource resource = template.getResource(file.getFilename());
GridFSFile file = operations.findOne(query(where("_id").is(id)));
GridFsResource resource = operations.getResource(file.getFilename());
return resource.getInputStream();
}
Upvotes: 6
Reputation: 11
GridFSDBFile file = ...
ByteArrayOutputStream baos = new ByteArrayOutputStream();
file.writeTo(baos);
byte[] ba = baos.toByteArray()
Upvotes: -3