This is a space that holds a collection of my personal work and ideas

Spring Data MongoDB GridFS 3.4

Posted on 10/17/2018

This article shows a typical usage of MongoDB GridFS with Spring Data MongoDB. This is for MongoDB java driver v3.4 and Spring Data MongoDB v1.10.x. Note that Spring Data MongoDB v2.X introduces breaking changes with the MongoDB java driver 3.6+. This article only shows the usage of the v3.4 driver with Spring Data MongoDB v1.10.x.

FileService Interface

public interface FileService {
    GridFSDBFile find(String id);
    Optional<GridFsResource> findAsResource(String id);
    String store(String name, MultipartFile file);
    void delete(String id);
}

FileService Implementation

We can't use GridFsTemplate.getResource(String location) here to get the resource because there might be files with the same name. To find the expected file, we need to find a list of the resources that all match the same filename and then filter them by the id after retrieving all the files. This is acceptable because at that point, the InputStream hasn't been requested yet. GridFsTemplate.getResources(String locationPattern) takes an Ant matching pattern and it returns all resources whose filename passes the test.

GridFsTemplate.store() returns an Object whose string literal could be converted to a ObjectId type. For instance, ObjectId objectId = new ObjectId(GridFsTemplate.store(...).toString()).

@Service
public class FileServiceImpl implements FileService {
    private GridFsTemplate gridFsTemplate;

    @Autowired
    public FileServiceImpl(final GridFsTemplate gridFsTemplate) {
        this.gridFsTemplate = gridFsTemplate;
    }


    @Override
    public GridFSDBFile find(final String id) {
        ObjectId objectId = new ObjectId(id);
        return gridFsTemplate.findOne(new Query(Criteria.where("_id").is(objectId)));
    }

    @Override
    public Optional<GridFsResource> findAsResource(final String id) {
        return Stream.of(gridFsTemplate.getResources(find(id).getFilename() + "*"))
            .filter(gridFsResource -> gridFsResource.getId().toString().equals(id))
            .findAny();
    }

    @Override
    public String store(String name, final MultipartFile file) {
        try {
            String filename = file.getOriginalFilename();
            if (name != null) {
                filename = FileNameUtils.getExtension(filename)
                    .map(ext -> name + "." + ext)
                    .orElse(name);
            }

            InputStream inputStream = file.getInputStream();
            GridFSFile gridFSFile = gridFsTemplate.store(
                inputStream, filename, file.getContentType());
            if (gridFSFile != null) {
                return gridFSFile.getId().toString();
            } throw new FileException("Failed to save file");
        } catch (IOException e) {
            throw new FileException(e);
        }
    }

    @Override
    public void delete(final String id) {
        ObjectId objectId = new ObjectId(id);
        gridFsTemplate.delete(new Query(Criteria.where("_id").is(objectId)));
    }
}

FileController

@RestController
@RequestMapping("/api/v1/files")
public class FileController {
    private FileService fileService;

    @Autowired
    public FileController(final FileService fileService) {
        this.fileService = fileService;
    }

    @PostMapping
    public Attachment uploadAttachment(@RequestParam("name") String name,
                                 @RequestParam("file") MultipartFile file) {
        String id =  fileService.store(name, file);
        return new Attachment(name, id);
    }

    @GetMapping("/{id}")
    public ResponseEntity<InputStreamResource> downloadFile(@PathVariable("id") String id) {
        Optional<GridFsResource> resourceOptional = fileService.findAsResource(id);

        return resourceOptional.map(resource -> {
            try {
                return ResponseEntity
                    .ok()
                    .contentType(MediaType.valueOf(resource.getContentType()))
                    .header(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment; filename="" + resource.getFilename() + """)
                    .body(new InputStreamResource(resource.getInputStream()));
            } catch (IOException e) {
                throw new FileException(e);
            }
        }).orElseThrow(() -> new FileException("File not found"));
    }

    @DeleteMapping("/{id}")
    public void deleteFile(@PathVariable("id") String id) {
        fileService.delete(id);
    }

    class Attachment {
        private String name;
        private String fileId;

        Attachment(final String name, final String fileId) {
            this.name = name;
            this.fileId = fileId;
        }
        // Getters and setters...
    }

}

References

  1. Mongo Java Driver v3.4
  2. Spring Data MongoDB v.1.10.16 API
  3. Spring Data MongoDB v.1.10.16 Reference

Read on GitHub